Difference between revisions of "Module:Age/sandbox"
Jump to navigation
Jump to search
blackwiki>Johnuniq (require Module:Date/sandbox when invoked from a sandbox template) |
blackwiki>Johnuniq (for {{time interval}}: replace abbr=letter with abbr=on per talk; for {{age in days}}: show_unit=abbr/full; format=commas; templates can invoke age_generic so remove redundant age_xxx functions) |
||
Line 72: | Line 72: | ||
'</strong>' .. | '</strong>' .. | ||
category | category | ||
+ | end | ||
+ | |||
+ | local function formatnumber(numstr) | ||
+ | -- Return the given number formatted with commas as group separators, | ||
+ | -- given that numstr consists of en digits only (no decimal mark). | ||
+ | local length = #numstr | ||
+ | local places = collection() | ||
+ | local pos = 0 | ||
+ | repeat | ||
+ | places:add(pos) | ||
+ | pos = pos + 3 | ||
+ | until pos >= length | ||
+ | places:add(length) | ||
+ | local groups = collection() | ||
+ | for i = places.n, 2, -1 do | ||
+ | local p1 = length - places[i] + 1 | ||
+ | local p2 = length - places[i - 1] | ||
+ | groups:add(numstr:sub(p1, p2)) | ||
+ | end | ||
+ | return groups:join(',') | ||
end | end | ||
Line 82: | Line 102: | ||
for i, v in ipairs(values) do | for i, v in ipairs(values) do | ||
if v > 0 or (text.n == 0 and i == count) or (text.n > 0 and components.keepzero) then | if v > 0 or (text.n == 0 and i == count) or (text.n > 0 and components.keepzero) then | ||
+ | local vstr = tostring(v) | ||
+ | if i == 1 and options.format == 'format_commas' then | ||
+ | -- Numbers after the first should be small and not need formatting. | ||
+ | vstr = formatnumber(vstr) | ||
+ | end | ||
local name = names[components[i]] | local name = names[components[i]] | ||
if name then | if name then | ||
− | text:add( | + | text:add(vstr .. sep .. name .. (v == 1 and '' or plural)) |
else | else | ||
− | text:add( | + | text:add(vstr) |
end | end | ||
end | end | ||
Line 111: | Line 136: | ||
local sign = '' | local sign = '' | ||
if options.isnegative then | if options.isnegative then | ||
− | -- Do not | + | -- Do not display negative zero. |
if text.n > 1 or (text.n == 1 and text[1]:sub(1, 1) ~= '0' ) then | if text.n > 1 or (text.n == 1 and text[1]:sub(1, 1) ~= '0' ) then | ||
− | sign = '−' -- Unicode U+2212 MINUS SIGN | + | 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 | ||
end | end | ||
− | return sign .. text:join() .. (options. | + | return sign .. text:join() .. (options.suffix or '') |
end | end | ||
− | local | + | local translate_parameters = { |
− | y = { 'y' }, | + | 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 = { | ||
+ | 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' }, | ||
+ | d = { 'd', id = 'd' }, | ||
+ | dh = { 'd', 'H', id = 'dh' }, | ||
+ | dhm = { 'd', 'H', 'M', id = 'dhm' }, | ||
+ | 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' }, | ||
+ | }, | ||
} | } | ||
− | local function | + | local function date_difference(parms) |
-- Return a formatted date difference using the given parameters | -- Return a formatted date difference using the given parameters | ||
-- which have been validated. | -- which have been validated. | ||
Line 155: | Line 212: | ||
}, | }, | ||
abbr_on = { | abbr_on = { | ||
+ | y = 'y', | ||
+ | m = 'm', | ||
+ | w = 'w', | ||
+ | d = 'd', | ||
+ | H = 'h', | ||
+ | M = 'm', | ||
+ | }, | ||
+ | abbr_infant = { -- for {{age for infant}} | ||
plural = 's', | plural = 's', | ||
sep = ' ', | sep = ' ', | ||
Line 163: | Line 228: | ||
H = 'hr', | H = 'hr', | ||
M = 'min', | M = 'min', | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
}, | }, | ||
abbr_raw = {}, | abbr_raw = {}, | ||
Line 176: | Line 233: | ||
local diff = parms.diff -- must be a valid date difference | local diff = parms.diff -- must be a valid date difference | ||
local show = parms.show -- may be nil; default is set below | local show = parms.show -- may be nil; default is set below | ||
− | local | + | local abbr = parms.abbr or 'abbr_off' |
− | local | + | local suffix, default_join |
− | if | + | if abbr ~= 'abbr_off' then |
default_join = 'sep_space' | default_join = 'sep_space' | ||
end | end | ||
if not show then | if not show then | ||
show = 'ymd' | show = 'ymd' | ||
− | if parms. | + | if parms.disp == 'disp_age' then |
if diff.years < 3 then | if diff.years < 3 then | ||
default_join = 'sep_space' | default_join = 'sep_space' | ||
Line 192: | Line 249: | ||
end | end | ||
else | else | ||
− | |||
− | |||
show = 'y' | show = 'y' | ||
end | end | ||
end | end | ||
end | end | ||
− | if parms. | + | if type(show) ~= 'table' then |
+ | show = translate_parameters.show[show] | ||
+ | end | ||
+ | if parms.disp == 'disp_raw' then | ||
default_join = 'sep_space' | default_join = 'sep_space' | ||
− | + | abbr = 'abbr_raw' | |
elseif parms.want_sc then | elseif parms.want_sc then | ||
default_join = 'sep_serialcomma' | default_join = 'sep_serialcomma' | ||
end | end | ||
local options = { | local options = { | ||
− | + | suffix = suffix, -- not currently used | |
− | join = parms. | + | format = parms.format, |
+ | join = parms.sep or default_join, | ||
isnegative = diff.isnegative, | isnegative = diff.isnegative, | ||
} | } | ||
− | local values = { diff:age(show, parms. | + | local values = { diff:age(show.id, parms.round, parms.want_duration) } |
− | return make_text(values, | + | return make_text(values, show, names[abbr], options) |
end | end | ||
− | local function get_dates(frame, | + | local function get_dates(frame, getopt) |
-- Parse template parameters and return one of: | -- Parse template parameters and return one of: | ||
-- * date (a date table, if single) | -- * date (a date table, if single) | ||
Line 222: | Line 281: | ||
-- from the current date, so can get a bizarre mixture of | -- from the current date, so can get a bizarre mixture of | ||
-- specified/current y/m/d as has been done by some "age" templates. | -- specified/current y/m/d as has been done by some "age" templates. | ||
+ | -- Some results may be placed in table getopt. | ||
local Date, current_date = get_exports(frame) | local Date, current_date = get_exports(frame) | ||
local args = frame:getParent().args | local args = frame:getParent().args | ||
Line 245: | Line 305: | ||
end | end | ||
end | end | ||
+ | getopt = getopt or {} | ||
+ | local single = getopt.single | ||
local dates = {} | local dates = {} | ||
if is_named or imax > 2 then | if is_named or imax > 2 then | ||
local nr_dates = single and 1 or 2 | local nr_dates = single and 1 or 2 | ||
− | if want_mixture then | + | if getopt.want_mixture then |
local components = { 'year', 'month', 'day' } | local components = { 'year', 'month', 'day' } | ||
for i = 1, nr_dates * 3 do | for i = 1, nr_dates * 3 do | ||
Line 269: | Line 331: | ||
end | end | ||
else | else | ||
+ | getopt.textdates = true | ||
dates[1] = Date(fields[1] or 'currentdate') | dates[1] = Date(fields[1] or 'currentdate') | ||
if not single then | if not single then | ||
Line 286: | Line 349: | ||
end | end | ||
− | + | local function age_generic(frame, name) | |
− | + | -- Return the result required by the specified template. | |
− | + | name = name or frame.args['template'] | |
− | local function | + | if not name then |
− | -- Return the result required by the template | + | return message('The template invoking this must have "|tamplate=x" where x is the wanted operation') |
+ | end | ||
local args = frame:getParent().args | local args = frame:getParent().args | ||
local specs = { | local specs = { | ||
age_days = { | age_days = { | ||
show = 'd', | show = 'd', | ||
− | + | disp = 'disp_raw', | |
}, | }, | ||
age_full_years = { | age_full_years = { | ||
show = 'y', | show = 'y', | ||
− | + | abbr = 'abbr_raw', | |
}, | }, | ||
age_infant = { | age_infant = { | ||
− | -- Do not set show | + | -- Do not set show because special processing is done later. |
− | + | abbr = yes(args.abbr) and 'abbr_infant' or 'abbr_off', | |
− | + | disp = 'disp_age', | |
− | + | sep = 'sep_space', | |
+ | }, | ||
+ | age_m = { | ||
+ | show = 'm', | ||
+ | disp = 'disp_raw', | ||
+ | }, | ||
+ | age_w = { | ||
+ | show = 'w', | ||
+ | disp = 'disp_raw', | ||
+ | }, | ||
+ | age_wd = { | ||
+ | show = 'wd', | ||
}, | }, | ||
age_yd = { | age_yd = { | ||
show = 'yd', | show = 'yd', | ||
− | + | sep = args.sep ~= 'and' and 'sep_comma' or nil, | |
}, | }, | ||
age_ym = { | age_ym = { | ||
show = 'ym', | show = 'ym', | ||
− | + | sep = 'sep_comma', | |
}, | }, | ||
age_ymd = { | age_ymd = { | ||
Line 324: | Line 399: | ||
} | } | ||
local spec = specs[name] | local spec = specs[name] | ||
− | local date1, date2 = get_dates(frame, | + | if not spec then |
+ | return message('The specified template name is not valid') | ||
+ | end | ||
+ | if name == 'age_days' then | ||
+ | local su = strip_to_nil(args['show unit']) | ||
+ | if su then | ||
+ | if su == 'abbr' or su == 'full' then | ||
+ | spec.disp = nil | ||
+ | spec.abbr = su == 'abbr' and 'abbr_on' or nil | ||
+ | end | ||
+ | end | ||
+ | end | ||
+ | local getopt = { want_mixture = spec.want_mixture } | ||
+ | local date1, date2 = get_dates(frame, getopt) | ||
if type(date1) == 'string' then | if type(date1) == 'string' then | ||
return date1 | return date1 | ||
+ | end | ||
+ | local format = strip_to_nil(args.format) | ||
+ | if format then | ||
+ | format = 'format_' .. format | ||
+ | elseif name == 'age_days' and getopt.textdates then | ||
+ | format = 'format_commas' | ||
end | end | ||
local parms = { | local parms = { | ||
diff = date2 - date1, | diff = date2 - date1, | ||
want_duration = yes(args.duration), | want_duration = yes(args.duration), | ||
− | |||
want_sc = yes(args.sc), | want_sc = yes(args.sc), | ||
show = spec.show, | show = spec.show, | ||
− | + | abbr = spec.abbr, | |
− | + | disp = spec.disp, | |
− | + | format = format, | |
+ | round = yes(args.round), | ||
+ | sep = spec.sep, | ||
} | } | ||
− | return | + | return date_difference(parms) |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
end | end | ||
Line 381: | Line 441: | ||
-- The returned value is negative for dates before 1 January 1 AD | -- The returned value is negative for dates before 1 January 1 AD | ||
-- despite the fact that GSD is not defined for such dates. | -- despite the fact that GSD is not defined for such dates. | ||
− | local date = get_dates(frame, true, true) | + | local date = get_dates(frame, { want_mixture=true, single=true }) |
if type(date) == 'string' then | if type(date) == 'string' then | ||
return date | return date | ||
Line 434: | Line 494: | ||
end | end | ||
parms.diff = date2 - date1 | parms.diff = date2 - date1 | ||
− | + | for argname, translate in pairs(translate_parameters) do | |
− | + | local parm = strip_to_nil(args[argname]) | |
− | + | if parm then | |
− | + | parm = translate[parm] | |
− | + | if not parm then | |
− | + | return message('Parameter ' .. argname .. '=' .. args[argname] .. ' is invalid') | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
end | end | ||
− | + | parms[argname] = parm | |
end | end | ||
− | |||
end | end | ||
− | parms. | + | if parms.round then |
− | + | local round = parms.round | |
− | + | local show = parms.show | |
− | + | if round ~= 'on' then | |
− | + | if show then | |
− | + | if show.id ~= round then | |
− | + | return message('Parameter show=' .. args.show .. ' conflicts with round=' .. args.round) | |
− | + | end | |
− | + | else | |
− | + | parms.show = translate_parameters.show[round] | |
− | + | end | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
end | end | ||
+ | parms.round = true | ||
end | end | ||
− | + | return date_difference(parms) | |
− | return | ||
end | end | ||
return { | return { | ||
− | + | age_generic = age_generic, -- can emulate several age templates | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
gsd = ymd_to_gsd, -- Template:Gregorian_serial_date | gsd = ymd_to_gsd, -- Template:Gregorian_serial_date | ||
JULIANDAY = ymd_to_jd, -- Template:JULIANDAY | JULIANDAY = ymd_to_jd, -- Template:JULIANDAY |
Revision as of 07:48, 10 June 2016
Documentation for this module may be created at Module:Age/sandbox/doc
-- Implement various "age of" and other date-related templates.
--[[ TODO
Check the "This implements" functions to see what parameters are accepted by the
existing template. The code here might not be emulating what the template does.
Some functions have not been tested yet.
These features have not yet been implemented:
* Some templates accept an ambiguous date with a missing component.
* Some templates provide a hidden sort key.
* Some templates have extra parameters not yet implemented.
* {{age in years}} Age as a range (month/day unknown).
* {{age in months}}
* {{age in weeks and days}}
* {{age in weeks}}
]]
local _Date, _current_date
local function get_exports(frame)
-- Return objects exported from the date module or its sandbox.
if not _Date then
local sandbox = frame:getTitle():find('sandbox', 1, true) and '/sandbox' or ''
local datemod = require('Module:Date' .. sandbox)
_Date = datemod._Date
_current_date = datemod._current
end
return _Date, _current_date
end
local function collection()
-- Return a table to hold items.
return {
n = 0,
add = function (self, item)
self.n = self.n + 1
self[self.n] = item
end,
join = function (self, sep)
return table.concat(self, sep)
end,
}
end
local function strip_to_nil(text)
-- If text is a string, return its trimmed content, or nil if empty.
-- Otherwise return text (which may, for example, be nil).
if type(text) == 'string' then
text = text:match('(%S.-)%s*$')
end
return text
end
local function yes(parameter)
-- Return true if parameter should be interpreted as "yes".
-- Do not want to accept mixed upper/lowercase unless done by current templates.
-- Need to accept "on" because "round=on" is wanted.
return ({ y = true, yes = true, on = true })[parameter]
end
local function message(msg, nocat)
-- Return formatted message text for an error.
-- Can append "#FormattingError" to URL of a page with a problem to find it.
local anchor = '<span id="FormattingError" />'
local category
if not nocat and mw.title.getCurrentTitle():inNamespaces(0, 10) then
-- Category only in namespaces: 0=article, 10=template.
category = '[[Category:Age error]]'
else
category = ''
end
return anchor ..
'<strong class="error">Error: ' ..
mw.text.nowiki(msg) ..
'</strong>' ..
category
end
local function formatnumber(numstr)
-- Return the given number formatted with commas as group separators,
-- given that numstr consists of en digits only (no decimal mark).
local length = #numstr
local places = collection()
local pos = 0
repeat
places:add(pos)
pos = pos + 3
until pos >= length
places:add(length)
local groups = collection()
for i = places.n, 2, -1 do
local p1 = length - places[i] + 1
local p2 = length - places[i - 1]
groups:add(numstr:sub(p1, p2))
end
return groups:join(',')
end
local function make_text(values, components, names, options)
-- Return wikitext representing an age or duration.
local text = collection()
local count = #values
local plural = names.plural or ''
local sep = names.sep or ''
for i, v in ipairs(values) do
if v > 0 or (text.n == 0 and i == count) or (text.n > 0 and components.keepzero) then
local vstr = tostring(v)
if i == 1 and options.format == 'format_commas' then
-- Numbers after the first should be small and not need formatting.
vstr = formatnumber(vstr)
end
local name = names[components[i]]
if name then
text:add(vstr .. sep .. name .. (v == 1 and '' or plural))
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 = ', and '
else
first = ', '
last = ' 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 sign .. text:join() .. (options.suffix or '')
end
local translate_parameters = {
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 = {
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' },
d = { 'd', id = 'd' },
dh = { 'd', 'H', id = 'dh' },
dhm = { 'd', 'H', 'M', id = 'dhm' },
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' },
},
}
local function date_difference(parms)
-- Return a formatted date difference using the given parameters
-- which have been validated.
-- TODO Handle 'partial' dates, for example, a year only.
local names = {
abbr_off = {
plural = 's',
sep = ' ',
y = 'year',
m = 'month',
w = 'week',
d = 'day',
H = 'hour',
M = 'minute',
},
abbr_on = {
y = 'y',
m = 'm',
w = 'w',
d = 'd',
H = 'h',
M = 'm',
},
abbr_infant = { -- for {{age for infant}}
plural = 's',
sep = ' ',
y = 'yr',
m = 'mo',
w = 'wk',
d = 'day',
H = 'hr',
M = 'min',
},
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 suffix, default_join
if abbr ~= 'abbr_off' then
default_join = 'sep_space'
end
if not show then
show = 'ymd'
if parms.disp == 'disp_age' then
if diff.years < 3 then
default_join = '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 = translate_parameters.show[show]
end
if parms.disp == 'disp_raw' then
default_join = 'sep_space'
abbr = 'abbr_raw'
elseif parms.want_sc then
default_join = 'sep_serialcomma'
end
local options = {
suffix = suffix, -- not currently used
format = parms.format,
join = parms.sep or default_join,
isnegative = diff.isnegative,
}
local values = { diff:age(show.id, parms.round, parms.want_duration) }
return make_text(values, show, names[abbr], options)
end
local function get_dates(frame, 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)
-- A missing date is replaced with the current date.
-- If want_mixture is true, a missing date component is replaced
-- from the current date, so can get a bizarre mixture of
-- specified/current y/m/d as has been done by some "age" templates.
-- Some results may be placed in table getopt.
local Date, current_date = get_exports(frame)
local args = frame:getParent().args
local fields = {}
local is_named = args.year or args.year1 or args.year2
if is_named then
fields[1] = args.year1 or args.year
fields[2] = args.month1 or args.month
fields[3] = args.day1 or args.day
fields[4] = args.year2
fields[5] = args.month2
fields[6] = args.day2
else
for i = 1, 6 do
fields[i] = args[i]
end
end
local imax = 0
for i = 1, 6 do
fields[i] = strip_to_nil(fields[i])
if fields[i] then
imax = i
end
end
getopt = getopt or {}
local single = getopt.single
local dates = {}
if is_named or imax > 2 then
local nr_dates = single and 1 or 2
if getopt.want_mixture then
local components = { 'year', 'month', 'day' }
for i = 1, nr_dates * 3 do
fields[i] = fields[i] or current_date[components[i > 3 and i - 3 or i]]
end
for i = 1, nr_dates do
local index = i == 1 and 1 or 4
dates[i] = Date(fields[index], fields[index+1], fields[index+2])
end
else
for i = 1, nr_dates do
local index = i == 1 and 1 or 4
local y, m, d = fields[index], fields[index+1], fields[index+2]
if y and m and d then
dates[i] = Date(y, m, d)
elseif not (y or m or d) then
dates[i] = Date('currentdate')
end
end
end
else
getopt.textdates = true
dates[1] = Date(fields[1] or 'currentdate')
if not single then
dates[2] = Date(fields[2] or 'currentdate')
end
end
if not dates[1] then
return message('Need valid year, month, day')
end
if single then
return dates[1]
end
if not dates[2] then
return message('Second date should be year, month, day')
end
return dates[1], dates[2]
end
local function age_generic(frame, name)
-- Return the result required by the specified template.
name = name or frame.args['template']
if not name then
return message('The template invoking this must have "|tamplate=x" where x is the wanted operation')
end
local args = frame:getParent().args
local specs = {
age_days = {
show = 'd',
disp = 'disp_raw',
},
age_full_years = {
show = 'y',
abbr = 'abbr_raw',
},
age_infant = {
-- Do not set show because special processing is done later.
abbr = yes(args.abbr) and 'abbr_infant' or 'abbr_off',
disp = 'disp_age',
sep = 'sep_space',
},
age_m = {
show = 'm',
disp = 'disp_raw',
},
age_w = {
show = 'w',
disp = 'disp_raw',
},
age_wd = {
show = 'wd',
},
age_yd = {
show = 'yd',
sep = args.sep ~= 'and' and 'sep_comma' or nil,
},
age_ym = {
show = 'ym',
sep = 'sep_comma',
},
age_ymd = {
show = 'ymd',
want_mixture = true,
},
age_ymwd = {
show = 'ymwd',
},
}
local spec = specs[name]
if not spec then
return message('The specified template name is not valid')
end
if name == 'age_days' then
local su = strip_to_nil(args['show unit'])
if su then
if su == 'abbr' or su == 'full' then
spec.disp = nil
spec.abbr = su == 'abbr' and 'abbr_on' or nil
end
end
end
local getopt = { want_mixture = spec.want_mixture }
local date1, date2 = get_dates(frame, getopt)
if type(date1) == 'string' then
return date1
end
local format = strip_to_nil(args.format)
if format then
format = 'format_' .. format
elseif name == 'age_days' and getopt.textdates then
format = 'format_commas'
end
local parms = {
diff = date2 - date1,
want_duration = yes(args.duration),
want_sc = yes(args.sc),
show = spec.show,
abbr = spec.abbr,
disp = spec.disp,
format = format,
round = yes(args.round),
sep = spec.sep,
}
return date_difference(parms)
end
local function ymd_to_gsd(frame)
-- This implements {{gregorian serial date}}.
-- Return Gregorian serial date of the given date, or the current date.
-- The returned value is negative for dates before 1 January 1 AD
-- despite the fact that GSD is not defined for such dates.
local date = get_dates(frame, { want_mixture=true, single=true })
if type(date) == 'string' then
return date
end
return tostring(date.gsd)
end
local function ymd_from_jd(frame)
-- Return formatted date from a Julian date.
-- The result is y-m-d or y-m-d H:M:S if input includes a fraction.
-- The word 'Julian' is accepted for the Julian calendar.
local Date = get_exports(frame)
local args = frame:getParent().args
local date = Date('juliandate', args[1], args[2])
if date then
return date:text()
end
return message('Need valid Julian date number')
end
local function ymd_to_jd(frame)
-- Return Julian date (a number) from a date (y-m-d), or datetime (y-m-d H:M:S),
-- or the current date ('currentdate') or current date and time ('currentdatetime').
-- The word 'Julian' is accepted for the Julian calendar.
local Date = get_exports(frame)
local args = frame:getParent().args
local date = Date(args[1], args[2], args[3], args[4], args[5], args[6], args[7])
if date then
return tostring(date.jd)
end
return message('Need valid year/month/day or "currentdate"')
end
local function time_interval(frame)
-- This implements {{time interval}}.
-- There are two positional arguments: date1, date2.
-- The default for each is the current date and time.
-- Result is date2 - date1 formatted.
local Date = get_exports(frame)
local args = frame:getParent().args
local parms = {
want_duration = yes(args.duration),
want_sc = yes(args.sc),
}
local date1 = Date(strip_to_nil(args[1]) or 'currentdatetime')
if not date1 then
return message('Invalid start date in first parameter')
end
local date2 = Date(strip_to_nil(args[2]) or 'currentdatetime')
if not date2 then
return message('Invalid end date in second parameter')
end
parms.diff = date2 - date1
for argname, translate in pairs(translate_parameters) do
local parm = strip_to_nil(args[argname])
if parm then
parm = translate[parm]
if not parm then
return message('Parameter ' .. argname .. '=' .. args[argname] .. ' is invalid')
end
parms[argname] = parm
end
end
if parms.round then
local round = parms.round
local show = parms.show
if round ~= 'on' then
if show then
if show.id ~= round then
return message('Parameter show=' .. args.show .. ' conflicts with round=' .. args.round)
end
else
parms.show = translate_parameters.show[round]
end
end
parms.round = true
end
return date_difference(parms)
end
return {
age_generic = age_generic, -- can emulate several age templates
gsd = ymd_to_gsd, -- Template:Gregorian_serial_date
JULIANDAY = ymd_to_jd, -- Template:JULIANDAY
time_interval = time_interval, -- Template:Time_interval
ymd_from_jd = ymd_from_jd, -- Template:?
ymd_to_jd = ymd_to_jd, -- Template:JULIANDAY (alias)
}