「モジュール:DateI18n」の版間の差分
ナビゲーションに移動
検索に移動
細
Protected "Module:DateI18n": High traffic page ([Edit=Allow only template editors and administrators] (indefinite) [Move=Allow only template editors and administrators] (indefinite))
細 (1版 をインポートしました) |
bsd>Jarekt 細 (Protected "Module:DateI18n": High traffic page ([Edit=Allow only template editors and administrators] (indefinite) [Move=Allow only template editors and administrators] (indefinite))) |
||
13行目: | 13行目: | ||
Authors and maintainers: | Authors and maintainers: | ||
* User:Parent5446 - original version of the function mimicking template:ISOdate | * User:Parent5446 - original version of the function mimicking template:ISOdate | ||
* User:Jarekt - original version of the functions mimicking template:Date | * User:Jarekt - original version of the functions mimicking template:Date | ||
]] | ]] | ||
require('Module:No globals') | |||
-- ======================================= | -- ================================================== | ||
-- === | -- === Internal functions =========================== | ||
-- ======================================= | -- ================================================== | ||
local function | -- 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 | |||
if | return val | ||
return | elseif type(val) == 'number' then | ||
if val==1 then | |||
return true | |||
elseif val==0 then | |||
return false | |||
end | end | ||
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 | 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 | ||
end | end | ||
--------------------------------------------------------------------------------------- | |||
-- process datevec | |||
-- | -- INPUT: | ||
-- * datevec - Array of {year,month,day,hour,minute,second, tzhour, tzmin} containing broken | |||
datevec[ | -- 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 | 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, ... | |||
-- create datecode based on which variables are provided and check for out of bound values | |||
local maxval = { | |||
local c = {'Y', 'M', 'D', 'H', 'M', 'S', '', ''} | 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] | local datenum = {} -- date-time encoded as a vector = [year, month, ... , second] | ||
for i, | for i = 1,8 do | ||
if | 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] | datecode = datecode .. c[i] | ||
end | end | ||
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 | -- create time stamp string (for example 2000-02-20 02:20:20) based on which variables were provided | ||
local timeStamp | local timeStamp | ||
136行目: | 119行目: | ||
elseif datecode == 'M' then | elseif datecode == 'M' then | ||
timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], 1 ) | timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], 1 ) | ||
elseif datecode == 'MD' then | elseif datecode == 'MD' then | ||
timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], datenum[3] ) | timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], datenum[3] ) | ||
else | else | ||
timeStamp = nil -- format not supported | |||
end | 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) | |||
local | for _, row in pairs(tab.data) do -- convert the output into a dictionary table | ||
local id, _, msg = unpack(row) | |||
T[id] = msg | |||
end | end | ||
local dFormat = T[datecode] | |||
if | 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 | 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 | 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 <time> node, use "" for no metadata at all | |||
]] | |||
function p._Date(datevec, lang, case, class, trim_year) | |||
-- make sure inputs are in the right format | |||
if not lang or not mw.language.isValidCode( lang ) then | |||
lang = mw.getCurrentFrame():callParserFunction( "int", "lang" ) -- get user's chosen language | |||
end | |||
if lang == 'be-tarsk' then | |||
lang = 'be-x-old' | |||
end | |||
-- | -- process datevec and extract timeStamp and datecode strings as well as numeric datenum array | ||
local datecode, datenum = parserDatevec(datevec) | |||
local year, month, day = datenum[1], datenum[2], datenum[3] | |||
local timeStamp, datecode = getTimestamp(datecode, datenum) | |||
if not timeStamp then -- something went wrong in parserDatevec | |||
return '' | |||
end | end | ||
-- Commons [[Data:DateI18n.tab]] page stores prefered formats for diferent | |||
-- languages and datecodes (specifying year-month-day or just year of month-day, etc) | |||
-- Look up country specific format input to {{#time}} function | |||
local dFormat = getDateFormat(datecode, day, lang) | |||
-- By default the gramatical case is not specified (case=='') allowing the format to be specified | |||
-- in [[Data:DateI18n.tab]]. You can overwrite the default grammatical case of the month by | |||
-- | -- specifying "case" variable. This is needed mostly by Slavic languages to create more complex | ||
-- | -- phrases as it is done in [[c:Module:Complex date]] | ||
local monthMsg = | case = case or '' | ||
if | if (lang=='qu' or lang=='qug') and (case=='nom') then | ||
-- 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}} | |||
dFormat = dFormat | -- in Nominative case that "pi" should be removed | ||
-- see https://commons.wikimedia.org/wiki/Template_talk:Date#Quechua from 2014 | |||
dFormat = dFormat:gsub('F"pi"', 'F') | |||
elseif (case=='gen') then | |||
dFormat = strReplace(dFormat, "F", "xg") | |||
elseif (case=='nom') then | |||
dFormat = strReplace(dFormat, "xg", "F") | |||
elseif (case ~= '') then | |||
-- 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 | |||
-- "F" and xg" in dFormat | |||
local monthMsg = MonthCase(month, case, lang) | |||
if monthMsg and monthMsg ~= '' then -- make sure it exists | |||
dFormat = strReplace(dFormat, 'F', '"'..monthMsg..'"') -- replace default month with month name we already looked up | |||
dFormat = strReplace(dFormat, 'xg', '"'..monthMsg..'"') | |||
end | end | ||
end | end | ||
-- Translate the date using specified format | |||
-- 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 | |||
local datestr = mw.language.new(lang):formatDate( dFormat, timeStamp) -- same as using {{#time}} parser function | |||
-- | -- Special case related to Thai solar calendar: prior to 1940 new-year was at different | ||
if lang=='th' and | -- 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 | |||
datestr = string.format('%04i หรือ %04i', year+542, year+543 ) | |||
end | end | ||
-- If year<1000 than either keep | -- 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 | if year and year<1000 then | ||
local trim = yesno(trim_year,nil) | if type(trim_year)=='nil' then | ||
if trim == nil then | trim_year = '100-999' | ||
end | |||
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 = (YMin~=nil and | trim = (YMin~=nil and year>=tonumber(YMin) and year<=tonumber(YMax)) | ||
end | end | ||
if trim==true then | |||
datestr = trimYear(datestr, year, lang) -- in datestr replace long year with trimmed one | |||
if trim then | |||
end | end | ||
end | end | ||
-- append timezone if present | -- append timezone if present | ||
if | if datenum[7] and (datecode == 'YMDHMS' or datecode == 'YMDHM') then | ||
local | -- use {{#time}} parser function to create timezone string, so that we use correct character set | ||
local sign = (datenum[7]<0) and '−' or '+' | |||
timeStamp = string.format("2000-01-01 %02i:%02i:00", math.abs(datenum[7]), datenum[8] or 0) | |||
local timezone = mw.language.new(lang):formatDate( 'H:i', timeStamp) -- same as using {{#time}} parser function | |||
datestr = datestr | datestr = string.format("%s %s%s", datestr, sign, timezone ) | ||
end | end | ||
-- html formating and tagging of date string | -- html formating and tagging of date string | ||
if class ~= '' then | if class and class ~= '' and datecode~='M' and datecode~='MD'then | ||
local DateHtmlTags = '<span style="white-space:nowrap"><time class="%s" datetime="%s">%s</time></span>' | local DateHtmlTags = '<span style="white-space:nowrap"><time class="%s" datetime="%s">%s</time></span>' | ||
datestr = DateHtmlTags:format(class, timeStamp, datestr) | datestr = DateHtmlTags:format(class, timeStamp, datestr) | ||
end | end | ||
return datestr | return datestr | ||
end | |||
--[[ ======================================================================================== | |||
Date | |||
This function is the core part of the ISOdate template. | |||
Usage: | |||
{{#invoke:DateI18n|Date|year=|month=|day=|hour=|minute=|second=|tzhour=|tzmin=|lang=en}} | |||
Parameters: | |||
* year, month, day, hour, minute, second: broken down date-time component strings | |||
* tzhour, tzmin: timezone offset 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 <time> node, use "" for no metadata at all | |||
]] | |||
function p.Date(frame) | |||
local args = {} | |||
for name, value in pairs( frame.args ) do | |||
name = string.gsub( string.lower(name), ' ', '_') | |||
args[name] = value | |||
end | |||
return p._Date( | |||
{ args.year, args.month, args.day, args.hour, args.minute, args.second, args.tzhour, args.tzmin }, | |||
args.lang, -- language | |||
args.case, -- allows to specify grammatical case for the month for languages that use them | |||
args.class or 'dtstart', -- allows to set the html class of the time node where the date is included. This is useful for microformats. | |||
args.trim_year or '100-999' -- by default pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is | |||
) | |||
end | end | ||
return p | return p |