else
result = '<span data-sort-value="_SORTKEY_♠"></span>'
end
return (result:gsub('_SORTKEY_', sortKey))
end
end  
local translateParameters = {
abbr = {
off = 'abbr_off',
on = 'abbr_on',
},
disp = {
age = 'disp_age',
raw = 'disp_raw',
},
format = {
raw = 'format_raw',
commas = 'format_commas',
},
round = {
on = 'on',
yes = 'on',
months = 'ym',
weeks = 'ymw',
days = 'ymd',
hours = 'ymdh',
},
sep = {
comma = 'sep_comma',
[','] = 'sep_comma',
serialcomma = 'sep_serialcomma',
space = 'sep_space',
},
show = {
hide = { id = 'hide' },
y = { 'y', id = 'y' },
ym = { 'y', 'm', id = 'ym' },
ymd = { 'y', 'm', 'd', id = 'ymd' },
ymw = { 'y', 'm', 'w', id = 'ymw' },
ymwd = { 'y', 'm', 'w', 'd', id = 'ymwd' },
yd = { 'y', 'd', id = 'yd', keepZero = true },
m = { 'm', id = 'm' },
md = { 'm', 'd', id = 'md' },
w = { 'w', id = 'w' },
wd = { 'w', 'd', id = 'wd' },
h = { 'H', id = 'h' },
hm = { 'H', 'M', id = 'hm' },
hms = { 'H', 'M', 'S', id = 'hms' },
M = { 'M', id = 'M' },
s = { 'S', id = 's' },
d = { 'd', id = 'd' },
dh = { 'd', 'H', id = 'dh' },
dhm = { 'd', 'H', 'M', id = 'dhm' },
dhms = { 'd', 'H', 'M', 'S', id = 'dhms' },
ymdh = { 'y', 'm', 'd', 'H', id = 'ymdh' },
ymdhm = { 'y', 'm', 'd', 'H', 'M', id = 'ymdhm' },
ymwdh = { 'y', 'm', 'w', 'd', 'H', id = 'ymwdh' },
ymwdhm = { 'y', 'm', 'w', 'd', 'H', 'M', id = 'ymwdhm' },
},
sortable = {
off = false,
on = 'sortable_on',
table = 'sortable_table',
debug = 'sortable_debug',
},
}  
local spellOptions = {
cardinal = {},
Cardinal = { upper = true },
cardinal_us = { us = true },
Cardinal_us = { us = true, upper = true },
ordinal = { ordinal = true },
Ordinal = { ordinal = true, upper = true },
ordinal_us = { ordinal = true, us = true },
Ordinal_us = { ordinal = true, us = true, upper = true },
}  
local function dateExtract
-- Return part of a date after performing an optional operation.
local Date = getExports
local args = :getParent.args
local parms = {}
for i, v in ipairs(args) do
parms[i] = v
end
if yes(args.fix) then
table.insert(parms, 'fix')
end
if yes(args.partial) then
table.insert(parms, 'partial')
end
local show = stripToNil(args.show) or 'dmy'
local date = Date(unpack(parms))
if not date then
if show == 'format' then
return 'error'
end
return message('mt-need-valid-date')
end
local add = stripToNil(args.add)
if add then
for item in add:gmatch('%S+') do
date = date + item
if not date then
return message('mt-cannot-add', item)
end
end
end
local sortKey, result
local sortable = translateParameters.sortable[args.sortable]
if sortable then
local value = (date.partial and date.partial.first or date).jdz
sortKey = makeSort(value, sortable)
end
if show ~= 'hide' then
result = date[show]
if result == nil then
result = from_en(date:text(show))
elseif type(result) == 'boolean' then
result = result and '1' or '0'
else
result = from_en(tostring(result))
end
end
return (sortKey or *) .. makeExtra(args) .. (result or *)
end  
local function rangeJoin(range)
-- Return text to be used between a range of ages.
return range == 'dash' and '–' or mtext['txt-or']
end  
local function makeText(values, components, names, options, noUpper)
-- Return wikitext representing an age or duration.
local text = Collection.new
local count = #values
local sep = names.sep or ''
for i, v in ipairs(values) do
-- v is a number (say 4 for 4 years), or a table ({4,5} for 4 or 5 years).
local islist = type(v) == 'table'
if (islist or v > 0) or (text.n == 0 and i == count) or (text.n > 0 and components.keepZero) then
local fmt, vstr
if options.spell then
fmt = function(number)
return spellNumber(number, options.spell, noUpper or i)
end
elseif i == 1 and options.format == 'format_commas' then
-- Numbers after the first should be small and not need formatting.
fmt = formatNumber
else
fmt = tostring
end
if islist then
vstr = fmt(v[1]) .. rangeJoin(options.range)
noUpper = true
vstr = vstr .. fmt(v[2])
else
vstr = fmt(v)
end
local name = names[components[i]]
if name then
if type(name) == 'table' then
name = mw.getContentLanguage:plural(islist and v[2] or v, name)
end
text:add(vstr .. sep .. name)
else
text:add(vstr)
end
end
end
local first, last
if options.join == 'sep_space' then
first = ' '
last = ' '
elseif options.join == 'sep_comma' then
first = ', '
last = ', '
elseif options.join == 'sep_serialcomma' and text.n > 2 then
first = ', '
last = mtext['txt-comma-and']
else
first = ', '
last = mtext['txt-and']
end
for i, v in ipairs(text) do
if i < text.n then
text[i] = v .. (i + 1 < text.n and first or last)
end
end
local sign = ''
if options.isnegative then
-- Do not display negative zero.
if text.n > 1 or (text.n == 1 and text[1]:sub(1, 1) ~= '0' ) then
if options.format == 'format_raw' then
sign = '-' -- plain hyphen so result can be used in a calculation
else
sign = '−' -- Unicode U+2212 MINUS SIGN
end
end
end
return
(options.sortKey or '') ..
(options.extra or '') ..
sign ..
text:join ..
(options.suffix or '')
end  
local function dateDifference(parms)
-- Return a formatted date difference using the given parameters
-- which have been validated.
local names = {
-- Each name is:
-- * a string if no plural form of the name is used; or
-- * a table of strings, one of which is selected using the rules at
-- https://translatewiki.net/wiki/Plural/Mediawiki_plural_rules
abbr_off = {
plural = '-கள்',
sep = '&nbsp;',
y = {'ஆண்டு', 'ஆண்டுகள்'},
m = {'மாதம்', 'மாதங்கள்'},
w = {'கிழமை', 'கிழமைகள்'},
d = {'நாள்', 'நாட்கள்'},
H = {'மணி', 'மணித்தியாலங்கள்'},
M = {'நிமிடம்', 'நிமிடங்கள்'},
S = {'செக்கன்', 'செக்கன்கள்'},
},
abbr_on = {
y = 'y',
m = 'm',
w = 'w',
d = 'd',
H = 'h',
M = 'm',
S = 's',
},
abbr_infant = { -- for
sep = '&nbsp;',
y = {'yr', 'yrs'},
m = {'mo', 'mos'},
w = {'wk', 'wks'},
d = {'day', 'days'},
H = {'hr', 'hrs'},
M = {'min', 'mins'},
S = {'sec', 'secs'},
},
abbr_raw = {},
}
local diff = parms.diff -- must be a valid date difference
local show = parms.show -- may be nil; default is set below
local abbr = parms.abbr or 'abbr_off'
local defaultJoin
if abbr ~= 'abbr_off' then
defaultJoin = 'sep_space'
end
if not show then
show = 'ymd'
if parms.disp == 'disp_age' then
if diff.years < 3 then
defaultJoin = 'sep_space'
if diff.years >= 1 then
show = 'ym'
else
show = 'md'
end
else
show = 'y'
end
end
end
if type(show) ~= 'table' then
show = translateParameters.show[show]
end
if parms.disp == 'disp_raw' then
defaultJoin = 'sep_space'
abbr = 'abbr_raw'
elseif parms.wantSc then
defaultJoin = 'sep_serialcomma'
end
local diffOptions = {
round = parms.round,
duration = parms.wantDuration,
range = parms.range and true or nil,
}
local sortKey
if parms.sortable then
local value = diff.age_days + (parms.wantDuration and 1 or 0) -- days and fraction of a day
if diff.isnegative then
value = -value
end
sortKey = makeSort(value, parms.sortable)
end
local textOptions = {
extra = parms.extra,
format = parms.format,
join = parms.sep or defaultJoin,
isnegative = diff.isnegative,
range = parms.range,
sortKey = sortKey,
spell = parms.spell,
suffix = parms.suffix, -- not currently used
}
if show.id == 'hide' then
return sortKey or ''
end
local values = { diff:age(show.id, diffOptions) }
if values[1] then
return makeText(values, show, names[abbr], textOptions)
end
if diff.partial then
-- Handle a more complex range such as
-- → 1 year, 12 days or 2 years, 11 days
local opt = {
format = textOptions.format,
join = textOptions.join,
isnegative = textOptions.isnegative,
spell = textOptions.spell,
}
return
(textOptions.sortKey or '') ..
makeText({ diff.partial.mindiff:age(show.id, diffOptions) }, show, names[abbr], opt) ..
rangeJoin(textOptions.range) ..
makeText({ diff.partial.maxdiff:age(show.id, diffOptions) }, show, names[abbr], opt, true) ..
(textOptions.suffix or '')
end
return message('mt-bad-show', show.id)
end  
local function getDates(, getopt)
-- Parse template parameters and return one of:
-- * date (a date table, if single)
-- * date1, date2 (two date tables, if not single)
-- * text (a string error message)