Module status
The module is currently only able to convert euro in currencies available at c:Data:ECB euro foreign exchange reference rates.tab and c:Data:Xe.com exchange rates.tab.
Functionality
The function rate
returns the number of 1 unit of the currency given as "source" in the currency given as "target" as the raw value calculated from available numbers. If "verbose" is set, the module throws an error if no conversion rate is available.
The function revisionTime
takes "source", "target" and "verbose" and returns the date of the last update for the rate.
The function convert
takes "source", "target" and "verbose" and additionally "amount". It converts the number given in "amount" in the "source" currency into the "target" currency with rounded digits.
local errormsg = ('[[Category:Articles that use unexpected currency]]' .. '<span class="exchangeinfo" style="display:none;" ' .. 'title="Exchange rate not found">Unexpected currency</span>' .. 'rate not found') local function countSigificantDigits(number) number = string.gsub(number, '%.', '', 1) number = mw.text.trim(number, '0') return #number end local function round(num, numSigificantDigits) local numDecimalPlaces = numSigificantDigits - math.floor(math.log10(num)) - 1 local mult = 10^(numDecimalPlaces or 0) return math.floor(num * mult + 0.5) / mult end local function getTabularDataFieldNames(tabularData) local fields = {} for _,field in pairs(tabularData.schema.fields) do table.insert(fields, field.name) end return fields end local function getColumnIndices(fields) local rowCurrencyIndex, dateIndex local targetCurrencyIndices = {} local sourceCurrencyIndices = {} for i,v in pairs(fields) do if v == 'currency' then rowCurrencyIndex = i elseif v == 'date' then dateIndex = i elseif string.match(v, '^%u%u%u$') then sourceCurrencyIndices[v] = i elseif string.match(v, '^_%u%u%u$') then targetCurrencyIndices[string.sub(v,2)] = i end end return rowCurrencyIndex, dateIndex, sourceCurrencyIndices, targetCurrencyIndices end local function getConversionTable(dataPageName) local tabularData = mw.ext.data.get(dataPageName) if not tabularData then return nil end local fields = getTabularDataFieldNames(tabularData) local rowCurrencyIndex, dateIndex, sourceCurrencyIndices, targetCurrencyIndices = getColumnIndices(fields) local conversionTable = {} if rowCurrencyIndex then for _,row in pairs(tabularData.data) do for sourceCurrency,index in pairs(sourceCurrencyIndices) do if not conversionTable[sourceCurrency] then conversionTable[sourceCurrency] = {} end conversionTable[sourceCurrency][row[rowCurrencyIndex]] = {rate = row[index], revisionTime = row[dateIndex]} end for targetCurrency,index in pairs(targetCurrencyIndices) do if not conversionTable[row[rowCurrencyIndex]] then conversionTable[row[rowCurrencyIndex]] = {} end conversionTable[row[rowCurrencyIndex]][targetCurrency] = {rate = row[index], revisionTime = row[dateIndex]} end end end return conversionTable end local function getDataFromRateDataPage(dataPageName, source, target) local conversionTable = getConversionTable(dataPageName) if not conversionTable then return nil end local rate, revisionTime if conversionTable[source] and conversionTable[source][target] then rate = conversionTable[source][target]['rate'] rateSignificantDigits = countSigificantDigits(rate) revisionTime = conversionTable[source][target]['revisionTime'] elseif conversionTable[target] and conversionTable[target][source] then local targetToSourceRate = conversionTable[target][source]['rate'] rate = targetToSourceRate^-1 rateSignificantDigits = countSigificantDigits(targetToSourceRate) revisionTime = conversionTable[target][source]['revisionTime'] end return rate, rateSignificantDigits, revisionTime end local p = {} function p._rate(source, target, rounded) local dataPageNames = { 'ECB euro foreign exchange reference rates.tab', 'Xe.com exchange rates.tab'} local rate, revisionTime, rateSignificantDigits for _,name in pairs(dataPageNames) do rate, rateSignificantDigits, revisionTime = getDataFromRateDataPage(name, source, target) if not rate or not revisionTime then for _,name in pairs(dataPageNames) do local USDtoTargetRate, UtoTSigDig, UtoTRevTime = getDataFromRateDataPage(name, 'USD', target) local USDtoSourceRate, UtoSSigDig, UtoSRevTime = getDataFromRateDataPage(name, 'USD', source) if USDtoTargetRate and USDtoSourceRate then rate = USDtoTargetRate/USDtoSourceRate revisionTime = UtoTRevTime < UtoSRevTime and UtoTRevTime or UtoSRevTime rateSignificantDigits = UtoTSigDig < UtoSSigDig and UtoTSigDig or UtoSSigDig end end end if rate and revisionTime then break end end if rate and revisionTime then if rounded then rate = round(rate, rateSignificantDigits) end return rate, revisionTime end end function p._convert(source, target, amount) local rate = p._rate(source, target) if rate then local amountSigificantDigitsCount = countSigificantDigits(amount) return round(amount * rate, amountSigificantDigitsCount + 1) end end function p._convertSingelOrRange(source, target, amounts) local amounts = string.gsub(amounts, ',', '') local splitOffset = mw.ustring.find(amounts, '-') local converted if splitOffset then local firstAmount = mw.ustring.sub(amounts, 0, splitOffset -1) local secondAmount = mw.ustring.sub(amounts, splitOffset + 1) local first = p._convert(source, target, firstAmount) local second = p._convert(source, target, secondAmount) converted = first and second and first .. '–' .. second else converted = p._convert(source, target, amounts) end return converted end function p.rate(frame) local args = frame.args local rate = p._rate(args.source, args.target, true) local result = rate or args.verbose and errormsg return result end function p.revisionTime(frame) local args = frame.args local _,revisionTime = p._rate(args.source, args.target) local result = revisionTime or args.verbose and errormsg return result end function p.convert(frame) local args = frame.args local amount = string.gsub(args.amount, ',', '') local convertedAmount = p._convert(args.source, args.target, amount) local result = convertedAmount or args.verbose and errormsg return result end function p.convertSingelOrRange(frame) local args = frame.args local convertedAmounts = p._convertSingelOrRange( args.source, args.target, args.amounts) local result = convertedAmounts or args.verbose and errormsg return result end local function currencyWithSymbol(currency, symbolFormat, amount) local currencyWithSymbol = ( symbolFormat and string.format(symbolFormat, amount) or currency .. amount) return currencyWithSymbol end function p.currencyWithConversions(frame) local args = frame.args local amount = (args.amount and args.amount ~= '') and args.amount or 1 local i18n = mw.loadData('Module:Exchangerate/i18n') local currencySymbols = i18n.symbols[args.currency] local shortSymbol = currencySymbols and currencySymbols.shortSymbol local currencyWithShortSymbol = currencyWithSymbol( args.currency, shortSymbol, amount) local uniqueSymbol = currencySymbols and currencySymbols.uniqueSymbol local currencyWithUniqueSymbol = currencyWithSymbol( args.currency, uniqueSymbol, amount) local conversionCurrencies = i18n.defaultConversions or {'USD', 'EUR'} local convertedStrings = {} for _,convCurrency in ipairs(conversionCurrencies) do if args.currency ~= convCurrency then local convertedAmount = p._convertSingelOrRange( args.currency, convCurrency, amount) local convCurrencyUniqueSymbol = (i18n.symbols[convCurrency] and i18n.symbols[convCurrency].uniqueSymbol) local convCurrencyWithSymbol = convertedAmount and currencyWithSymbol( convCurrency, convCurrencyUniqueSymbol, convertedAmount) table.insert(convertedStrings, convCurrencyWithSymbol) end end local comma = mw.message.new('comma-separator'):plain() local allConvertedStrings = table.concat(convertedStrings, comma) local conversions = (allConvertedStrings ~= '') and ' ≈ ' .. allConvertedStrings or '' local resultFormat = '<abbr title="%s%s">%s</abbr>' local result = string.format(resultFormat, currencyWithUniqueSymbol, conversions, currencyWithShortSymbol) return result end return p