Módulo:Exchange rate
Version check
Designación de la versión en Wikidata: 2024-02-24
Uso
Esta documentación está transcluida desde Módulo:Exchange rate/doc.
Los editores pueden experimentar en la zona de pruebas (crear) y en los casos de prueba (crear) del módulo.
Por favor, añade las categorías en la subpágina de documentación. Subpáginas de este módulo.
Los editores pueden experimentar en la zona de pruebas (crear) y en los casos de prueba (crear) del módulo.
Por favor, añade las categorías en la subpágina de documentación. Subpáginas de este módulo.
--[[
Thanks to GiftBot who is uploading/updating currency exchange rates to Wikimedia
Commons. This service is available since March of 2022.
]]--
-- module variable and administration
local er = {
moduleInterface = {
suite = 'Exchange rate',
serial = '2024-02-24',
item = 112066294
}
}
-- require( 'strict' )
-- Exchange-rate tables stored on Wikimedia Commons
local tableNames = {
'ECB euro foreign exchange reference rates.tab',
'Xe.com exchange rates.tab'
}
-- language-dependent error messages
local messages = {
unknownIsoCode = '[[Category:Währung: Seiten mit unbekanntem Währungscode]] <span class="error">Unbekannter Währungscode</span>',
wrongParams = '[[Category:Währung: Fehlerhafte Parameter]] <span class="error">Fehlerhafte(r) Parameter</span>'
}
-- language-dependent constants
local language = {
defaultUnits = { 'EUR', 'CHF', 'USD' },
decimalSep = ',', -- decimal separator
thousandsSep = '.',
commaSep = mw.message.new( 'comma-separator' ):plain(),
dateFormat = 'j. M Y',
convertFormatter = '≈ %s',
defaultFormatter = '%s unit',
wrapperClass = 'voy-currency',
conversionVia = 'EUR', -- EUR or USD
all = 'alle', -- lowercase letters
date = 'datum'
}
-- variables for internal use
local cu -- for currencies-table module
local rateTables = {} -- to prevent multiple fetching
-- check if arg is set
local function isSet( arg )
return arg and arg ~= ''
end
-- returns a currency formatter string for isoCode
-- the following function must be localized
local function getFormatter( isoCode, externalFormatter )
isoCode = isSet( isoCode ) and isoCode:upper() or 'XXX'
if externalFormatter then
return externalFormatter( isoCode )
elseif not cu then
cu = mw.loadData( 'Module:CountryData/Currencies' )
end
local tab = cu.isoToQid[ isoCode ] and cu.currencies[ cu.isoToQid[ isoCode ] ]
local default = cu.currencies.default or language.defaultFormatter
if tab then
if tab.f then
return tab.f
else
local unit = tab.add and tab.add:gsub( ',.*', '' ) or tab.iso
return default:gsub( 'unit', unit )
end
end
return default:gsub( 'unit', isoCode )
end
-- returns count of significant digits
-- zeros after decimal separator are significant
local function getDigitCount( num )
num = num:gsub( '%.', '' ):gsub( '^0+', '' )
return #num
end
-- rounds mantissa/significand of number num to digit count digitCount
local function round( num, digitCount )
return tonumber( string.format( '%.' .. digitCount .. 'g', num ) )
end
-- returns tabularData fields schema as associative table
local function getFields( tabularData )
local fields = {}
local tFields = tabularData.schema.fields
for i = 1, #tFields do
fields[ tFields[ i ].name ] = i
end
return fields
end
-- returns currency-rates table as associative table
-- this is an expensive function: the rateTables should be established only once
local function getRateTable( tableName )
local rows = {}
local colNo, fields, row, tData
if not rateTables[ tableName ] then
local tabularData = mw.ext.data.get( tableName )
if not tabularData then
return nil
end
fields = getFields( tabularData )
colNo = fields[ 'currency' ]
tData = tabularData.data
for i = 1, #tData do
row = tData[ i ]
rows[ row[ colNo ] ] = row
end
rateTables[ tableName ] = {
fields = fields,
rows = rows
}
end
return rateTables[ tableName ]
end
-- returns exchange-rate properties for source -> target iso codes
local function getCurrencyData( rateTable, source, target )
local rate, digitCount, asOf
local fields = rateTable.fields
local row = rateTable.rows[ source ]
if row then
rate = row[ fields[ target ] ]:gsub( ',', '' )
-- remove English thousands separator
digitCount = getDigitCount( rate )
rate = tonumber( rate )
asOf = row[ fields[ 'date' ] ]
end
return rate, digitCount, asOf
end
-- returns exchange rate for source -> target iso codes
-- toRound: Boolean
function er.getRate( source, target, toRound )
-- source, target are three-letter ISO 4217 codes
if not source:match( '^%a%a%a$' ) or not target:match( '^%a%a%a$' ) then
return nil
end
local rateTable, fields, rate, rows, digitCount, asOf
source = source:upper()
target = target:upper()
for i = 1, #tableNames do
rateTable = getRateTable( tableNames[ i ] )
if rateTable then
fields = rateTable.fields
if fields[ target ] then
rate, digitCount, asOf = getCurrencyData( rateTable, source, target )
if rate then
rate = 1/rate
end
elseif fields[ source ] then
rate, digitCount, asOf = getCurrencyData( rateTable, target, source )
elseif fields[ language.conversionVia ] then
local rate1, digitCount1, asOf1 = getCurrencyData( rateTable, source, language.conversionVia )
local rate2, digitCount2, asOf2 = getCurrencyData( rateTable, target, language.conversionVia )
if rate1 and rate2 then
rate = rate2/rate1
digitCount = digitCount1 < digitCount2 and digitCount1 or digitCount2
asOf = asOf1 < asOf2 and asOf1 or asOf2
end
end
end
if rate then
break
end
end
if rate and toRound then
rate = round( rate, digitCount )
end
return rate, asOf, digitCount
end
-- returns a converted date for aDate due to formatStr
local function getDate( aDate, formatStr )
local function formatDate( aDate, formatStr )
return mw.getContentLanguage():formatDate( formatStr, aDate, true )
end
if isSet( aDate ) then
local success, t = pcall( formatDate, aDate, formatStr )
return success and t or ''
else
return ''
end
end
-- inserts thousands separators in amount string
local function insertThousandsSep( amount )
local k
local sep = '%1' .. language.thousandsSep .. '%2'
while true do
amount, k = amount:gsub( '^(-?%d+)(%d%d%d)', sep )
if k == 0 then
break
end
end
return amount
end
-- localizes a number string
local function formatNumber( num )
if language.decimalSep ~= '.' then
num = num:gsub( '%.', language.decimalSep )
end
return insertThousandsSep( num )
end
-- adds the currency unit of isoCode to amount string
local function addUnit( amount, isoCode, externalFormatter )
local formatStr = getFormatter( isoCode, externalFormatter )
return mw.ustring.format( mw.text.decode( formatStr ), amount )
end
local function outputFormat( digits )
digits = math.floor( tonumber( digits ) or 2 )
if digits < 0 or digits > 6 then
digits = 2
end
return '%.'.. digits .. 'f'
end
-- selects different rate outputs due to show
local function formatRate( rate, asOf, show, digits, target )
show = ( show or '' ):lower()
rate = formatNumber( isSet( digits ) and outputFormat( digits ):format( rate )
or tostring( rate ) )
if isSet( digits ) or show == 'all' or show == language.all then
rate = addUnit( rate, target )
end
if show == 'all' or show == language.all then
return rate .. ' (' .. getDate( asOf, language.dateFormat ) .. ')'
elseif show == 'date' or show == language.date then
return getDate( asOf, language.dateFormat )
else
return rate
end
end
-- converts a single currency amount without adding the currency unit
local function convertSingle( source, target, amount, digits )
local rate, asOf, digitCount = er.getRate( source, target )
if rate then
return formatNumber( outputFormat( digits ):format(
round( amount * rate, digitCount ) ):gsub( '%.0*$', '' ) )
else
return nil
end
end
-- converts a single currency amount or an amount range and adding the currency unit
function er._convert( source, targets, amount, withUnit, digits, externalFormatter )
local amount1, amount2, pos, result
local results = {}
if not isSet( targets ) then
targets = language.defaultUnits
withUnit = true
elseif type( targets ) == 'string' then
targets = { targets }
end
amount = amount:gsub( '[ %a%' .. language.thousandsSep .. ']+', '' ):gsub( '-', '–' )
if language.decimalSep ~= '.' then
amount = amount:gsub( language.decimalSep, '.' )
end
for i, target in ipairs( targets ) do
if target ~= source then
pos = mw.ustring.find( amount, '[^,%.%d]' )
if pos then
amount1 = mw.ustring.sub( amount, 1, pos - 1 )
amount2 = tonumber( mw.ustring.sub( amount, pos + 1 ) )
else
amount1 = amount
end
amount1 = tonumber( amount1 ) or 1
result = convertSingle( source, target, amount1, digits )
if pos and result and amount2 then
amount2 = convertSingle( source, target, amount2, digits )
result = amount2 and
( result .. mw.ustring.sub( amount, pos, pos ) .. amount2 )
end
if result then
if withUnit then
result = addUnit( result, target, externalFormatter )
end
table.insert( results, result )
end
end
end
result = table.concat( results, language.commaSep )
return result ~= '' and result
end
-- returns a wrapper format string with tooltip title
function er.getWrapper( amount, source, target, digits, externalFormatter,
withMaintenance)
local formatStr = getFormatter( source, externalFormatter )
local title = er._convert( source, target, amount, true, digits )
if title then
return tostring( mw.html.create( 'abbr' )
:attr( 'title', mw.ustring.format( language.convertFormatter, title ) )
:addClass( language.wrapperClass )
:addClass( language.wrapperClass .. '-' .. source:lower() )
:wikitext( formatStr )
)
else
return formatStr .. ( withMaintenance and messages.wrongParams or '' )
end
end
-- #invoke function returning the exchange rate
function er.rate( frame )
local args = frame.args
local rate, asOf, digitCount = er.getRate( args.source, args.target, true )
return rate and formatRate( rate, asOf, args.show, args.digits, args.target )
or messages.unknownIsoCode
end
-- #invoke function returning the converted amount or amount range
function er.convert( frame )
local args = frame.args
if isSet( args.show ) then
return er.rate( frame )
else
return er._convert( args.source, args.target,
isSet( args.amount ) and args.amount or '1', ( args.plain or '' ) ~= '1',
args.digits ) or messages.wrongParams
end
end
-- #invoke function returning exchange-rate information
-- returns the formatted amount or amount range with a tooltip containing
-- converted values
function er.currencyWithConversions( frame )
local args = frame.args
if not isSet( args.amount ) then
args.amount = '1'
end
return mw.ustring.format(
er.getWrapper( args.amount, args.source, args.target, args.digits, nil, true ),
args.amount:gsub( '-', '–' )
)
end
return er