Module:Exchangerate

From Wikivoyage
Jump to navigation Jump to search
[view] [edit] [history] [purge] Documentation

Module status[edit]

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[edit]

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 .. '&ndash;' .. 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