Module:Complex date/core
Lua
CodeDiscussionEditHistoryLinksLink count Subpages:DocumentationTestsResultsSandboxLive code All modules
This module contains core functions of Module:Complex date separated in order to allow proper unit testing. It relies on the following tables:
Code
--[[
__ __ _ _ ____ _ _
| \/ | ___ __| |_ _| | ___ _ / ___|___ _ __ ___ _ __ | | _____ __ __| | __ _| |_ ___
| |\/| |/ _ \ / _` | | | | |/ _ (_) | / _ \| '_ ` _ \| '_ \| |/ _ \ \/ / / _` |/ _` | __/ _ \
| | | | (_) | (_| | |_| | | __/_| |__| (_) | | | | | | |_) | | __/> < | (_| | (_| | || __/
|_| |_|\___/ \__,_|\__,_|_|\___(_)\____\___/|_| |_| |_| .__/|_|\___/_/\_\ \__,_|\__,_|\__\___|
|_|
This module is intended for creation of complex date phrases in variety of languages.
Once deployed, please do not modify this code without applying the changes first at Module:Complex date/sandbox and testing
at Module:Complex date/sandbox/testcases.
Authors and maintainers:
* User:Sn1per - first draft of the original version
* User:Jarekt - corrections and expansion of the original version
]]
-- List of external modules and functions
local p = {Error = nil}
local cdate = {}
require('strict') -- used for debugging purposes as it detects cases of unintended global variables
local ISOdate = require('Module:ISOdate')._ISOdate -- date localization
local core = require('Module:Core')
--local i18n = require('Module:i18n/complex date') -- used for translations of date related phrases
--local Calendar -- loaded lazily
-- =======================================================================
local function Ordinal(...)
-- Name of the Module:
-- * Module:Ordinal - on Commons and Wikidata
-- * Module:Ordinal-cd - on English Wikipedia
return require('Module:Ordinal')._Ordinal(...)
end
-- ===========================================================================
local function contains(element, list)
for __, item in ipairs(list) do
if item == element then
return true
end
end
return false
end
-- =======================================================================
-- langSwitch with default
local function langSwitch(list, lang)
local langList = mw.language.getFallbacksFor(lang)
table.insert(langList,1,lang)
table.insert(langList,math.max(#langList,2),'default')
for i,language in ipairs(langList) do
if list[language] then
return list[language]
end
end
end
-- ==================================================
function cdate.formatnum(numStr, lang)
-- same as [[Module:Formatnum]] except that it does not deal with floats, exponents, thousand-separators, etc.
local number = tonumber(numStr)
local digit = { -- substitution of decimal digits for languages not supported by mw.language:formatNum() in core Lua libraries for MediaWiki
["ml-old"] = { '൦', '൧', '൨', '൩', '൪', '൫', '൬', '൭', '൮', '൯' },
["mn"] = { '᠐', '᠑', '᠒', '᠓', '᠔', '᠕', '᠖', '᠗', '᠘', '᠙'},
["ta"] = { '௦', '௧', '௨', '௩', '௪', '௫', '௬', '௭', '௮', '௯'},
["te"] = { '౦', '౧', '౨', '౩', '౪', '౫', '౬', '౭', '౮', '౯'},
["th"] = { '๐', '๑', '๒', '๓', '๔', '๕', '๖', '๗', '๘', '๙'}
}
if number and digit[lang] then
for i, v in ipairs(digit[lang]) do
numStr = mw.ustring.gsub(numStr, tostring(i - 1), v)
end
return numStr
elseif number then
return mw.getLanguage(lang):formatNum(number, { noCommafy = true })
else
return numStr
end
end
-- ==================================================
--[[
This function returns a string containing the input value formatted as a Roman numeral.
It works for values between 0 and 3999.
]]
function cdate.Roman(value)
local d0, d1, d2, d3, i0, i1, i2, i3
d0 = { [0] = '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX' }
d1 = { [0] = '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC' }
d2 = { [0] = '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM' }
d3 = { [0] = '', 'M', 'MM', 'MMM'}
local result = ''
value = tonumber(value)
if ((value >= 0) and (value < 4000)) then
i3 = math.floor(value / 1e3)
i2 = math.floor(value / 1e2) % 10
i1 = math.floor(value / 1e1) % 10
i0 = math.floor(value / 1e0) % 10
result = d3[i3] .. d2[i2] .. d1[i1] .. d0[i0]
end
return result
end
-- =======================================================================
-- initialize the class by loading JSON data from [[Data:Complex date.tab]]
function cdate.init()
cdate.error = nil
cdate.translations = {}
for _, row in pairs(mw.ext.data.get('Complex date.tab', '_').data) do
local key, tbl = unpack(row)
cdate.translations[key] = tbl
end
end
-- =======================================================================
--[[
INPUTS:
date1_str - string with first date
date2_str - string with second date
operation -
lang - language of the user, like "en"
OUTPUT:
date_str - translated string
]]
function cdate.translatePhrase(date1_str, date2_str, operation, lang)
-- use tables in Module:i18n/complex date to translate a phrase
if not cdate.translations[operation] then
p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: input parameter "%s" is not recognized.</span>', operation or 'nil')
return ''
end
local dateStr = langSwitch(cdate.translations[operation], lang)
local date1_num, date2_num = tonumber(date1_str), tonumber(date1_str)
if date1_num and dateStr:match('$date1_rom') then
date1_str = cdate.Roman(date1_num)
elseif date1_num and dateStr:match('$date1_ord') then
date1_str = Ordinal(date1_num, lang)
end
if date2_num and dateStr:match('$date2_rom') then
date2_str = cdate.Roman(date0_num)
elseif date2_num and dateStr:match('$date2_ord') then
date2_str = Ordinal(date2_num, lang)
end
if type(dateStr)=='string' then
-- replace parts of the string '$date1' and '$date2' with date1 and date2 strings
dateStr = mw.ustring.gsub(dateStr, '$date1[_%w]*', date1_str)
dateStr = mw.ustring.gsub(dateStr, '$date2[_%w]*', date2_str)
end
return dateStr
end
-- =======================================================================
--[[
INPUTS:
date1 - table with
date1.adj - adjective ("early", "middle", etc.) or preposition ("before", "after", "circa", etc.)
date1.date - date string - either YYYY-MM-DD or a number
date1.precision - 8="decade", 7="century", 6="millennium" or nil for "year", "month", "day"
date1.era - "ad", "ah", "bc", "bp"
date1.case - gramatical case: like "gen", "loc", or nil for basic
data1.num - first date or second
lang - language of the user, like "en"
OUTPUT:
date_str - translated string
]]
function cdate.oneDatePhrase(date1, lang)
local case, case2, dateStr
if date1.adj then
dateStr = langSwitch(cdate.translations[date1.adj], lang)
case2 = dateStr:match('$date1_(%w+)')
end
case = case2 or date1.case
if date1.precision==11 then
case = nil -- dates with days usually use basic form
end
-- dateStr can have many forms: ISO date, year or a number for
-- decade, century or millennium
local lut = {[8]='decade', [7]='century', [6]='millennium'}
local units = lut[date1.precision]
if units then -- units is "decade", "century", "millennium''
dateStr = cdate.translatePhrase(date1.date, '', units, lang)
elseif date1.precision and date1.precision>=9 then -- unit is "year", "month", "day"
local class, trim_year, _ = '', true, nil
dateStr, _ = ISOdate(date1.date, lang, case, class, trim_year)
else -- other units
dateStr = date1.date
end
-- add adjective ("early", "mid", etc.) or preposition ("before", "after",
-- "circa", etc.) to the date
if date1.adj then
dateStr = cdate.translatePhrase(dateStr, '', date1.adj, lang)
else -- only era?
dateStr = cdate.formatnum(dateStr, lang)
end
-- add era
local eras = {'bc','bh', 'ad', 'ah'}
if date1.era and contains(date1.era, eras) then
dateStr = cdate.translatePhrase(dateStr, '', date1.era, lang)
end
return dateStr --.."/"..(case2 or 'x')
end
-- =======================================================================
function cdate.isodate2timestamp(dateStr, precision, era)
-- convert date string to timestamps used by Quick Statements
local tStamp = nil
era = era or ''
if era == 'ah' or precision<6 then
return nil
elseif era ~= '' then
local eraLUT = {ad='+', bc='-', bp='-' }
era = eraLUT[era]
else
era='+'
end
-- convert isodate to timestamp used by quick statements
if precision>=9 then
if string.match(dateStr,"^%d%d%d%d$") then -- if YYYY format
tStamp = era .. dateStr .. '-00-00T00:00:00Z/9'
elseif string.match(dateStr,"^%d%d%d%d%-%d%d$") then -- if YYYY-MM format
tStamp = era .. dateStr .. '-00T00:00:00Z/10'
elseif string.match(dateStr,"^%d%d%d%d%-%d%d%-%d%d$") then -- if YYYY-MM-DD format
tStamp = era .. dateStr .. 'T00:00:00Z/11'
end
elseif precision==8 then -- decade
tStamp = era .. dateStr .. '-00-00T00:00:00Z/8'
elseif precision==7 then -- century
local d = tostring(tonumber(dateStr)-1)
tStamp = era .. d .. '50-00-00T00:00:00Z/7'
elseif precision==6 then
local d = tostring(tonumber(dateStr)-1)
tStamp = era .. d .. '500-00-00T00:00:00Z/6'
end
return tStamp
end
-- =======================================================================
--[[ create QuickStatements string for "one date" dates
INPUTS:
date1 - table with
date1.adj - adjective ("early", "middle", etc.) or preposition ("before", "after", "circa", etc.)
date1.date - date string - either YYYY-MM-DD or a number
date1.precision - 8="decade", 7="century", 6="millennium" or nil for "year", "month", "day"
date1.era - "ad", "ah", "bc", "bp"
OUTPUT:
outputStr - QuickStatements string
]]
function cdate.oneDateQScode(date1)
local outputStr = ''
local d = cdate.isodate2timestamp(date1.date, date1.precision, date1.era)
if not d then
return ''
end
local rLUT = {
early='Q40719727', mid='Q40719748', late='Q40719766', first_half='Q40719687', second_half='Q40719707',
first_quarter='Q40690303', second_quarter='Q40719649', third_quarter='Q40719662', forth_quarter='Q40719674',
spring='Q40720559', summer='Q40720564', autumn='Q40720568', winter='Q40720553',
}
local qLUT = {['from']='P580', ['until']='P582', ['after']='P1319', ['before']='P1326', ['by']='P1326'}
local refine = rLUT[date1.adj]
local qualitier = qLUT[date1.adj]
if date1.adj=='' then
outputStr = d
elseif date1.adj=='circa' then
outputStr = d..",P1480,Q5727902"
elseif refine then
outputStr = d..",P4241,"..refine
elseif date1.precision>7 and qualitier then
local century = string.gsub(d, 'Z%/%d+', 'Z/7')
outputStr = century ..",".. qualitier ..","..d
end
return outputStr
end
-- =======================================================================
function cdate.twoDateQScode(date1, date2, conj)
-- create QuickStatements string for "two date" dates
if date1.adj or date2.adj or date1.era~=date2.era then
return '' -- QuickStatements string are not generated for two date phrases with adjectives
end
local outputStr = ''
local d1 = cdate.isodate2timestamp(date1.date, date1.precision, date1.era)
local d2 = cdate.isodate2timestamp(date2.date, date2.precision, date2.era)
if (not d1) or (not d2) then
return ''
end
-- find date with lower precision in common to both dates
local cd
local year1 = tonumber(string.sub(d1,2,5))
local year2 = tonumber(string.sub(d2,2,5))
local k = 0
for i = 1,10,1 do
if string.sub(d1,1,i)==string.sub(d2,1,i) then
k = i -- find last matching letter
end
end
if k>=9 then -- same month, since "+YYYY-MM-" is in common
cd = cdate.isodate2timestamp(string.sub(d1,2,8), 10, date1.era)
elseif k>=6 and k<9 then -- same year, since "+YYYY-" is in common
cd = cdate.isodate2timestamp(tostring(year1), 9, date1.era)
elseif k==4 then -- same decade(k=4, precision=8), since "+YYY" is in common
cd = cdate.isodate2timestamp(tostring(year1), 8, date1.era)
elseif k==3 then -- same century(k=3, precision=7) since "+YY" is in common
local d = tostring(math.floor(year1/100) +1) -- convert 1999 -> 20
cd = cdate.isodate2timestamp( d, 7, date1.era)
elseif k==2 then -- same millennium (k=2, precision=6), since "+Y" is in common
local d = tostring(math.floor(year1/1000) +1) -- convert 1999 -> 2
cd = cdate.isodate2timestamp( d, 6, date1.era)
end
if not cd then
return ''
end
--if not cd then
-- return ' <br/>error: ' .. d1.." / " .. d2.." / ".. (cd or '') .." / ".. string.sub(d1,2,5).." / " .. string.sub(d2,2,5).." / " .. tostring(k)
--end
--
if (conj=='from-until') or (conj=='and' and year1==year2-1) then
outputStr = cd ..",P580,".. d1 ..",P582,".. d2
elseif (conj=='between') or (conj=='or' and year1==year2-1) then
outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2
elseif conj=='circa2' then
outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 ..",P1480,Q5727902"
end
return outputStr
end
return cdate