diff --git a/Other/DateI18n.lua b/Other/DateI18n.lua new file mode 100644 index 0000000..270086a --- /dev/null +++ b/Other/DateI18n.lua @@ -0,0 +1,364 @@ +--[[ + __ __ _ _ ____ _ ___ _ ___ + | \/ | ___ __| |_ _| | ___ _| _ \ __ _| |_ ___|_ _/ |( _ ) _ __ + | |\/| |/ _ \ / _` | | | | |/ _ (_) | | |/ _` | __/ _ \| || |/ _ \| '_ \ + | | | | (_) | (_| | |_| | | __/_| |_| | (_| | || __/| || | (_) | | | | + |_| |_|\___/ \__,_|\__,_|_|\___(_)____/ \__,_|\__\___|___|_|\___/|_| |_| + +This module is intended for processing of date strings. + +Please do not modify this code without applying the changes first at Module:Date/sandbox and testing +at Module:Date/sandbox/testcases and Module talk:Date/sandbox/testcases. + +Authors and maintainers: +* User:Parent5446 - original version of the function mimicking template:ISOdate +* User:Jarekt - original version of the functions mimicking template:Date +]] +require('strict') + +-- ================================================== +-- === Internal functions =========================== +-- ================================================== + +-- Function allowing for consistent treatment of boolean-like wikitext input. +-- It works similarly to Module:Yesno +local function yesno(val, default) + if type(val) == 'boolean' then + return val + elseif type(val) == 'number' then + if val==1 then + return true + elseif val==0 then + return false + end + elseif type(val) == 'string' then + val = mw.ustring.lower(val) -- put in lower case + if val == 'no' or val == 'n' or val == 'false' or tonumber(val) == 0 then + return false + elseif val == 'yes' or val == 'y' or val == 'true' or tonumber(val) == 1 then + return true + end + end + return default +end + +--------------------------------------------------------------------------------------- +-- String replacement that ignores part of the string in "..." +local function strReplace(String, old, new) + if String:find('"') then + local T={} + for i, str in ipairs(mw.text.split( String, '"', true )) do + if i%2==1 then + str = str:gsub(old, new) + end + table.insert(T, str) + end + return table.concat(T,'"') + else + return String:gsub(old, new) + end +end + +--------------------------------------------------------------------------------------- +-- process datevec +-- INPUT: +-- * datevec - Array of {year,month,day,hour,minute,second, tzhour, tzmin} containing broken +-- down date-time component strings or numbers +-- OUTPUT: +-- * datecode - a code specifying content of the array where Y' is year, 'M' is month, +-- '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 +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 + local maxval = {nil, 12, 31, 23, 59, 59, 23, 59} -- max values for year, month, ... + local minval = {nil, 1, 1, 0, 0, 0, -23, 0} -- min values for year, month, ... + local c = {'Y', 'M', 'D', 'H', 'M', 'S', '', ''} + local datecode = '' -- a string signifying which combination of variables was provided + local datenum = {} -- date-time encoded as a vector = [year, month, ... , second] + for i = 1,8 do + datenum[i] = tonumber(datevec[i]) + if datenum[i] and (i==1 or (datenum[i]>=minval[i] and datenum[i]<=maxval[i])) then + datecode = datecode .. c[i] + end + end + return datecode, datenum +end + +--------------------------------------------------------------------------------------- +-- process datevec +-- INPUT: +-- * datecode - a code specifying content of the array where Y' is year, 'M' is month, +-- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y +-- * datenum - Array of {year,month,day,hour,minute,second, tzhour, tzmin} as numbers or nuls +-- OUTPUT: +-- * 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/Help:Extension:ParserFunctions#.23time +-- * datecode - with possible corrections +local function getTimestamp(datecode, datenum) + -- create time stamp string (for example 2000-02-20 02:20:20) based on which variables were provided + local timeStamp + 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] ) + elseif datecode == 'YMDHM' then + 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 + timeStamp = string.format('%04i-%02i-%02i', datenum[1], datenum[2], datenum[3] ) + datecode = 'YMD' -- 'YMD', 'YMDHMS' and 'YMDHM' are the only supported format starting with 'YMD'. All others will be converted to 'YMD' + elseif datecode == 'YM' then + timeStamp = string.format('%04i-%02i', datenum[1], datenum[2] ) + elseif datecode:sub(1,1)=='Y' then + timeStamp = string.format('%04i', datenum[1] ) + datecode = 'Y' + elseif datecode == 'M' then + timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], 1 ) + elseif datecode == 'MD' then + timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], datenum[3] ) + else + timeStamp = nil -- format not supported + end + return timeStamp, datecode +end + +--------------------------------------------------------------------------------------- +-- trim leading zeros in years prior to year 1000 +-- INPUT: +-- * datestr - translated date string +-- * lang - language of translation +-- OUTPUT: +-- * datestr - updated date string + +local function trimYear(datestr, year, lang) + local yearStr0, yearStr1, yearStr2, zeroStr + yearStr0 = string.format('%04i', year ) -- 4 digit year in standard form "0123" + yearStr1 = mw.language.new(lang):formatDate( 'Y', yearStr0) -- same as calling {{#time}} parser function + --yearStr1 = mw.getCurrentFrame():callParserFunction( "#time", { 'Y', yearStr0, lang } ) -- translate to a language + if yearStr0==yearStr1 then -- most of languages use standard form of year + yearStr2 = tostring(year) + else -- some languages use different characters for numbers + yearStr2 = yearStr1 + zeroStr = mw.ustring.sub(yearStr1,1,1) -- get "0" in whatever language + for i=1,3 do -- trim leading zeros + if mw.ustring.sub(yearStr2,1,1)==zeroStr then + yearStr2 = mw.ustring.sub(yearStr2, 2, 5-i) + else + break + end + end + end + return string.gsub(datestr, yearStr1, yearStr2 ) -- in datestr replace long year with trimmed one +end + +--------------------------------------------------------------------------------------- +-- Look up proper format string to be passed to {{#time}} parser function +-- INPUTS: +-- * datecode: YMDHMS, YMDHM, YMD, YM, MD, Y, or M +-- * day : Number between 1 and 31 (not needed for most languages) +-- * lang : language +-- OUTPUT: +-- * dFormat : input to {{#time}} function +local function getDateFormat(datecode, day, lang) + local function parseFormat(dFormat, day) + if dFormat:find('default') and #dFormat>10 then + -- special (and messy) case of dFormat code depending on a day number + -- then json contains a string with more json containing "default" field and 2 digit day keys + -- if desired day is not in that json than use "default" case + dFormat = dFormat:gsub('”','"') -- change fancy double quote to a straight one, used for json marking + local D = mw.text.jsonDecode( dFormat ) --com = mw.dumpObject(D) + day = string.format('d%02i',day) -- create day key + dFormat = D[day] or D.default + dFormat = dFormat:gsub("'", '"') -- change single quote to a double quote, used for {{#time}} marking + end + return dFormat + end + + local T = {} + local tab = mw.ext.data.get('DateI18n.tab', lang) + for _, row in pairs(tab.data) do -- convert the output into a dictionary table + local id, _, msg = unpack(row) + T[id] = msg + end + local dFormat = T[datecode] + if dFormat=='default' and (datecode=='YMDHMS' or datecode=='YMDHM') then + -- 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 + -- English which stores word "default" + dFormat = parseFormat(T['YMD'], day).. ', H:i' + if datecode=='YMDHMS' then + dFormat = dFormat .. ':s' + end + else + dFormat = parseFormat(dFormat, day) + end + return dFormat +end + +--------------------------------------------------------------------------------------- +-- Look up proper format string to be passed to {{#time}} parser function +-- INPUTS: +-- * month : month number +-- * case : gramatic case abbriviation, like "ins", "loc" +-- * lang : language +-- OUTPUT: +-- * dFormat : input to {{#time}} function +local function MonthCase(month, case, lang) + local T = {{},{},{},{},{},{},{},{},{},{},{},{}} + local tab = mw.ext.data.get('I18n/MonthCases.tab', lang) + for _, row in pairs(tab.data) do + local mth, cs, msg = unpack(row) + T[mth][cs] = msg + end + return T[month][case] +end + +-- ================================================== +-- === External functions =========================== +-- ================================================== +local p = {} + +--[[ ======================================================================================== +Date + +This function is the core part of the ISOdate template. + +Usage: + local Date = require('Module:DateI18n')._Date + local dateStr = Date({2020, 12, 30, 12, 20, 11}, lang) + +Parameters: + * {year,month,day,hour,minute,second, tzhour, tzmin}: broken down date-time component strings or numbers + tzhour, tzmin are timezone offsets from UTC, hours and minutes + * lang: The language to display it in + * case: Language format (genitive, etc.) for some languages + * class: CSS class for the