|
|
Line 27: |
Line 27: |
| return val | | return val |
| elseif type(val) == 'number' then | | elseif type(val) == 'number' then |
| if val == 1 then | | if val==1 then |
| return true | | return true |
| elseif val == 0 then | | elseif val==0 then |
| return false | | return false |
| end | | end |
| elseif type(val) == 'string' then | | elseif type(val) == 'string' then |
| val = string.lower(val) -- put in lower case | | val = mw.ustring.lower(val) -- put in lower case |
| if val == 'no' or val == 'n' or val == 'false' or tonumber(val) == 0 then | | if val == 'no' or val == 'n' or val == 'false' or tonumber(val) == 0 then |
| return false | | return false |
Line 66: |
Line 66: |
| -- down date-time component strings or numbers | | -- down date-time component strings or numbers |
| -- OUTPUT: | | -- OUTPUT: |
| -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, 'D' is day, | | -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, |
| -- 'h' is hour, 'm' minute, 's' is second. output has to be one of YMDhms, YMDhm, YMD, YM, Y, MDhms, MDhm, MD, M
| | -- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y |
| -- * datenum - same array but holding only numbers or nuls | | -- * datenum - same array but holding only numbers or nuls |
| local function parserDatevec(datevec) | | local function parserDatevec(datevec) |
| | -- if month is not a number than check if it is a month name in project's language |
| | local month = datevec[2] |
| | if month and month~='' and not tonumber(month) then |
| | datevec[2] = mw.getContentLanguage():formatDate( "n", month) |
| | end |
| | |
| -- create datecode based on which variables are provided and check for out-of-bound values | | -- create datecode based on which variables are provided and check for out-of-bound values |
| local maxval = { 1/0, 12, 31, 23, 59, 60, 23, 59 } -- max values (or 1/0=+inf) for year, month, day, hour, minute, second, tzhour, tzmin | | local maxval = {nil, 12, 31, 23, 59, 59, 23, 59} -- max values for year, month, ... |
| local minval = { -1/0, 01, 01, 00, 00, 00, -23, 00 } -- min values (or -1/0=-inf) for year, month, ... | | local minval = {nil, 1, 1, 0, 0, 0, -23, 0} -- min values for year, month, ... |
| local codes = { 'Y', 'M', 'D', 'h', 'm', 's', '', '' } -- WARNING: 'M' alone would be ambiguous if it does not follow 'Y' or 'h' | | local c = {'Y', 'M', 'D', 'H', 'M', 'S', '', ''} |
| local datecode = '' -- a string signifying which combination of variables was provided | | local datecode = '' -- a string signifying which combination of variables was provided |
| local datenum = {} -- date-time encoded as a vector = [year, month, ... , second, tzhour, tzmin] | | local datenum = {} -- date-time encoded as a vector = [year, month, ... , second] |
| for i = 1, 8 do | | for i = 1,8 do |
| local c, val = codes[i], datevec[i]
| | datenum[i] = tonumber(datevec[i]) |
| if c == 'M' and type(val) == 'string' and val ~= '' and not tonumber(val) then
| | if datenum[i] and (i==1 or (datenum[i]>=minval[i] and datenum[i]<=maxval[i])) then |
| -- When the month is not a number, check if it's a month name in the project's language.
| | datecode = datecode .. c[i] |
| val = mw.getContentLanguage():formatDate('n', val)
| |
| end
| |
| val = tonumber(val)
| |
| if val and val >= minval[i] and val <= maxval[i] then -- These tests work with infinite min/max values.
| |
| if c == 'm' then -- Field for minute accepted only if it follows another valid 'M', 'D' or 'h' field.
| |
| if not string.find('MDh', datecode:sub(-1)) then
| |
| c = ''; val = nil -- field for minute is invalid
| |
| end
| |
| elseif c == 's' then -- Field for leap second '60' is valid only at end of 23:59 UTC, on 30 June or 31 December of specific years.
| |
| if val == 60 and not( -- Leap second are are added (or dropped) on specific dates planned only some months before.
| |
| datenum[1] and -- A year is specified (to check it would require constantly maintaining a table of dates).
| |
| (datenum[2] == 6 and datenum[3] == 30 or datenum[2] == 12 and datenum[3] == 31) and
| |
| datenum[4] == 23 and datenum[5] == 59
| |
| ) then
| |
| c = ''; val = nil -- Field for second is invalid in this case, don't add the field.
| |
| end
| |
| end
| |
| datecode = datecode .. c | |
| datenum[i] = val
| |
| end | | end |
| end | | end |
Line 107: |
Line 94: |
| -- process datevec | | -- process datevec |
| -- INPUT: | | -- INPUT: |
| -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, | | -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, |
| -- 'D' is day, 'H' is hour, 'i' minute, 's' is second. | | -- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y |
| -- Output has to be one of YMDhms, YMDhm, YMD, YM, Y, MDhms, MDhm, MD, M.
| |
| -- * datenum - Array of {year,month,day,hour,minute,second, tzhour, tzmin} as numbers or nuls | | -- * datenum - Array of {year,month,day,hour,minute,second, tzhour, tzmin} as numbers or nuls |
| -- OUTPUT: | | -- OUTPUT: |
| -- * timeStamp - date string in the format taken by mw.language:formatDate lua function and {{#time}} parser function | | -- * timeStamp - date string in the format taken by mw.language:formatDate lua function and {{#time}} perser function |
| -- https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate | | -- https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate |
| -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions#.23time | | -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions#.23time |
| -- * datecode - with possible corrections | | -- * datecode - with possible corrections |
| local function getTimestamp(datecode, datenum) | | local function getTimestamp(datecode, datenum) |
| -- create timestamp string (for example 2000-02-20 02:20:20) based on which variables were provided | | -- create time stamp string (for example 2000-02-20 02:20:20) based on which variables were provided |
| local timeStamp | | local timeStamp |
| -- date starting by a year
| | if datecode == 'YMDHMS' then |
| if datecode == 'YMDhms' then | |
| timeStamp = string.format('%04i-%02i-%02i %02i:%02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5], datenum[6] ) | | timeStamp = string.format('%04i-%02i-%02i %02i:%02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5], datenum[6] ) |
| elseif datecode == 'YMDhm' then | | elseif datecode == 'YMDHM' then |
| timeStamp = string.format('%04i-%02i-%02i %02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5] ) | | timeStamp = string.format('%04i-%02i-%02i %02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5] ) |
| elseif datecode:sub(1,3)=='YMD' then | | elseif datecode:sub(1,3)=='YMD' then |
| timeStamp = string.format('%04i-%02i-%02i', datenum[1], datenum[2], datenum[3] ) | | timeStamp = string.format('%04i-%02i-%02i', datenum[1], datenum[2], datenum[3] ) |
| datecode = 'YMD' -- 'YMDhms', 'YMDhm' and 'YMD' are the only supported format starting with 'YMD'; all others will be converted to 'YMD'. | | datecode = 'YMD' -- 'YMD', 'YMDHMS' and 'YMDHM' are the only supported format starting with 'YMD'. All others will be converted to 'YMD' |
| elseif datecode:sub(1,2) == 'YM' then | | elseif datecode == 'YM' then |
| timeStamp = string.format('%04i-%02i', datenum[1], datenum[2] ) | | timeStamp = string.format('%04i-%02i', datenum[1], datenum[2] ) |
| elseif datecode:sub(1,1)=='Y' then | | elseif datecode:sub(1,1)=='Y' then |
| timeStamp = string.format('%04i', datenum[1] ) | | timeStamp = string.format('%04i', datenum[1] ) |
| datecode = 'Y' | | datecode = 'Y' |
| -- date starting by a month (the implied year is 2000)
| | elseif datecode == 'M' then |
| elseif datecode== 'MDhms' then | | timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], 1 ) |
| timeStamp = string.format('%04i-%02i-%02i %02i:%02i:%02i', 2000, datenum[2], datenum[3], datenum[4], datenum[5], datenum[6] ) | | elseif datecode == 'MD' then |
| elseif datecode == 'MDhm' then
| |
| timeStamp = string.format('%04i-%02i-%02i %02i:%02i', 2000, datenum[2], datenum[3], datenum[4], datenum[5] )
| |
| elseif datecode:sub(1,2) == 'MD' then | |
| timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], datenum[3] ) | | timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], datenum[3] ) |
| datecode = 'MD' -- 'MDhms', 'MDhm' and 'MD' are the only supported format starting with 'MD'; all others will be converted to 'MD'
| |
| elseif datecode:sub(1,1) == 'M' then -- Ambiguous: could mean minutes, but here means month (when parsed as a name/abbrev, not as a number).
| |
| timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], 1 )
| |
| -- other possible but unrecognized formats (e.g. 'DHis', 'DHi', 'D', 'His', 'Hi');
| |
| -- note that 'Dh', 'D', 'h', 's' may eventually work, but not 'm' for minute only, which is ambiguous with 'M' for month only.
| |
| else | | else |
| timeStamp = nil -- format not supported | | timeStamp = nil -- format not supported |
Line 182: |
Line 159: |
| -- Look up proper format string to be passed to {{#time}} parser function | | -- Look up proper format string to be passed to {{#time}} parser function |
| -- INPUTS: | | -- INPUTS: |
| -- * datecode: YMDhms, YMDhm, YMD, YM, Y, MDhms, MDhm, MD, or M | | -- * datecode: YMDHMS, YMDHM, YMD, YM, MD, Y, or M |
| -- * day : Number between 1 and 31 (not needed for most languages) | | -- * day : Number between 1 and 31 (not needed for most languages) |
| -- * lang : language | | -- * lang : language |
Line 190: |
Line 167: |
| local function parseFormat(dFormat, day) | | local function parseFormat(dFormat, day) |
| if dFormat:find('default') and #dFormat>10 then | | if dFormat:find('default') and #dFormat>10 then |
| -- Special (and messy) case of dFormat code depending on a day number, where data is a | | -- special (and messy) case of dFormat code depending on a day number |
| -- JSON-encoded table {”default”:”*”,”dDD”:”*”} including fields for specific 2-digit days. | | -- then json contains a string with more json containing "default" field and 2 digit day keys |
| -- Change curly double quotes (possibly used for easier editing in tabular data) in dFormat | | -- if desired day is not in that json than use "default" case |
| -- to straight ASCII double quotes (required for parsing of this JSON-encoded table). | | dFormat = dFormat:gsub('”','"') -- change fancy double quote to a straight one, used for json marking |
| local D = mw.text.jsonDecode(mw.ustring.gsub(dFormat, '[„“‟”]', '"')) --com = mw.dumpObject(D) | | local D = mw.text.jsonDecode( dFormat ) --com = mw.dumpObject(D) |
| -- If the desired day is not in that JSON table, then use its "default" case. | | day = string.format('d%02i',day) -- create day key |
| dFormat = D[string.format('d%02i', day)] or D.default
| | dFormat = D[day] or D.default |
| -- Change ASCII single quotes to ASCII double quotes used for {{#time}} marking.
| | dFormat = dFormat:gsub("'", '"') -- change single quote to a double quote, used for {{#time}} marking |
| -- Apostrophes needed in plain-text must not use ASCII single quotes but curly apostrophe
| |
| -- e.g. { ‟default”: ‟j”, ‟d01”: ‟j’'o'” }, not { ‟default”: ‟j”, ‟d01”: ‟j''o'” }.
| |
| end | | end |
| dFormat = dFormat:gsub("'", '"')
| |
| return dFormat | | return dFormat |
| end | | end |
Line 211: |
Line 185: |
| T[id] = msg | | T[id] = msg |
| end | | end |
| -- Compatibility of legacy data using 'HMS' or 'HM', where 'M' is ambiguous
| |
| T.YMDhms = T.YMDhms or T.YMDHMS
| |
| T.YMDhm = T.YMDhm or T.YMDHM
| |
| datecode = datecode == 'YMDHMS' and 'YMDhms' or datecode == 'YMDHM' and 'YMDhm' or datecode
| |
|
| |
| local dFormat = T[datecode] | | local dFormat = T[datecode] |
| if dFormat == 'default' and (datecode == 'YMDhms' or datecode == 'YMDhm') then | | if dFormat=='default' and (datecode=='YMDHMS' or datecode=='YMDHM') then |
| -- For most languages adding hour:minute:second is done by adding ", HH:ii:ss to the | | -- for most languages adding hour:minute:second is done by adding ", HH:MM:SS to the |
| -- day precission date, those languages are skipped in DateI18n.tab and default to | | -- day precission date, those languages are skipped in DateI18n.tab and default to |
| -- English which stores word "default" | | -- English which stores word "default" |
| dFormat = parseFormat(T['YMD'], day).. ', H:i' | | dFormat = parseFormat(T['YMD'], day).. ', H:i' |
| if datecode == 'YMDhms' then | | if datecode=='YMDHMS' then |
| dFormat = dFormat .. ':s' | | dFormat = dFormat .. ':s' |
| end | | end |
Line 275: |
Line 244: |
| lang = mw.getCurrentFrame():callParserFunction( "int", "lang" ) -- get user's chosen language | | lang = mw.getCurrentFrame():callParserFunction( "int", "lang" ) -- get user's chosen language |
| end | | end |
| if lang == 'be-tarask' then | | if lang == 'be-tarsk' then |
| lang = 'be-x-old' | | lang = 'be-x-old' |
| end | | end |
Line 296: |
Line 265: |
| -- phrases as it is done in [[c:Module:Complex date]] | | -- phrases as it is done in [[c:Module:Complex date]] |
| case = case or '' | | case = case or '' |
| if (lang=='qu' or lang=='qug') and case=='nom' then | | if (lang=='qu' or lang=='qug') and (case=='nom') then |
| -- Special case related to Quechua and Kichwa languages. The form in the I18n is | | -- Special case related to Quechua and Kichwa languages. The form in the I18n is |
| -- Genitive case with suffix "pi" added to month names provided by {#time}} | | -- Genitive case with suffix "pi" added to month names provided by {#time}} |
Line 302: |
Line 271: |
| -- see https://commons.wikimedia.org/wiki/Template_talk:Date#Quechua from 2014 | | -- see https://commons.wikimedia.org/wiki/Template_talk:Date#Quechua from 2014 |
| dFormat = dFormat:gsub('F"pi"', 'F') | | dFormat = dFormat:gsub('F"pi"', 'F') |
| elseif case == 'gen' then | | elseif (case=='gen') then |
| dFormat = strReplace(dFormat, "F", "xg") | | dFormat = strReplace(dFormat, "F", "xg") |
| elseif case == 'nom' then | | elseif (case=='nom') then |
| dFormat = strReplace(dFormat, "xg", "F") | | dFormat = strReplace(dFormat, "xg", "F") |
| elseif case ~= '' then | | elseif (case ~= '') then |
| -- see is page [[Data:I18n/MonthCases.tab]] on Commons have name of the month | | -- see is page [[Data:I18n/MonthCases.tab]] on Commons have name of the month |
| -- in specific gramatic case in desired language. If we have it than replace | | -- in specific gramatic case in desired language. If we have it than replace |
Line 317: |
Line 286: |
| end | | end |
|
| |
|
| -- Translate the date using specified format. | | -- Translate the date using specified format |
| -- See https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate and | | -- See https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate and |
| -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##time for explanation of the format | | -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##time for explanation of the format |
| local langObj = mw.language.new(lang) | | local datestr = mw.language.new(lang):formatDate( dFormat, timeStamp) -- same as using {{#time}} parser function |
| local datestr = langObj:formatDate(dFormat, timeStamp) -- same as using {{#time}} parser function
| |
| | | |
| -- Special case related to Thai solar calendar: prior to 1940 new-year was at different time of year, | | -- Special case related to Thai solar calendar: prior to 1940 new-year was at different |
| -- so just year (datecode == 'Y') is ambiguous and is replaced by "YYYY or YYYY" phrase
| | -- time of year, so just year (datecode=='Y') is ambiguous and is replaced by "YYYY or YYYY" phrase |
| if lang=='th' and datecode=='Y' and year<=1940 then | | if lang=='th' and datecode=='Y' and year<=1940 then |
| datestr = string.format('%04i หรือ %04i', year+542, year+543 ) | | datestr = string.format('%04i หรือ %04i', year+542, year+543 ) |
| end | | end |
| | | |
| -- If year < 1000 than either keep the date padded to the length of 4 digits or trim it. | | -- If year<1000 than either keep the date padded to the length of 4 digits or trim it |
| -- Decide if the year will stay padded with zeros (for years in 0-999 range). | | -- decide if the year will stay padded with zeros (for years in 0-999 range) |
| if year and year < 1000 then | | if year and year<1000 then |
| trim_year = yesno(trim_year, '100-999') | | if type(trim_year)=='nil' then |
| if type(trim_year) == 'string' then | | trim_year = '100-999' |
| -- If `trim_year` not a simple boolean, then it's a range of dates. | | end |
| -- For example '100-999' means to pad 1-or-2-digit years to be 4-digit long, while keeping 3-digit years as is. | | local trim = yesno(trim_year,nil) -- convert to boolean |
| | if trim==nil and type(trim_year)=='string' then |
| | -- if "trim_year" not a simple True/False than it is range of dates |
| | -- for example '100-999' means to pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is |
| local YMin, YMax = trim_year:match( '(%d+)-(%d+)' ) | | local YMin, YMax = trim_year:match( '(%d+)-(%d+)' ) |
| trim_year = YMin and year >= tonumber(YMin) and year <= tonumber(YMax) | | trim = (YMin~=nil and year>=tonumber(YMin) and year<=tonumber(YMax)) |
| end | | end |
| if trim_year then | | if trim==true then |
| datestr = trimYear(datestr, year, lang) -- in datestr replace long year with trimmed one | | datestr = trimYear(datestr, year, lang) -- in datestr replace long year with trimmed one |
| end | | end |
| end | | end |
|
| |
|
| -- Append a timezone if present (after the hour and minute of the day). | | -- append timezone if present |
| if datenum[7] and (datecode.sub(1, 5) == 'YMDhm' or datecode.sub(1, 4) == 'MDhm') then | | if datenum[7] and (datecode == 'YMDHMS' or datecode == 'YMDHM') then |
| -- Use {{#time}} parser function to create timezone string, so that we use the correct character set. | | -- use {{#time}} parser function to create timezone string, so that we use correct character set |
| local sign = (datenum[7]<0) and '−' or '+' | | local sign = (datenum[7]<0) and '−' or '+' |
| timeStamp = string.format("2000-01-01 %02i:%02i:00", math.abs(datenum[7]), datenum[8] or 0) | | timeStamp = string.format("2000-01-01 %02i:%02i:00", math.abs(datenum[7]), datenum[8] or 0) |
| local timezone = langObj:formatDate('H:i', timeStamp) -- same as using {{#time}} parser function | | local timezone = mw.language.new(lang):formatDate( 'H:i', timeStamp) -- same as using {{#time}} parser function |
| datestr = string.format("%s %s%s", datestr, sign, timezone ) | | datestr = string.format("%s %s%s", datestr, sign, timezone ) |
| end | | end |
|
| |
|
| -- HTML formating of date string and tagging for microformats (only for absolute dates with a year). | | -- html formating and tagging of date string |
| if class and class ~= '' and datecode.sub(1,1) == 'Y' then -- | | if class and class ~= '' and datecode~='M' and datecode~='MD'then |
| datestr = | | local DateHtmlTags = '<span style="white-space:nowrap"><time class="%s" datetime="%s">%s</time></span>' |
| ('<time class="%s" datetime="%s" lang="%s" dir="%s" style="white-space:nowrap">%s</time>')
| | datestr = DateHtmlTags:format(class, timeStamp, datestr) |
| :format(class, timeStamp, lang, langObj:getDir(), datestr)
| |
| end | | end |
| return datestr | | return datestr |