https://wiki.swarma.org/index.php?title=%E6%A8%A1%E5%9D%97:Age&feed=atom&action=history模块:Age - 版本历史2024-03-28T10:30:21Z本wiki的该页面的版本历史MediaWiki 1.35.0https://wiki.swarma.org/index.php?title=%E6%A8%A1%E5%9D%97:Age&diff=18578&oldid=prevSwarma:建立内容为“-- Implement various "age of" and other date-related templates. local mtext = { -- Message and other text that should be localized. ['mt-bad-param1'] =…”的新页面2020-11-17T09:40:09Z<p>建立内容为“-- Implement various "age of" and other date-related templates. local mtext = { -- Message and other text that should be localized. ['mt-bad-param1'] =…”的新页面</p>
<p><b>新页面</b></p><div>-- Implement various "age of" and other date-related templates.<br />
<br />
local mtext = {<br />
-- Message and other text that should be localized.<br />
['mt-bad-param1'] = 'Invalid parameter $1',<br />
['mt-bad-param2'] = 'Parameter $1=$2 is invalid',<br />
['mt-bad-show'] = 'Parameter show=$1 is not supported here',<br />
['mt-cannot-add'] = 'Cannot add "$1"',<br />
['mt-conflicting-show'] = 'Parameter show=$1 conflicts with round=$2',<br />
['mt-date-wrong-order'] = 'The second date must be later in time than the first date',<br />
['mt-dd-future'] = 'Death date (first date) must not be in the future',<br />
['mt-dd-wrong-order'] = 'Death date (first date) must be later in time than the birth date (second date)',<br />
['mt-invalid-bd-age'] = 'Invalid birth date for calculating age',<br />
['mt-invalid-dates-age'] = 'Invalid dates for calculating age',<br />
['mt-invalid-end'] = 'Invalid end date in second parameter',<br />
['mt-invalid-start'] = 'Invalid start date in first parameter',<br />
['mt-need-jdn'] = 'Need valid Julian date number',<br />
['mt-need-valid-bd'] = 'Need valid birth date: year, month, day',<br />
['mt-need-valid-bd2'] = 'Need valid birth date (second date): year, month, day',<br />
['mt-need-valid-date'] = 'Need valid date',<br />
['mt-need-valid-dd'] = 'Need valid death date (first date): year, month, day',<br />
['mt-need-valid-ymd'] = 'Need valid year, month, day',<br />
['mt-need-valid-ymd-current'] = 'Need valid year|month|day or "currentdate"',<br />
['mt-need-valid-ymd2'] = 'Second date should be year, month, day',<br />
['mt-template-bad-name'] = 'The specified template name is not valid',<br />
['mt-template-x'] = 'The template invoking this must have "|template=x" where x is the wanted operation',<br />
['txt-age'] = '(age&nbsp;',<br />
['txt-aged'] = ' (aged&nbsp;',<br />
['txt-and'] = ' and ',<br />
['txt-comma-and'] = ', and ',<br />
['txt-error'] = 'Error: ',<br />
['txt-or'] = '&nbsp;or ',<br />
}<br />
<br />
local translate, from_en, to_en, isZero<br />
if translate then<br />
-- Functions to translate from en to local language and reverse go here.<br />
-- See example at [[:bn:Module:বয়স]].<br />
else<br />
from_en = function (text)<br />
return text<br />
end<br />
isZero = function (text)<br />
return tonumber(text) == 0<br />
end<br />
end<br />
<br />
local _Date, _currentDate<br />
local function getExports(frame)<br />
-- Return objects exported from the date module or its sandbox.<br />
if not _Date then<br />
local sandbox = frame:getTitle():find('sandbox', 1, true) and '/sandbox' or ''<br />
local datemod = require('Module:Date' .. sandbox)<br />
local realDate = datemod._Date<br />
_currentDate = datemod._current<br />
if to_en then<br />
_Date = function (...)<br />
local args = {}<br />
for i, v in ipairs({...}) do<br />
args[i] = to_en(v)<br />
end<br />
return realDate(unpack(args))<br />
end<br />
else<br />
_Date = realDate<br />
end<br />
end<br />
return _Date, _currentDate<br />
end<br />
<br />
local Collection -- a table to hold items<br />
Collection = {<br />
add = function (self, item)<br />
if item ~= nil then<br />
self.n = self.n + 1<br />
self[self.n] = item<br />
end<br />
end,<br />
join = function (self, sep)<br />
return table.concat(self, sep)<br />
end,<br />
remove = function (self, pos)<br />
if self.n > 0 and (pos == nil or (0 < pos and pos <= self.n)) then<br />
self.n = self.n - 1<br />
return table.remove(self, pos)<br />
end<br />
end,<br />
sort = function (self, comp)<br />
table.sort(self, comp)<br />
end,<br />
new = function ()<br />
return setmetatable({n = 0}, Collection)<br />
end<br />
}<br />
Collection.__index = Collection<br />
<br />
local function stripToNil(text)<br />
-- If text is a string, return its trimmed content, or nil if empty.<br />
-- Otherwise return text (which may, for example, be nil).<br />
if type(text) == 'string' then<br />
text = text:match('(%S.-)%s*$')<br />
end<br />
return text<br />
end<br />
<br />
local function yes(parameter)<br />
-- Return true if parameter should be interpreted as "yes".<br />
-- Do not want to accept mixed upper/lowercase unless done by current templates.<br />
-- Need to accept "on" because "round=on" is wanted.<br />
return ({ y = true, yes = true, on = true })[parameter]<br />
end<br />
<br />
local function message(msg, id)<br />
-- Return formatted message text for an error or warning.<br />
local function getText(msg)<br />
return mtext[msg] or error('Bug: message "' .. tostring(msg) .. '" not defined')<br />
end<br />
local text<br />
if type(msg) == 'table' then<br />
text = getText(msg[1])<br />
local rep = {}<br />
for i, v in ipairs(msg) do<br />
if i > 1 then<br />
rep['$' .. (i - 1)] = v<br />
end<br />
end<br />
text = text:gsub('$%d+', rep)<br />
else<br />
text = getText(msg)<br />
end<br />
local categories = {<br />
error = '[[Category:Age error]]',<br />
warning = '[[Category:Age error]]', -- same as error until determine whether 'Age warning' would be worthwhile<br />
}<br />
local a, b, category<br />
if id == 'warning' then<br />
a = '<sup>[<i>'<br />
b = '</i>]</sup>'<br />
else<br />
a = '<strong class="error">' .. getText('txt-error')<br />
b = '</strong>'<br />
end<br />
if mw.title.getCurrentTitle():inNamespaces(0) then<br />
-- Category only in namespaces: 0=article.<br />
category = categories[id or 'error']<br />
end<br />
return<br />
a ..<br />
mw.text.nowiki(text) ..<br />
b ..<br />
(category or '')<br />
end<br />
<br />
local function formatNumber(number)<br />
-- Return the given number formatted with commas as group separators,<br />
-- given that the number is an integer.<br />
local numstr = tostring(number)<br />
local length = #numstr<br />
local places = Collection.new()<br />
local pos = 0<br />
repeat<br />
places:add(pos)<br />
pos = pos + 3<br />
until pos >= length<br />
places:add(length)<br />
local groups = Collection.new()<br />
for i = places.n, 2, -1 do<br />
local p1 = length - places[i] + 1<br />
local p2 = length - places[i - 1]<br />
groups:add(numstr:sub(p1, p2))<br />
end<br />
return groups:join(',')<br />
end<br />
<br />
local function spellNumber(number, options, i)<br />
-- Return result of spelling number, or<br />
-- return number (as a string) if cannot spell it.<br />
-- i == 1 for the first number which can optionally start with an uppercase letter.<br />
number = tostring(number)<br />
return require('Module:ConvertNumeric').spell_number(<br />
number,<br />
nil, -- fraction numerator<br />
nil, -- fraction denominator<br />
i == 1 and options.upper, -- true: 'One' instead of 'one'<br />
not options.us, -- true: use 'and' between tens/ones etc<br />
options.adj, -- true: hyphenated<br />
options.ordinal -- true: 'first' instead of 'one'<br />
) or number<br />
end<br />
<br />
local function makeExtra(args, flagCurrent)<br />
-- Return extra text that will be inserted before the visible result<br />
-- but after any sort key.<br />
local extra = args.prefix or ''<br />
if mw.ustring.len(extra) > 1 then<br />
-- Parameter "~" gives "~3" whereas "over" gives "over 3".<br />
if extra:sub(-6, -1) ~= '&nbsp;' then<br />
extra = extra .. ' '<br />
end<br />
end<br />
if flagCurrent then<br />
extra = '<span class="currentage"></span>' .. extra<br />
end<br />
return extra<br />
end<br />
<br />
local function makeSort(value, sortable)<br />
-- Return a sort key if requested.<br />
-- Assume value is a valid number which has not overflowed.<br />
if sortable == 'sortable_table' or sortable == 'sortable_on' or sortable == 'sortable_debug' then<br />
local sortKey<br />
if value == 0 then<br />
sortKey = '5000000000000000000'<br />
else<br />
local mag = math.floor(math.log10(math.abs(value)) + 1e-14)<br />
if value > 0 then<br />
sortKey = 7000 + mag<br />
else<br />
sortKey = 2999 - mag<br />
value = value + 10^(mag+1)<br />
end<br />
sortKey = string.format('%d', sortKey) .. string.format('%015.0f', math.floor(value * 10^(14-mag)))<br />
end<br />
local result<br />
if sortable == 'sortable_table' then<br />
result = 'data-sort-value="_SORTKEY_"|'<br />
elseif sortable == 'sortable_debug' then<br />
result = '<span data-sort-value="_SORTKEY_♠"><span style="border:1px solid">_SORTKEY_♠</span></span>'<br />
else<br />
result = '<span data-sort-value="_SORTKEY_♠"></span>'<br />
end<br />
return result:gsub('_SORTKEY_', sortKey)<br />
end<br />
end<br />
<br />
local translateParameters = {<br />
abbr = {<br />
off = 'abbr_off',<br />
on = 'abbr_on',<br />
},<br />
disp = {<br />
age = 'disp_age',<br />
raw = 'disp_raw',<br />
},<br />
format = {<br />
raw = 'format_raw',<br />
commas = 'format_commas',<br />
},<br />
round = {<br />
on = 'on',<br />
yes = 'on',<br />
months = 'ym',<br />
weeks = 'ymw',<br />
days = 'ymd',<br />
hours = 'ymdh',<br />
},<br />
sep = {<br />
comma = 'sep_comma',<br />
[','] = 'sep_comma',<br />
serialcomma = 'sep_serialcomma',<br />
space = 'sep_space',<br />
},<br />
show = {<br />
hide = { id = 'hide' },<br />
y = { 'y', id = 'y' },<br />
ym = { 'y', 'm', id = 'ym' },<br />
ymd = { 'y', 'm', 'd', id = 'ymd' },<br />
ymw = { 'y', 'm', 'w', id = 'ymw' },<br />
ymwd = { 'y', 'm', 'w', 'd', id = 'ymwd' },<br />
yd = { 'y', 'd', id = 'yd', keepZero = true },<br />
m = { 'm', id = 'm' },<br />
md = { 'm', 'd', id = 'md' },<br />
w = { 'w', id = 'w' },<br />
wd = { 'w', 'd', id = 'wd' },<br />
h = { 'H', id = 'h' },<br />
hm = { 'H', 'M', id = 'hm' },<br />
hms = { 'H', 'M', 'S', id = 'hms' },<br />
M = { 'M', id = 'M' },<br />
s = { 'S', id = 's' },<br />
d = { 'd', id = 'd' },<br />
dh = { 'd', 'H', id = 'dh' },<br />
dhm = { 'd', 'H', 'M', id = 'dhm' },<br />
dhms = { 'd', 'H', 'M', 'S', id = 'dhms' },<br />
ymdh = { 'y', 'm', 'd', 'H', id = 'ymdh' },<br />
ymdhm = { 'y', 'm', 'd', 'H', 'M', id = 'ymdhm' },<br />
ymwdh = { 'y', 'm', 'w', 'd', 'H', id = 'ymwdh' },<br />
ymwdhm = { 'y', 'm', 'w', 'd', 'H', 'M', id = 'ymwdhm' },<br />
},<br />
sortable = {<br />
off = false,<br />
on = 'sortable_on',<br />
table = 'sortable_table',<br />
debug = 'sortable_debug',<br />
},<br />
}<br />
<br />
local spellOptions = {<br />
cardinal = {},<br />
Cardinal = { upper = true },<br />
cardinal_us = { us = true },<br />
Cardinal_us = { us = true, upper = true },<br />
ordinal = { ordinal = true },<br />
Ordinal = { ordinal = true, upper = true },<br />
ordinal_us = { ordinal = true, us = true },<br />
Ordinal_us = { ordinal = true, us = true, upper = true },<br />
}<br />
<br />
local function dateExtract(frame)<br />
-- Return part of a date after performing an optional operation.<br />
local Date = getExports(frame)<br />
local args = frame:getParent().args<br />
local parms = {}<br />
for i, v in ipairs(args) do<br />
parms[i] = v<br />
end<br />
if yes(args.fix) then<br />
table.insert(parms, 'fix')<br />
end<br />
if yes(args.partial) then<br />
table.insert(parms, 'partial')<br />
end<br />
local show = stripToNil(args.show) or 'dmy'<br />
local date = Date(unpack(parms))<br />
if not date then<br />
if show == 'format' then<br />
return 'error'<br />
end<br />
return message('mt-need-valid-date')<br />
end<br />
local add = stripToNil(args.add)<br />
if add then<br />
for item in add:gmatch('%S+') do<br />
date = date + item<br />
if not date then<br />
return message({ 'mt-cannot-add', item })<br />
end<br />
end<br />
end<br />
local sortKey, result<br />
local sortable = translateParameters.sortable[args.sortable]<br />
if sortable then<br />
local value = (date.partial and date.partial.first or date).jdz<br />
sortKey = makeSort(value, sortable)<br />
end<br />
if show ~= 'hide' then<br />
result = date[show]<br />
if result == nil then<br />
result = from_en(date:text(show))<br />
elseif type(result) == 'boolean' then<br />
result = result and '1' or '0'<br />
else<br />
result = from_en(tostring(result))<br />
end<br />
end<br />
return (sortKey or '') .. makeExtra(args) .. (result or '')<br />
end<br />
<br />
local function rangeJoin(range)<br />
-- Return text to be used between a range of ages.<br />
return range == 'dash' and '–' or mtext['txt-or']<br />
end<br />
<br />
local function makeText(values, components, names, options, noUpper)<br />
-- Return wikitext representing an age or duration.<br />
local text = Collection.new()<br />
local count = #values<br />
local sep = names.sep or ''<br />
for i, v in ipairs(values) do<br />
-- v is a number (say 4 for 4 years), or a table ({4,5} for 4 or 5 years).<br />
local islist = type(v) == 'table'<br />
if (islist or v > 0) or (text.n == 0 and i == count) or (text.n > 0 and components.keepZero) then<br />
local fmt, vstr<br />
if options.spell then<br />
fmt = function(number)<br />
return spellNumber(number, options.spell, noUpper or i)<br />
end<br />
elseif i == 1 and options.format == 'format_commas' then<br />
-- Numbers after the first should be small and not need formatting.<br />
fmt = formatNumber<br />
else<br />
fmt = tostring<br />
end<br />
if islist then<br />
vstr = fmt(v[1]) .. rangeJoin(options.range)<br />
noUpper = true<br />
vstr = vstr .. fmt(v[2])<br />
else<br />
vstr = fmt(v)<br />
end<br />
local name = names[components[i]]<br />
if name then<br />
local plural = names.plural<br />
if not plural or (islist and v[2] or v) == 1 then<br />
plural = ''<br />
end<br />
text:add(vstr .. sep .. name .. plural)<br />
else<br />
text:add(vstr)<br />
end<br />
end<br />
end<br />
local first, last<br />
if options.join == 'sep_space' then<br />
first = ' '<br />
last = ' '<br />
elseif options.join == 'sep_comma' then<br />
first = ', '<br />
last = ', '<br />
elseif options.join == 'sep_serialcomma' and text.n > 2 then<br />
first = ', '<br />
last = mtext['txt-comma-and']<br />
else<br />
first = ', '<br />
last = mtext['txt-and']<br />
end<br />
for i, v in ipairs(text) do<br />
if i < text.n then<br />
text[i] = v .. (i + 1 < text.n and first or last)<br />
end<br />
end<br />
local sign = ''<br />
if options.isnegative then<br />
-- Do not display negative zero.<br />
if text.n > 1 or (text.n == 1 and text[1]:sub(1, 1) ~= '0' ) then<br />
if options.format == 'format_raw' then<br />
sign = '-' -- plain hyphen so result can be used in a calculation<br />
else<br />
sign = '−' -- Unicode U+2212 MINUS SIGN<br />
end<br />
end<br />
end<br />
return<br />
(options.sortKey or '') ..<br />
(options.extra or '') ..<br />
sign ..<br />
text:join() ..<br />
(options.suffix or '')<br />
end<br />
<br />
local function dateDifference(parms)<br />
-- Return a formatted date difference using the given parameters<br />
-- which have been validated.<br />
local names = {<br />
abbr_off = {<br />
plural = 's',<br />
sep = '&nbsp;',<br />
y = 'year',<br />
m = 'month',<br />
w = 'week',<br />
d = 'day',<br />
H = 'hour',<br />
M = 'minute',<br />
S = 'second',<br />
},<br />
abbr_on = {<br />
y = 'y',<br />
m = 'm',<br />
w = 'w',<br />
d = 'd',<br />
H = 'h',<br />
M = 'm',<br />
S = 's',<br />
},<br />
abbr_infant = { -- for {{age for infant}}<br />
plural = 's',<br />
sep = '&nbsp;',<br />
y = 'yr',<br />
m = 'mo',<br />
w = 'wk',<br />
d = 'day',<br />
H = 'hr',<br />
M = 'min',<br />
S = 'sec',<br />
},<br />
abbr_raw = {},<br />
}<br />
local diff = parms.diff -- must be a valid date difference<br />
local show = parms.show -- may be nil; default is set below<br />
local abbr = parms.abbr or 'abbr_off'<br />
local defaultJoin<br />
if abbr ~= 'abbr_off' then<br />
defaultJoin = 'sep_space'<br />
end<br />
if not show then<br />
show = 'ymd'<br />
if parms.disp == 'disp_age' then<br />
if diff.years < 3 then<br />
defaultJoin = 'sep_space'<br />
if diff.years >= 1 then<br />
show = 'ym'<br />
else<br />
show = 'md'<br />
end<br />
else<br />
show = 'y'<br />
end<br />
end<br />
end<br />
if type(show) ~= 'table' then<br />
show = translateParameters.show[show]<br />
end<br />
if parms.disp == 'disp_raw' then<br />
defaultJoin = 'sep_space'<br />
abbr = 'abbr_raw'<br />
elseif parms.wantSc then<br />
defaultJoin = 'sep_serialcomma'<br />
end<br />
local diffOptions = {<br />
round = parms.round,<br />
duration = parms.wantDuration,<br />
range = parms.range and true or nil,<br />
}<br />
local sortKey<br />
if parms.sortable then<br />
local value = diff.age_days + (parms.wantDuration and 1 or 0) -- days and fraction of a day<br />
if diff.isnegative then<br />
value = -value<br />
end<br />
sortKey = makeSort(value, parms.sortable)<br />
end<br />
local textOptions = {<br />
extra = parms.extra,<br />
format = parms.format,<br />
join = parms.sep or defaultJoin,<br />
isnegative = diff.isnegative,<br />
range = parms.range,<br />
sortKey = sortKey,<br />
spell = parms.spell,<br />
suffix = parms.suffix, -- not currently used<br />
}<br />
if show.id == 'hide' then<br />
return sortKey or ''<br />
end<br />
local values = { diff:age(show.id, diffOptions) }<br />
if values[1] then<br />
return makeText(values, show, names[abbr], textOptions)<br />
end<br />
if diff.partial then<br />
-- Handle a more complex range such as<br />
-- {{age_yd|20 Dec 2001|2003|range=yes}} → 1 year, 12 days or 2 years, 11 days<br />
local opt = {<br />
format = textOptions.format,<br />
join = textOptions.join,<br />
isnegative = textOptions.isnegative,<br />
spell = textOptions.spell,<br />
}<br />
return<br />
(textOptions.sortKey or '') ..<br />
makeText({ diff.partial.mindiff:age(show.id, diffOptions) }, show, names[abbr], opt) ..<br />
rangeJoin(textOptions.range) ..<br />
makeText({ diff.partial.maxdiff:age(show.id, diffOptions) }, show, names[abbr], opt, true) ..<br />
(textOptions.suffix or '')<br />
end<br />
return message({ 'mt-bad-show', show.id })<br />
end<br />
<br />
local function getDates(frame, getopt)<br />
-- Parse template parameters and return one of:<br />
-- * date (a date table, if single)<br />
-- * date1, date2 (two date tables, if not single)<br />
-- * text (a string error message)<br />
-- A missing date is optionally replaced with the current date.<br />
-- If wantMixture is true, a missing date component is replaced<br />
-- from the current date, so can get a bizarre mixture of<br />
-- specified/current y/m/d as has been done by some "age" templates.<br />
-- Some results may be placed in table getopt.<br />
local Date, currentDate = getExports(frame)<br />
getopt = getopt or {}<br />
local function flagCurrent(text)<br />
-- This allows the calling template to detect if the current date has been used,<br />
-- that is, whether both dates have been entered in a template expecting two.<br />
-- For example, an infobox may want the age when an event occurred, not the current age.<br />
-- Don't bother detecting if wantMixture is used because not needed and it is a poor option.<br />
if not text then<br />
if getopt.noMissing then<br />
return nil -- this gives a nil date which gives an error<br />
end<br />
text = 'currentdate'<br />
if getopt.flag == 'usesCurrent' then<br />
getopt.usesCurrent = true<br />
end<br />
end<br />
return text<br />
end<br />
local args = frame:getParent().args<br />
local fields = {}<br />
local isNamed = args.year or args.year1 or args.year2 or<br />
args.month or args.month1 or args.month2 or<br />
args.day or args.day1 or args.day2<br />
if isNamed then<br />
fields[1] = args.year1 or args.year<br />
fields[2] = args.month1 or args.month<br />
fields[3] = args.day1 or args.day<br />
fields[4] = args.year2<br />
fields[5] = args.month2<br />
fields[6] = args.day2<br />
else<br />
for i = 1, 6 do<br />
fields[i] = args[i]<br />
end<br />
end<br />
local imax = 0<br />
for i = 1, 6 do<br />
fields[i] = stripToNil(fields[i])<br />
if fields[i] then<br />
imax = i<br />
end<br />
if getopt.omitZero and i % 3 ~= 1 then -- omit zero months and days as unknown values but keep year 0 which is 1 BCE<br />
if isZero(fields[i]) then<br />
fields[i] = nil<br />
getopt.partial = true<br />
end<br />
end<br />
end<br />
local fix = getopt.fix and 'fix' or ''<br />
local partialText = getopt.partial and 'partial' or ''<br />
local dates = {}<br />
if isNamed or imax >= 3 then<br />
local nrDates = getopt.single and 1 or 2<br />
if getopt.wantMixture then<br />
-- Cannot be partial since empty fields are set from current.<br />
local components = { 'year', 'month', 'day' }<br />
for i = 1, nrDates * 3 do<br />
fields[i] = fields[i] or currentDate[components[i > 3 and i - 3 or i]]<br />
end<br />
for i = 1, nrDates do<br />
local index = i == 1 and 1 or 4<br />
local y, m, d = fields[index], fields[index+1], fields[index+2]<br />
if (m == 2 or m == '2') and (d == 29 or d == '29') then<br />
-- Workaround error with following which attempt to use invalid date 2001-02-29.<br />
-- {{age_ymwd|year1=2001|year2=2004|month2=2|day2=29}}<br />
-- {{age_ymwd|year1=2001|month1=2|year2=2004|month2=1|day2=29}}<br />
-- TODO Get rid of wantMixture because even this ugly code does not handle<br />
-- 'Feb' or 'February' or 'feb' or 'february'.<br />
if not ((y % 4 == 0 and y % 100 ~= 0) or y % 400 == 0) then<br />
d = 28<br />
end<br />
end<br />
dates[i] = Date(y, m, d)<br />
end<br />
else<br />
-- If partial dates are allowed, accept<br />
-- year only, or<br />
-- year and month only<br />
-- Do not accept year and day without a month because that makes no sense<br />
-- (and because, for example, Date('partial', 2001, nil, 12) sets day = nil, not 12).<br />
for i = 1, nrDates do<br />
local index = i == 1 and 1 or 4<br />
local y, m, d = fields[index], fields[index+1], fields[index+2]<br />
if (getopt.partial and y and (m or not d)) or (y and m and d) then<br />
dates[i] = Date(fix, partialText, y, m, d)<br />
elseif not y and not m and not d then<br />
dates[i] = Date(flagCurrent())<br />
end<br />
end<br />
end<br />
else<br />
getopt.textdates = true -- have parsed each date from a single text field<br />
dates[1] = Date(fix, partialText, flagCurrent(fields[1]))<br />
if not getopt.single then<br />
dates[2] = Date(fix, partialText, flagCurrent(fields[2]))<br />
end<br />
end<br />
if not dates[1] then<br />
return message(getopt.missing1 or 'mt-need-valid-ymd')<br />
end<br />
if getopt.single then<br />
return dates[1]<br />
end<br />
if not dates[2] then<br />
return message(getopt.missing2 or 'mt-need-valid-ymd2')<br />
end<br />
return dates[1], dates[2]<br />
end<br />
<br />
local function ageGeneric(frame)<br />
-- Return the result required by the specified template.<br />
-- Can use sortable=x where x = on/table/off/debug in any supported template.<br />
-- Some templates default to sortable=on but can be overridden.<br />
local name = frame.args.template<br />
if not name then<br />
return message('mt-template-x')<br />
end<br />
local args = frame:getParent().args<br />
local specs = {<br />
age_days = { -- {{age in days}}<br />
show = 'd',<br />
disp = 'disp_raw',<br />
},<br />
age_days_nts = { -- {{age in days nts}}<br />
show = 'd',<br />
disp = 'disp_raw',<br />
format = 'format_commas',<br />
sortable = 'on',<br />
},<br />
duration_days = { -- {{duration in days}}<br />
show = 'd',<br />
disp = 'disp_raw',<br />
duration = true,<br />
},<br />
duration_days_nts = { -- {{duration in days nts}}<br />
show = 'd',<br />
disp = 'disp_raw',<br />
format = 'format_commas',<br />
sortable = 'on',<br />
duration = true,<br />
},<br />
age_full_years = { -- {{age}}<br />
show = 'y',<br />
abbr = 'abbr_raw',<br />
flag = 'usesCurrent',<br />
omitZero = true,<br />
range = 'no',<br />
},<br />
age_full_years_nts = { -- {{age nts}}<br />
show = 'y',<br />
abbr = 'abbr_raw',<br />
format = 'format_commas',<br />
sortable = 'on',<br />
},<br />
age_in_years = { -- {{age in years}}<br />
show = 'y',<br />
abbr = 'abbr_raw',<br />
negative = 'error',<br />
range = 'dash',<br />
},<br />
age_in_years_nts = { -- {{age in years nts}}<br />
show = 'y',<br />
abbr = 'abbr_raw',<br />
negative = 'error',<br />
range = 'dash',<br />
format = 'format_commas',<br />
sortable = 'on',<br />
},<br />
age_infant = { -- {{age for infant}}<br />
-- Do not set show because special processing is done later.<br />
abbr = yes(args.abbr) and 'abbr_infant' or 'abbr_off',<br />
disp = 'disp_age',<br />
sep = 'sep_space',<br />
sortable = 'on',<br />
},<br />
age_m = { -- {{age in months}}<br />
show = 'm',<br />
disp = 'disp_raw',<br />
},<br />
age_w = { -- {{age in weeks}}<br />
show = 'w',<br />
disp = 'disp_raw',<br />
},<br />
age_wd = { -- {{age in weeks and days}}<br />
show = 'wd',<br />
},<br />
age_yd = { -- {{age in years and days}}<br />
show = 'yd',<br />
format = 'format_commas',<br />
sep = args.sep ~= 'and' and 'sep_comma' or nil,<br />
},<br />
age_yd_nts = { -- {{age in years and days nts}}<br />
show = 'yd',<br />
format = 'format_commas',<br />
sep = args.sep ~= 'and' and 'sep_comma' or nil,<br />
sortable = 'on',<br />
},<br />
age_ym = { -- {{age in years and months}}<br />
show = 'ym',<br />
sep = 'sep_comma',<br />
},<br />
age_ymd = { -- {{age in years, months and days}}<br />
show = 'ymd',<br />
range = true,<br />
},<br />
age_ymwd = { -- {{age in years, months, weeks and days}}<br />
show = 'ymwd',<br />
wantMixture = true,<br />
},<br />
}<br />
local spec = specs[name]<br />
if not spec then<br />
return message('mt-template-bad-name')<br />
end<br />
if name == 'age_days' then<br />
local su = stripToNil(args['show unit'])<br />
if su then<br />
if su == 'abbr' or su == 'full' then<br />
spec.disp = nil<br />
spec.abbr = su == 'abbr' and 'abbr_on' or nil<br />
end<br />
end<br />
end<br />
local partial, autofill<br />
local range = stripToNil(args.range) or spec.range<br />
if range then<br />
-- Suppose partial dates are used and age could be 11 or 12 years.<br />
-- "|range=" (empty value) has no effect (spec is used).<br />
-- "|range=yes" or spec.range == true sets range = true (gives "11 or 12")<br />
-- "|range=dash" or spec.range == 'dash' sets range = 'dash' (gives "11–12").<br />
-- "|range=no" or spec.range == 'no' sets range = nil and fills each date in the diff (gives "12").<br />
-- ("on" is equivalent to "yes", and "off" is equivalent to "no").<br />
-- "|range=OTHER" sets range = nil and rejects partial dates.<br />
range = ({ dash = 'dash', off = 'no', no = 'no', [true] = true })[range] or yes(range)<br />
if range then<br />
partial = true -- accept partial dates with a possible age range for the result<br />
if range == 'no' then<br />
autofill = true -- missing month/day in first or second date are filled from other date or 1<br />
range = nil<br />
end<br />
end<br />
end<br />
local getopt = {<br />
fix = yes(args.fix),<br />
flag = stripToNil(args.flag) or spec.flag,<br />
omitZero = spec.omitZero,<br />
partial = partial,<br />
wantMixture = spec.wantMixture,<br />
}<br />
local date1, date2 = getDates(frame, getopt)<br />
if type(date1) == 'string' then<br />
return date1<br />
end<br />
local format = stripToNil(args.format)<br />
local spell = spellOptions[format]<br />
if format then<br />
format = 'format_' .. format<br />
elseif name == 'age_days' and getopt.textdates then<br />
format = 'format_commas'<br />
end<br />
local parms = {<br />
diff = date2:subtract(date1, { fill = autofill }),<br />
wantDuration = spec.duration or yes(args.duration),<br />
range = range,<br />
wantSc = yes(args.sc),<br />
show = args.show == 'hide' and 'hide' or spec.show,<br />
abbr = spec.abbr,<br />
disp = spec.disp,<br />
extra = makeExtra(args, getopt.usesCurrent and format ~= 'format_raw'),<br />
format = format or spec.format,<br />
round = yes(args.round),<br />
sep = spec.sep,<br />
sortable = translateParameters.sortable[args.sortable or spec.sortable],<br />
spell = spell,<br />
}<br />
if (spec.negative or frame.args.negative) == 'error' and parms.diff.isnegative then<br />
return message('mt-date-wrong-order')<br />
end<br />
return from_en(dateDifference(parms))<br />
end<br />
<br />
local function bda(frame)<br />
-- Implement [[Template:Birth date and age]].<br />
local args = frame:getParent().args<br />
local options = {<br />
missing1 = 'mt-need-valid-bd',<br />
noMissing = true,<br />
single = true,<br />
}<br />
local date = getDates(frame, options)<br />
if type(date) == 'string' then<br />
return date -- error text<br />
end<br />
local Date = getExports(frame)<br />
local diff = Date('currentdate') - date<br />
if diff.isnegative or diff.years > 150 then<br />
return message('mt-invalid-bd-age')<br />
end<br />
local disp, show = 'disp_raw', 'y'<br />
if diff.years < 2 then<br />
disp = 'disp_age'<br />
if diff.years == 0 and diff.months == 0 then<br />
show = 'd'<br />
else<br />
show = 'm'<br />
end<br />
end<br />
local df = stripToNil(args.df) -- day first (dmy); default is month first (mdy)<br />
local result = '(<span class="bday">%-Y-%m-%d</span>) </span>' ..<br />
(df and '%-d %B %-Y' or '%B %-d, %-Y')<br />
result = from_en('<span style="display:none"> ' ..<br />
date:text(result) ..<br />
'<span class="noprint ForceAgeToShow"> ' ..<br />
mtext['txt-age'] ..<br />
dateDifference({<br />
diff = diff,<br />
show = show,<br />
abbr = 'abbr_off',<br />
disp = disp,<br />
sep = 'sep_space',<br />
}) ..<br />
')</span>')<br />
local warnings = tonumber(frame.args.warnings)<br />
if warnings and warnings > 0 then<br />
local good = {<br />
df = true,<br />
mf = true,<br />
day = true,<br />
day1 = true,<br />
month = true,<br />
month1 = true,<br />
year = true,<br />
year1 = true,<br />
}<br />
local invalid<br />
local imax = options.textdates and 1 or 3<br />
for k, _ in pairs(args) do<br />
if type(k) == 'number' then<br />
if k > imax then<br />
invalid = tostring(k)<br />
break<br />
end<br />
else<br />
if not good[k] then<br />
invalid = k<br />
break<br />
end<br />
end<br />
end<br />
if invalid then<br />
result = result .. message({ 'mt-bad-param1', invalid }, 'warning')<br />
end<br />
end<br />
return result<br />
end<br />
<br />
local function dda(frame)<br />
-- Implement [[Template:Death date and age]].<br />
local args = frame:getParent().args<br />
local options = {<br />
missing1 = 'mt-need-valid-dd',<br />
missing2 = 'mt-need-valid-bd2',<br />
noMissing = true,<br />
partial = true,<br />
}<br />
local date1, date2 = getDates(frame, options)<br />
if type(date1) == 'string' then<br />
return date1<br />
end<br />
local diff = date1 - date2<br />
if diff.isnegative then<br />
return message('mt-dd-wrong-order')<br />
end<br />
local Date = getExports(frame)<br />
local today = Date('currentdate') + 1 -- one day in future allows for timezones<br />
if date1 > today then<br />
return message('mt-dd-future')<br />
end<br />
local years<br />
if diff.partial then<br />
years = diff.partial.years<br />
years = type(years) == 'table' and years[2] or years<br />
else<br />
years = diff.years<br />
end<br />
if years > 150 then<br />
return message('mt-invalid-dates-age')<br />
end<br />
local df = stripToNil(args.df) -- day first (dmy); default is month first (mdy)<br />
local result<br />
if date1.day then -- y, m, d known<br />
result = (df and<br />
'%-d %B %-Y' or<br />
'%B %-d, %-Y') ..<br />
'<span style="display:none">(%-Y-%m-%d)</span>'<br />
elseif date1.month then -- y, m known; d unknown<br />
result =<br />
'%B %-Y' ..<br />
'<span style="display:none">(%-Y-%m-00)</span>'<br />
else -- y known; m, d unknown<br />
result =<br />
'%-Y' ..<br />
'<span style="display:none">(%-Y-00-00)</span>'<br />
end<br />
result = from_en(date1:text(result) ..<br />
mtext['txt-aged'] ..<br />
dateDifference({<br />
diff = diff,<br />
show = 'y',<br />
abbr = 'abbr_off',<br />
disp = 'disp_raw',<br />
range = 'dash',<br />
sep = 'sep_space',<br />
}) ..<br />
')')<br />
local warnings = tonumber(frame.args.warnings)<br />
if warnings and warnings > 0 then<br />
local good = {<br />
df = true,<br />
mf = true,<br />
}<br />
local invalid<br />
local imax = options.textdates and 2 or 6<br />
for k, _ in pairs(args) do<br />
if type(k) == 'number' then<br />
if k > imax then<br />
invalid = tostring(k)<br />
break<br />
end<br />
else<br />
if not good[k] then<br />
invalid = k<br />
break<br />
end<br />
end<br />
end<br />
if invalid then<br />
result = result .. message({ 'mt-bad-param1', invalid }, 'warning')<br />
end<br />
end<br />
return result<br />
end<br />
<br />
local function dateToGsd(frame)<br />
-- Implement [[Template:Gregorian serial date]].<br />
-- Return Gregorian serial date of the given date, or the current date.<br />
-- The returned value is negative for dates before 1 January 1 AD<br />
-- despite the fact that GSD is not defined for such dates.<br />
local date = getDates(frame, { wantMixture=true, single=true })<br />
if type(date) == 'string' then<br />
return date<br />
end<br />
return tostring(date.gsd)<br />
end<br />
<br />
local function jdToDate(frame)<br />
-- Return formatted date from a Julian date.<br />
-- The result includes a time if the input includes a fraction.<br />
-- The word 'Julian' is accepted for the Julian calendar.<br />
local Date = getExports(frame)<br />
local args = frame:getParent().args<br />
local date = Date('juliandate', args[1], args[2])<br />
if date then<br />
return from_en(date:text())<br />
end<br />
return message('mt-need-jdn')<br />
end<br />
<br />
local function dateToJd(frame)<br />
-- Return Julian date (a number) from a date which may include a time,<br />
-- or the current date ('currentdate') or current date and time ('currentdatetime').<br />
-- The word 'Julian' is accepted for the Julian calendar.<br />
local Date = getExports(frame)<br />
local args = frame:getParent().args<br />
local date = Date(args[1], args[2], args[3], args[4], args[5], args[6], args[7])<br />
if date then<br />
return tostring(date.jd)<br />
end<br />
return message('mt-need-valid-ymd-current')<br />
end<br />
<br />
local function timeInterval(frame)<br />
-- Implement [[Template:Time interval]].<br />
-- There are two positional arguments: date1, date2.<br />
-- The default for each is the current date and time.<br />
-- Result is date2 - date1 formatted.<br />
local Date = getExports(frame)<br />
local args = frame:getParent().args<br />
local parms = {<br />
extra = makeExtra(args),<br />
wantDuration = yes(args.duration),<br />
range = yes(args.range) or (args.range == 'dash' and 'dash' or nil),<br />
wantSc = yes(args.sc),<br />
}<br />
local fix = yes(args.fix) and 'fix' or ''<br />
local date1 = Date(fix, 'partial', stripToNil(args[1]) or 'currentdatetime')<br />
if not date1 then<br />
return message('mt-invalid-start')<br />
end<br />
local date2 = Date(fix, 'partial', stripToNil(args[2]) or 'currentdatetime')<br />
if not date2 then<br />
return message('mt-invalid-end')<br />
end<br />
parms.diff = date2 - date1<br />
for argname, translate in pairs(translateParameters) do<br />
local parm = stripToNil(args[argname])<br />
if parm then<br />
parm = translate[parm]<br />
if parm == nil then -- test for nil because false is a valid setting<br />
return message({ 'mt-bad-param2', argname, args[argname] })<br />
end<br />
parms[argname] = parm<br />
end<br />
end<br />
if parms.round then<br />
local round = parms.round<br />
local show = parms.show<br />
if round ~= 'on' then<br />
if show then<br />
if show.id ~= round then<br />
return message({ 'mt-conflicting-show', args.show, args.round })<br />
end<br />
else<br />
parms.show = translateParameters.show[round]<br />
end<br />
end<br />
parms.round = true<br />
end<br />
return from_en(dateDifference(parms))<br />
end<br />
<br />
return {<br />
age_generic = ageGeneric, -- can emulate several age templates<br />
birth_date_and_age = bda, -- Template:Birth_date_and_age<br />
death_date_and_age = dda, -- Template:Death_date_and_age<br />
gsd = dateToGsd, -- Template:Gregorian_serial_date<br />
extract = dateExtract, -- Template:Extract<br />
jd_to_date = jdToDate, -- Template:?<br />
JULIANDAY = dateToJd, -- Template:JULIANDAY<br />
time_interval = timeInterval, -- Template:Time_interval<br />
}</div>Swarma