Модул:Age — разлика између измена

Iz Vojne Enciklopedije
Пређи на навигацију Пређи на претрагу
(+)
 
м (1 измена увезена)
Ред 1: Ред 1:
-- Implement various "age of" and other date-related templates.
-- Implement various "age of" and other date-related templates.


local _Date, _currentDate
local _Date, _current_date
local function getExports(frame)
local function get_exports(frame)
-- Return objects exported from the date module or its sandbox.
-- Return objects exported from the date module or its sandbox.
if not _Date then
if not _Date then
Ред 8: Ред 8:
local datemod = require('Module:Date' .. sandbox)
local datemod = require('Module:Date' .. sandbox)
_Date = datemod._Date
_Date = datemod._Date
_currentDate = datemod._current
_current_date = datemod._current
end
end
return _Date, _currentDate
return _Date, _current_date
end
end


local Collection  -- a table to hold items
local function collection()
Collection = {
-- Return a table to hold items.
add = function (self, item)
return {
if item ~= nil then
n = 0,
add = function (self, item)
self.n = self.n + 1
self.n = self.n + 1
self[self.n] = item
self[self.n] = item
end
end,
end,
join = function (self, sep)
join = function (self, sep)
return table.concat(self, sep)
return table.concat(self, sep)
end,
end,
}
remove = function (self, pos)
end
if self.n > 0 and (pos == nil or (0 < pos and pos <= self.n)) then
self.n = self.n - 1
return table.remove(self, pos)
end
end,
sort = function (self, comp)
table.sort(self, comp)
end,
new = function ()
return setmetatable({n = 0}, Collection)
end
}
Collection.__index = Collection


local function stripToNil(text)
local function strip_to_nil(text)
-- If text is a string, return its trimmed content, or nil if empty.
-- If text is a string, return its trimmed content, or nil if empty.
-- Otherwise return text (which may, for example, be nil).
-- Otherwise return text (which may, for example, be nil).
Ред 55: Ред 43:
end
end


local function message(msg, id)
local function message(msg, nocat)
-- Return formatted message text for an error or warning.
-- Return formatted message text for an error.
local categories = {
-- Can append "#FormattingError" to URL of a page with a problem to find it.
error = '[[Категорија:Грешке у годинама]]',
local anchor = '<span id="FormattingError"></span>'
warning = '[[Категорија:Грешке у годинама]]',  -- same as error until determine whether 'Age warning' would be worthwhile
local category
}
if not nocat and mw.title.getCurrentTitle():inNamespaces(0, 10) then
local a, b, category
-- Category only in namespaces: 0=article, 10=template.
if id == 'warning' then
category = '[[Category:Age error]]'
a = '<sup>[<i>'
b = '</i>]</sup>'
else
else
a = '<strong class="error">Грешка: '
category = ''
b = '</strong>'
end
end
if mw.title.getCurrentTitle():inNamespaces(0) then
return anchor ..
-- Category only in namespaces: 0=article.
'<strong class="error">Greška: ' ..
category = categories[id or 'error']
end
return
a ..
mw.text.nowiki(msg) ..
mw.text.nowiki(msg) ..
b ..
'</strong>' ..
(category or '')
category
end
end


local function formatNumber(number)
local function formatnumber(number)
-- Return the given number formatted with commas as group separators,
-- Return the given number formatted with commas as group separators,
-- given that the number is an integer.
-- given that the number is an integer.
local numstr = tostring(number)
local numstr = tostring(number)
local length = #numstr
local length = #numstr
local places = Collection.new()
local places = collection()
local pos = 0
local pos = 0
repeat
repeat
Ред 92: Ред 73:
until pos >= length
until pos >= length
places:add(length)
places:add(length)
local groups = Collection.new()
local groups = collection()
for i = places.n, 2, -1 do
for i = places.n, 2, -1 do
local p1 = length - places[i] + 1
local p1 = length - places[i] + 1
Ред 98: Ред 79:
groups:add(numstr:sub(p1, p2))
groups:add(numstr:sub(p1, p2))
end
end
return groups:join(',')
return groups:join('.')
end
 
local function spellNumber(number, options, i)
-- Return result of spelling number, or
-- return number (as a string) if cannot spell it.
-- i == 1 for the first number which can optionally start with an uppercase letter.
number = tostring(number)
return require('Module:ConvertNumeric').spell_number(
number,
nil,                      -- fraction numerator
nil,                      -- fraction denominator
i == 1 and options.upper,  -- true: 'One' instead of 'one'
not options.us,            -- true: use 'and' between tens/ones etc
options.adj,              -- true: hyphenated
options.ordinal            -- true: 'first' instead of 'one'
) or number
end
 
local function makeExtra(args, flagCurrent)
-- Return extra text that will be inserted before the visible result
-- but after any sort key.
local extra = args.prefix or ''
if mw.ustring.len(extra) > 1 then
-- Parameter "~" gives "~3" whereas "over" gives "over 3".
extra = extra .. ' '
end
if flagCurrent then
extra = '<span class="currentage"></span>' .. extra
end
return extra
end
end


local function makeSort(value, sortable)
local function make_sort(value, sortable)
-- Return a sort key if requested.
-- Return a sort key in a span if specified.
-- Assume value is a valid number which has not overflowed.
-- Assume value is a valid number which has not overflowed.
if sortable == 'sortable_table' or sortable == 'sortable_on' or sortable == 'sortable_debug' then
if sortable == 'sortable_on' or sortable == 'sortable_debug' then
local sortKey
local sortkey
if value == 0 then
if value == 0 then
sortKey = '5000000000000000000'
sortkey = '5000000000000000000'
else
else
local mag = math.floor(math.log10(math.abs(value)) + 1e-14)
local mag = math.floor(math.log10(math.abs(value)) + 1e-14)
local prefix
if value > 0 then
if value > 0 then
sortKey = 7000 + mag
prefix = 7000 + mag
else
else
sortKey = 2999 - mag
prefix = 2999 - mag
value = value + 10^(mag+1)
value = value + 10^(mag+1)
end
end
sortKey = string.format('%d', sortKey) .. string.format('%015.0f', math.floor(value * 10^(14-mag)))
sortkey = string.format('%d', prefix) .. string.format('%015.0f', math.floor(value * 10^(14-mag)))
end
end
local lhs, rhs
local lhs = sortable == 'sortable_debug' and
if sortable == 'sortable_table' then
'<span style="border:1px solid;display:inline;" class="sortkey">' or
lhs = 'data-sort-value="'
'<span style="display:none" class="sortkey">'
rhs = '"|'
return lhs .. sortkey .. '♠</span>'
else
lhs = sortable == 'sortable_debug' and
'<span style="border:1px solid;display:inline;" class="sortkey">' or
'<span style="display:none" class="sortkey">'
rhs = '♠</span>'
end
return lhs .. sortKey .. rhs
end
end
end
end


local translateParameters = {
local translate_parameters = {
abbr = {
abbr = {
off = 'abbr_off',
off = 'abbr_off',
Ред 196: Ред 141:
ymw = { 'y', 'm', 'w', id = 'ymw' },
ymw = { 'y', 'm', 'w', id = 'ymw' },
ymwd = { 'y', 'm', 'w', 'd', id = 'ymwd' },
ymwd = { 'y', 'm', 'w', 'd', id = 'ymwd' },
yd = { 'y', 'd', id = 'yd', keepZero = true },
yd = { 'y', 'd', id = 'yd', keepzero = true },
m = { 'm', id = 'm' },
m = { 'm', id = 'm' },
md = { 'm', 'd', id = 'md' },
md = { 'm', 'd', id = 'md' },
Ред 216: Ред 161:
off = false,
off = false,
on = 'sortable_on',
on = 'sortable_on',
table = 'sortable_table',
debug = 'sortable_debug',
debug = 'sortable_debug',
},
},
}
}


local spellOptions = {
local function date_extract(frame)
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(frame)
-- Return part of a date after performing an optional operation.
-- Return part of a date after performing an optional operation.
local Date = getExports(frame)
local Date = get_exports(frame)
local args = frame:getParent().args
local args = frame:getParent().args
local parms = {}
local parms = {}
Ред 246: Ред 179:
table.insert(parms, 'partial')
table.insert(parms, 'partial')
end
end
local show = stripToNil(args.show) or 'dmy'
local date = Date(unpack(parms))
local date = Date(unpack(parms))
if not date then
if not date then
if show == 'format' then
return message('Potreban validan datum')
return 'error'
end
return message('Потребан валидан датум')
end
end
local add = stripToNil(args.add)
local add = strip_to_nil(args.add)
if add then
if add then
for item in add:gmatch('%S+') do
for item in add:gmatch('%S+') do
date = date + item
date = date + item
if not date then
if not date then
return message('Није могуће додати „' .. item .. '')
return message('Ne može se sabrati "' .. item .. '"')
end
end
end
end
end
end
local sortKey, result
local prefix, result
local sortable = translateParameters.sortable[args.sortable]
local sortable = translate_parameters.sortable[args.sortable]
if sortable then
if sortable then
local value = (date.partial and date.partial.first or date).jdz
local value = (date.partial and date.partial.first or date).jdz
sortKey = makeSort(value, sortable)
prefix = make_sort(value, sortable)
end
end
local show = strip_to_nil(args.show) or 'dmy'
if show ~= 'hide' then
if show ~= 'hide' then
result = date[show]
result = date[show]
Ред 279: Ред 209:
end
end
end
end
return (sortKey or '') .. (result or '')
return (prefix or '') .. (result or '')
end
end


local function rangeJoin(range)
local function make_text(values, components, names, options)
-- Return text to be used between a range of ages.
return range == 'dash' and '—' or '&nbsp;или '
end
 
local function makeText(values, components, names, options, noUpper)
-- Return wikitext representing an age or duration.
-- Return wikitext representing an age or duration.
local text = Collection.new()
local text = collection()
local count = #values
local count = #values
local sep = names.sep or ''
local sep = names.sep or ''
Ред 295: Ред 220:
-- v is a number (say 4 for 4 years), or a table ({4,5} for 4 or 5 years).
-- v is a number (say 4 for 4 years), or a table ({4,5} for 4 or 5 years).
local islist = type(v) == 'table'
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
if (islist or v > 0) or (text.n == 0 and i == count) or (text.n > 0 and components.keepzero) then
local fmt, vstr
local fmt, vstr
if options.spell then
if i == 1 and options.format == 'format_commas' 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.
-- Numbers after the first should be small and not need formatting.
fmt = formatNumber
fmt = formatnumber
else
else
fmt = tostring
fmt = tostring
end
end
if islist then
if islist then
vstr = fmt(v[1]) .. rangeJoin(options.range)
local join = options.range == 'dash' and '—' or '&nbsp;ili '
noUpper = true
vstr = fmt(v[1]) .. join .. fmt(v[2])
vstr = vstr .. fmt(v[2])
else
else
vstr = fmt(v)
vstr = fmt(v)
Ред 335: Ред 255:
elseif options.join == 'sep_serialcomma' and text.n > 2 then
elseif options.join == 'sep_serialcomma' and text.n > 2 then
first = ', '
first = ', '
last = ' и '
last = ' i '
else
else
first = ', '
first = ', '
last = ' и '
last = ' i '
end
end
for i, v in ipairs(text) do
for i, v in ipairs(text) do
Ред 357: Ред 277:
end
end
return
return
(options.sortKey or '') ..
(options.prefix or '') ..
(options.extra or '') ..
sign ..
sign ..
text:join() ..
text:join() ..
Ред 364: Ред 283:
end
end


local function dateDifference(parms)
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.
Ред 371: Ред 290:
plural = '',
plural = '',
sep = '&nbsp;',
sep = '&nbsp;',
y = 'година',
y = 'godina',
m = 'месеци',
m = 'meseci',
w = 'недеља',
w = 'nedelja',
d = 'дана',
d = 'dana',
H = 'часова',
H = 'časova',
M = 'минута',
M = 'minuta',
S = 'секунди',
S = 'sekundi',
},
},
abbr_on = {
abbr_on = {
y = 'г',
y = 'y',
m = 'м',
m = 'm',
w = 'н',
w = 'w',
d = 'д',
d = 'd',
H = 'ч',
H = 'h',
M = 'м',
M = 'm',
S = 'с',
S = 's',
},
},
abbr_infant = {      -- for {{age for infant}}
abbr_infant = {      -- for {{age for infant}}
plural = '',
plural = 's',
sep = '&nbsp;',
sep = '&nbsp;',
y = 'год',
y = 'g',
m = 'мес',
m = 'm',
w = 'нед',
w = 'n',
d = 'дан',
d = 'd',
H = 'час',
H = 'č',
M = 'мин',
M = 'm',
S = 'сек',
S = 's',
},
},
abbr_raw = {},
abbr_raw = {},
Ред 404: Ред 323:
local show = parms.show  -- may be nil; default is set below
local show = parms.show  -- may be nil; default is set below
local abbr = parms.abbr or 'abbr_off'
local abbr = parms.abbr or 'abbr_off'
local defaultJoin
local default_join
if abbr ~= 'abbr_off' then
if abbr ~= 'abbr_off' then
defaultJoin = 'sep_space'
default_join = 'sep_space'
end
end
if not show then
if not show then
Ред 412: Ред 331:
if parms.disp == 'disp_age' then
if parms.disp == 'disp_age' then
if diff.years < 3 then
if diff.years < 3 then
defaultJoin = 'sep_space'
default_join = 'sep_space'
if diff.years >= 1 then
if diff.years >= 1 then
show = 'ym'
show = 'ym'
Ред 424: Ред 343:
end
end
if type(show) ~= 'table' then
if type(show) ~= 'table' then
show = translateParameters.show[show]
show = translate_parameters.show[show]
end
end
if parms.disp == 'disp_raw' then
if parms.disp == 'disp_raw' then
defaultJoin = 'sep_space'
default_join = 'sep_space'
abbr = 'abbr_raw'
abbr = 'abbr_raw'
elseif parms.wantSc then
elseif parms.want_sc then
defaultJoin = 'sep_serialcomma'
default_join = 'sep_serialcomma'
end
end
local diffOptions = {
local diff_options = {
round = parms.round,
round = parms.round,
duration = parms.wantDuration,
duration = parms.want_duration,
range = parms.range and true or nil,
range = parms.range and true or nil,
}
}
local sortKey
local prefix
if parms.sortable then
if parms.sortable then
local value = diff.age_days + (parms.wantDuration and 1 or 0)  -- days and fraction of a day
local value = diff.age_days + (parms.want_duration and 1 or 0)  -- days and fraction of a day
if diff.isnegative then
if diff.isnegative then
value = -value
value = -value
end
end
sortKey = makeSort(value, parms.sortable)
prefix = make_sort(value, parms.sortable)
end
end
local textOptions = {
local text_options = {
extra = parms.extra,
prefix = prefix,
suffix = parms.suffix, -- not currently used
format = parms.format,
format = parms.format,
join = parms.sep or defaultJoin,
join = parms.sep or default_join,
isnegative = diff.isnegative,
isnegative = diff.isnegative,
range = parms.range,
range = parms.range,
sortKey = sortKey,
spell = parms.spell,
suffix = parms.suffix,  -- not currently used
}
}
if show.id == 'hide' then
if show.id == 'hide' then
return sortKey or ''
return prefix or ''
end
end
local values = { diff:age(show.id, diffOptions) }
local values = { diff:age(show.id, diff_options) }
if values[1] then
if values[1] then
return makeText(values, show, names[abbr], textOptions)
return make_text(values, show, names[abbr], text_options)
end
end
if diff.partial then
return message('Parametar show=' .. show.id .. ' ovde nije podržan')
-- Handle a more complex range such as
-- {{age_yd|20 Dec 2001|2003|range=yes}} → 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('Параметар show=' .. show.id .. ' овде није подржан')
end
end


local function getDates(frame, getopt)
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)
-- * date1, date2 (two date tables, if not single)
-- * date1, date2 (two date tables, if not single)
-- * text        (a string error message)
-- * text        (a string error message)
-- A missing date is optionally replaced with the current date.
-- A missing date is replaced with the current date.
-- If wantMixture is true, a missing date component is replaced
-- If want_mixture is true, a missing date component is replaced
-- 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.
-- Some results may be placed in table getopt.
local Date, currentDate = getExports(frame)
local Date, current_date = get_exports(frame)
getopt = getopt or {}
getopt = getopt or {}
local function flagCurrent(text)
local fix = getopt.fix and 'fix' or ''
-- This allows the calling template to detect if the current date has been used,
local partial = getopt.range and 'partial' or ''
-- that is, whether both dates have been entered in a template expecting two.
-- For example, an infobox may want the age when an event occurred, not the current age.
-- Don't bother detecting if wantMixture is used because not needed and it is a poor option.
if not text then
if getopt.noMissing then
return nil  -- this gives a nil date which gives an error
end
text = 'currentdate'
if getopt.flag == 'usesCurrent' then
getopt.usesCurrent = true
end
end
return text
end
local args = frame:getParent().args
local args = frame:getParent().args
local fields = {}
local fields = {}
local isNamed = args.year or args.year1 or args.year2 or
local is_named = args.year or args.year1 or args.year2 or
args.month or args.month1 or args.month2 or
args.month or args.month1 or args.month2 or
args.day or args.day1 or args.day2
args.day or args.day1 or args.day2
if isNamed then
if is_named then
fields[1] = args.year1 or args.year
fields[1] = args.year1 or args.year
fields[2] = args.month1 or args.month
fields[2] = args.month1 or args.month
Ред 528: Ред 415:
local imax = 0
local imax = 0
for i = 1, 6 do
for i = 1, 6 do
fields[i] = stripToNil(fields[i])
fields[i] = strip_to_nil(fields[i])
if fields[i] then
if fields[i] then
imax = i
imax = i
end
if getopt.omitZero and i % 3 ~= 1 then  -- omit zero months and days as unknown values but keep year 0 which is 1 BCE
if tonumber(fields[i]) == 0 then
fields[i] = nil
getopt.partial = true
end
end
end
end
end
local fix = getopt.fix and 'fix' or ''
local single = getopt.single
local partialText = getopt.partial and 'partial' or ''
local dates = {}
local dates = {}
if isNamed or imax >= 3 then
if is_named or imax > 2 then
local nrDates = getopt.single and 1 or 2
local nr_dates = single and 1 or 2
if getopt.wantMixture then
if getopt.want_mixture then
-- Cannot be partial since empty fields are set from current.
-- Cannot be partial since empty fields are set from current.
local components = { 'year', 'month', 'day' }
local components = { 'year', 'month', 'day' }
for i = 1, nrDates * 3 do
for i = 1, nr_dates * 3 do
fields[i] = fields[i] or currentDate[components[i > 3 and i - 3 or i]]
fields[i] = fields[i] or current_date[components[i > 3 and i - 3 or i]]
end
end
for i = 1, nrDates do
for i = 1, nr_dates do
local index = i == 1 and 1 or 4
local index = i == 1 and 1 or 4
dates[i] = Date(fields[index], fields[index+1], fields[index+2])
dates[i] = Date(fields[index], fields[index+1], fields[index+2])
end
end
else
else
-- If partial dates are allowed, accept
for i = 1, nr_dates do
--    year only, or
--    year and month only
-- Do not accept year and day without a month because that makes no sense
-- (and because, for example, Date('partial', 2001, nil, 12) sets day = nil, not 12).
for i = 1, nrDates do
local index = i == 1 and 1 or 4
local index = i == 1 and 1 or 4
local y, m, d = fields[index], fields[index+1], fields[index+2]
local y, m, d = fields[index], fields[index+1], fields[index+2]
if (getopt.partial and y and (m or not d)) or (y and m and d) then
if (partial and y) or (y and m and d) then
dates[i] = Date(fix, partialText, y, m, d)
dates[i] = Date(fix, partial, y, m, d)
elseif not y and not m and not d then
elseif not (y or m or d) then
dates[i] = Date(flagCurrent())
dates[i] = Date('currentdate')
end
end
end
end
end
end
else
else
getopt.textdates = true -- have parsed each date from a single text field
getopt.textdates = true
dates[1] = Date(fix, partialText, flagCurrent(fields[1]))
dates[1] = Date(fix, partial, fields[1] or 'currentdate')
if not getopt.single then
if not single then
dates[2] = Date(fix, partialText, flagCurrent(fields[2]))
dates[2] = Date(fix, partial, fields[2] or 'currentdate')
end
end
end
end
if not dates[1] then
if not dates[1] then
return message(getopt.missing1 or 'Потребни валидни година, месец, дан')
return message('Potrebna validna godina, mesec, dan')
end
end
if getopt.single then
if single then
return dates[1]
return dates[1]
end
end
if not dates[2] then
if not dates[2] then
return message(getopt.missing2 or 'Други датум треба да буде година, месец, дан')
return message('Drugi datum treba da bude godina, mesec, dan')
end
end
return dates[1], dates[2]
return dates[1], dates[2]
end
end


local function ageGeneric(frame)
local function age_generic(frame)
-- Return the result required by the specified template.
-- Return the result required by the specified template.
-- Can use sortable=x where x = on/table/off/debug in any supported template.
-- Can use sortable=x where x = on/off/debug in any supported template.
-- Some templates default to sortable=on but can be overridden.
-- Some templates default to sortable=on but can be overridden with sortable=off.
local name = frame.args.template
local name = frame.args.template
if not name then
if not name then
return message('Шаблон који ово позива мора имати "|template=x" где је x жељена операција')
return message('Šablon koji ovo poziva mora imati "|template=x" gde je x željena operacija')
end
end
local args = frame:getParent().args
local args = frame:getParent().args
Ред 624: Ред 499:
show = 'y',
show = 'y',
abbr = 'abbr_raw',
abbr = 'abbr_raw',
flag = 'usesCurrent',
omitZero = true,
range = 'no',
},
},
age_full_years_nts = {      -- {{age nts}}
age_full_years_nts = {      -- {{age nts}}
Ред 668: Ред 540:
age_yd = {                  -- {{age in years and days}}
age_yd = {                  -- {{age in years and days}}
show = 'yd',
show = 'yd',
format = 'format_commas',
sep = args.sep ~= 'and' and 'sep_comma' or nil,
},
age_yd_nts = {              -- {{age in years and days nts}}
show = 'yd',
format = 'format_commas',
sep = args.sep ~= 'and' and 'sep_comma' or nil,
sep = args.sep ~= 'and' and 'sep_comma' or nil,
sortable = 'on',
sortable = 'on',
Ред 687: Ред 553:
age_ymwd = {                -- {{age in years, months, weeks and days}}
age_ymwd = {                -- {{age in years, months, weeks and days}}
show = 'ymwd',
show = 'ymwd',
wantMixture = true,
want_mixture = true,
},
},
}
}
local spec = specs[name]
local spec = specs[name]
if not spec then
if not spec then
return message('Одабрани шаблон није валидан')
return message('Odabrani šablon nije validan')
end
end
if name == 'age_days' then
if name == 'age_days' then
local su = stripToNil(args['show unit'])
local su = strip_to_nil(args['show unit'])
if su then
if su then
if su == 'abbr' or su == 'full' then
if su == 'abbr' or su == 'full' then
Ред 703: Ред 569:
end
end
end
end
local partial, autofill
local range = spec.range or yes(args.range) or (args.range == 'dash' and 'dash' or nil)
local range = stripToNil(args.range) or spec.range
if range then
-- Suppose partial dates are used and age could be 11 or 12 years.
-- "|range=" (empty value) has no effect (spec is used).
-- "|range=yes" or spec.range == true sets range = true (gives "11 or 12")
-- "|range=dash" or spec.range == 'dash' sets range = 'dash' (gives "11–12").
-- "|range=no" or spec.range == 'no' sets range = nil and fills each date in the diff (gives "12").
--    ("on" is equivalent to "yes", and "off" is equivalent to "no").
-- "|range=OTHER" sets range = nil and rejects partial dates.
range = ({ dash = 'dash', off = 'no', no = 'no', [true] = true })[range] or yes(range)
if range then
partial = true  -- accept partial dates with a possible age range for the result
if range == 'no' then
autofill = true  -- missing month/day in first or second date are filled from other date or 1
range = nil
end
end
end
local getopt = {
local getopt = {
fix = yes(args.fix),
fix = yes(args.fix),
flag = stripToNil(args.flag) or spec.flag,
range = range,
omitZero = spec.omitZero,
want_mixture = spec.want_mixture,
partial = partial,
wantMixture = spec.wantMixture,
}
}
local date1, date2 = getDates(frame, getopt)
local date1, date2 = get_dates(frame, getopt)
if type(date1) == 'string' then
if type(date1) == 'string' then
return date1
return date1
end
end
local format = stripToNil(args.format)
local format = strip_to_nil(args.format)
local spell = spellOptions[format]
if format then
if format then
format = 'format_' .. format
format = 'format_' .. format
Ред 741: Ред 586:
end
end
local parms = {
local parms = {
diff = date2:subtract(date1, { fill = autofill }),
diff = date2 - date1,
wantDuration = spec.duration or yes(args.duration),
want_duration = spec.duration or yes(args.duration),
range = range,
range = range,
wantSc = yes(args.sc),
want_sc = yes(args.sc),
show = args.show == 'hide' and 'hide' or spec.show,
show = args.show == 'hide' and 'hide' or spec.show,
abbr = spec.abbr,
abbr = spec.abbr,
disp = spec.disp,
disp = spec.disp,
extra = makeExtra(args, getopt.usesCurrent and format ~= 'format_raw'),
format = format or spec.format,
format = format or spec.format,
round = yes(args.round),
round = yes(args.round),
sep = spec.sep,
sep = spec.sep,
sortable = translateParameters.sortable[args.sortable or spec.sortable],
sortable = translate_parameters.sortable[args.sortable or spec.sortable],
spell = spell,
}
}
if (spec.negative or frame.args.negative) == 'error' and parms.diff.isnegative then
if (spec.negative or frame.args.negative) == 'error' and parms.diff.isnegative then
return message('Други датум не сме да буде пре првог датума')
return message('Drugi datum ne sme da bude pre prvog datuma')
end
return dateDifference(parms)
end
 
local function bda(frame)
-- Implement [[Template:Birth date and age]].
local args = frame:getParent().args
local options = {
missing1 = 'Потребан валидан датум рођења: година, месец, дан',
noMissing = true,
single = true,
}
local date = getDates(frame, options)
if type(date) == 'string' then
return date  -- error text
end
local Date = getExports(frame)
local diff = Date('currentdate') - date
if diff.isnegative or diff.years > 150 then
return message('Невалидан датум рођења за израчунавање старости')
end
local disp, show = 'disp_raw', 'y'
if diff.years < 2 then
disp = 'disp_age'
if diff.years == 0 and diff.months == 0 then
show = 'd'
else
show = 'm'
end
end
local df = stripToNil(args.df)  -- day first (dmy); default is month first (mdy)
local result = df and
'%-d %B %-Y' or
'%-d. %B %-Y.'
result = '(<span class="bday">%-Y-%m-%d</span>) </span>' .. result
result = '<span style="display:none"> ' ..
date:text(result) ..
'<span class="noprint ForceAgeToShow"> ' ..
'(' ..
dateDifference({
diff = diff,
show = show,
abbr = 'abbr_off',
disp = disp,
sep = 'sep_space',
}) ..
'&nbsp;год.)</span>'
local warnings = tonumber(frame.args.warnings)
if warnings and warnings > 0 then
local good = {
df = true,
mf = true,
day = true,
day1 = true,
month = true,
month1 = true,
year = true,
year1 = true,
}
local invalid
local imax = options.textdates and 1 or 3
for k, _ in pairs(args) do
if type(k) == 'number' then
if k > imax then
invalid = tostring(k)
break
end
else
if not good[k] then
invalid = k
break
end
end
end
if invalid then
result = result .. message('невалидан параметар ' .. invalid, 'упозорење')
end
end
return result
end
 
local function dda(frame)
-- Implement [[Template:Death date and age]].
local args = frame:getParent().args
local options = {
missing1 = 'Потребан валидан датум смрти (први датум): година, месец, дан',
missing2 = 'Потребан валидан датум рођења (други датум): година, месец, дан',
noMissing = true,
partial = true,
}
local date1, date2 = getDates(frame, options)
if type(date1) == 'string' then
return date1
end
local diff = date1 - date2
if diff.isnegative then
return message('Датум смрти (први датум) мора да се унесе после датума рођења (други датум)')
end
local years
if diff.partial then
years = diff.partial.years
years = type(years) == 'table' and years[2] or years
else
years = diff.years
end
if years > 150 then
return message('Невалидни датуми за израчунавање старости')
end
local df = stripToNil(args.df)  -- day first (dmy); default is month first (mdy)
local result
if date1.day then  -- y, m, d known
result = (df and
'%-d %B %-Y' or
'%-d. %B %-Y.') ..
'<span style="display:none">(%-Y-%m-%d)</span>'
elseif date1.month then  -- y, m known; d unknown
result =
'%B %-Y' ..
'<span style="display:none">(%-Y-%m-00)</span>'
else  -- y known; m, d unknown
result =
'%-Y' ..
'<span style="display:none">(%-Y-00-00)</span>'
end
result = date1:text(result) ..
' (' ..
dateDifference({
diff = diff,
show = 'y',
abbr = 'abbr_off',
disp = 'disp_raw',
range = 'dash',
sep = 'sep_space',
}) ..
'&nbsp;год.)'
local warnings = tonumber(frame.args.warnings)
if warnings and warnings > 0 then
local good = {
df = true,
mf = true,
}
local invalid
local imax = options.textdates and 2 or 6
for k, _ in pairs(args) do
if type(k) == 'number' then
if k > imax then
invalid = tostring(k)
break
end
else
if not good[k] then
invalid = k
break
end
end
end
if invalid then
result = result .. message('невалидан параметар ' .. invalid, 'упозорење')
end
end
end
return result
return date_difference(parms)
end
end


local function dateToGsd(frame)
local function date_to_gsd(frame)
-- Implement [[Template:Gregorian serial date]].
-- This implements {{gregorian serial date}}.
-- Return Gregorian serial date of the given date, or the current 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
-- 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 = getDates(frame, { wantMixture=true, single=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
Ред 932: Ред 616:
end
end


local function jdToDate(frame)
local function jd_to_date(frame)
-- Return formatted date from a Julian date.
-- Return formatted date from a Julian date.
-- The result includes a time if the input includes a fraction.
-- The result includes a time if the input includes a fraction.
-- The word 'Julian' is accepted for the Julian calendar.
-- The word 'Julian' is accepted for the Julian calendar.
local Date = getExports(frame)
local Date = get_exports(frame)
local args = frame:getParent().args
local args = frame:getParent().args
local date = Date('juliandate', args[1], args[2])
local date = Date('juliandate', args[1], args[2])
Ред 942: Ред 626:
return date:text()
return date:text()
end
end
return message('Потребан валидан јулијански датум')
return message('Potreban validan broj za datum iz julijanskog kalendara')
end
end


local function dateToJd(frame)
local function date_to_jd(frame)
-- Return Julian date (a number) from a date which may include a time,
-- Return Julian date (a number) from a date which may include a time,
-- or the current date ('currentdate') or current date and time ('currentdatetime').
-- or the current date ('currentdate') or current date and time ('currentdatetime').
-- The word 'Julian' is accepted for the Julian calendar.
-- The word 'Julian' is accepted for the Julian calendar.
local Date = getExports(frame)
local Date = get_exports(frame)
local args = frame:getParent().args
local args = frame:getParent().args
local date = Date(args[1], args[2], args[3], args[4], args[5], args[6], args[7])
local date = Date(args[1], args[2], args[3], args[4], args[5], args[6], args[7])
Ред 955: Ред 639:
return tostring(date.jd)
return tostring(date.jd)
end
end
return message('Потребна валидна година/месец/дан или „currentdate”')
return message('Potrebna validna godina/mesec/dan ili „currentdate”')
end
end


local function timeInterval(frame)
local function time_interval(frame)
-- Implement [[Template:Time interval]].
-- This implements {{time interval}}.
-- There are two positional arguments: date1, date2.
-- There are two positional arguments: date1, date2.
-- The default for each is the current date and time.
-- The default for each is the current date and time.
-- Result is date2 - date1 formatted.
-- Result is date2 - date1 formatted.
local Date = getExports(frame)
local Date = get_exports(frame)
local args = frame:getParent().args
local args = frame:getParent().args
local parms = {
local parms = {
extra = makeExtra(args),
want_duration = yes(args.duration),
wantDuration = yes(args.duration),
range = yes(args.range) or (args.range == 'dash' and 'dash' or nil),
range = yes(args.range) or (args.range == 'dash' and 'dash' or nil),
wantSc = yes(args.sc),
want_sc = yes(args.sc),
}
}
local fix = yes(args.fix) and 'fix' or ''
local fix = yes(args.fix) and 'fix' or ''
local date1 = Date(fix, 'partial', stripToNil(args[1]) or 'currentdatetime')
local date1 = Date(fix, 'partial', strip_to_nil(args[1]) or 'currentdatetime')
if not date1 then
if not date1 then
return message('Невалидан почетни датум у првом параметру')
return message('Nevalidan početni datum u prvom parametru')
end
end
local date2 = Date(fix, 'partial', stripToNil(args[2]) or 'currentdatetime')
local date2 = Date(fix, 'partial', strip_to_nil(args[2]) or 'currentdatetime')
if not date2 then
if not date2 then
return message('Невалидан крајњи датум у првом параметру')
return message('Nevalidan krajnji datum u drugom parametru')
end
end
parms.diff = date2 - date1
parms.diff = date2 - date1
for argname, translate in pairs(translateParameters) do
for argname, translate in pairs(translate_parameters) do
local parm = stripToNil(args[argname])
local parm = strip_to_nil(args[argname])
if parm then
if parm then
parm = translate[parm]
parm = translate[parm]
if parm == nil then  -- test for nil because false is a valid setting
if parm == nil then  -- test for nil because false is a valid setting
return message('Параметар ' .. argname .. '=' .. args[argname] .. ' није валидан')
return message('Parametar ' .. argname .. '=' .. args[argname] .. ' nije validan')
end
end
parms[argname] = parm
parms[argname] = parm
Ред 997: Ред 680:
if show then
if show then
if show.id ~= round then
if show.id ~= round then
return message('Параметар show=' .. args.show .. ' у сукобу са round=' .. args.round)
return message('Parametar show=' .. args.show .. ' u sukobu sa round=' .. args.round)
end
end
else
else
parms.show = translateParameters.show[round]
parms.show = translate_parameters.show[round]
end
end
end
end
parms.round = true
parms.round = true
end
end
return dateDifference(parms)
return date_difference(parms)
end
end


return {
return {
age_generic = ageGeneric,           -- can emulate several age templates
age_generic = age_generic,         -- can emulate several age templates
birth_date_and_age = bda,          -- Template:Birth_date_and_age
gsd = date_to_gsd,                 -- Template:Gregorian_serial_date
death_date_and_age = dda,          -- Template:Death_date_and_age
extract = date_extract,           -- Template:Extract
gsd = dateToGsd,                   -- Template:Gregorian_serial_date
jd_to_date = jd_to_date,           -- Template:?
extract = dateExtract,             -- Template:Extract
JULIANDAY = date_to_jd,           -- Template:JULIANDAY
jd_to_date = jdToDate,             -- Template:?
time_interval = time_interval,     -- Template:Time_interval
JULIANDAY = dateToJd,               -- Template:JULIANDAY
time_interval = timeInterval,       -- Template:Time_interval
}
}

Верзија на датум 1. новембар 2020. у 13:38

Templates supported

Module:Age implements the following templates:

Template Required wikitext
{{extract}} {{#invoke:age|extract}}
{{gregorian serial date}} {{#invoke:age|gsd}}
{{JULIANDAY}} {{#invoke:age|JULIANDAY}}
{{time interval}} {{#invoke:age|time_interval}}
{{age in days}} {{#invoke:age|age_generic|template=age_days}}
{{age in days nts}} {{#invoke:age|age_generic|template=age_days_nts}}
{{duration in days}} {{#invoke:age|age_generic|template=duration_days}}
{{duration in days nts}} {{#invoke:age|age_generic|template=duration_days_nts}}
{{age}} {{#invoke:age|age_generic|template=age_full_years}}
{{age nts}} {{#invoke:age|age_generic|template=age_full_years_nts}}
{{age in years}} {{#invoke:age|age_generic|template=age_in_years}}
{{age in years nts}} {{#invoke:age|age_generic|template=age_in_years_nts}}
{{age for infant}} {{#invoke:age|age_generic|template=age_infant}}
{{age in months}} {{#invoke:age|age_generic|template=age_m}}
{{age in weeks}} {{#invoke:age|age_generic|template=age_w}}
{{age in weeks and days}} {{#invoke:age|age_generic|template=age_wd}}
{{age in years and days}} {{#invoke:age|age_generic|template=age_yd}}
{{age in years and months}} {{#invoke:age|age_generic|template=age_ym}}
{{age in years, months and days}} {{#invoke:age|age_generic|template=age_ymd}}
{{age in years, months, weeks and days}} {{#invoke:age|age_generic|template=age_ymwd}}

The age templates expect the older date to be first. The implementations of age_in_years and age_in_years_nts display an error message if that is not the case. If similar checking is wanted for other templates, negative=error can be added to the invoke. For example, {{age}} might use:

  • {{#invoke:age|age_generic|template=age_full_years|negative=error}}

If negative=error does not apply, a negative difference is indicated with a minus sign (−).

Date formats

Dates can use numbered or named parameters to specify year/month/day. Alternatively, a full date can be entered in a variety of formats. For example:

  • {{age in years and months|year1=2001|month1=1|day1=10|year2=2012|month2=2|day2=20}} → 11 godina, 1 meseci
  • {{age in years and months|year=2001|month=1|day=10|year2=2012|month2=2|day2=20}} → 11 godina, 1 meseci
  • {{age in years and months|2001|1|10|2012|2|20}} → 11 godina, 1 meseci
  • {{age in years and months|2001-1-10|2012-2-20}} → 11 godina, 1 meseci
  • {{age in years and months|10 Jan 2001|20 Feb 2012}} → 11 godina, 1 meseci
  • {{age in years and months|January 10, 2001|Feb 20, 2012}} → 11 godina, 1 meseci

If the first or second date is omitted, the current date is used. For example:

  • {{age in years and months|year2=2012|month2=2|day2=20}} → −13 godina, 11 meseci
  • {{age in years and months|year2=2012|month2=2|day2=20}} → −13 godina, 11 meseci
  • {{age in years and months||||2012|2|20}} → −13 godina, 11 meseci
  • {{age in years and months||2012-2-20}} → −13 godina, 11 meseci
  • {{age in years and months||20 Feb 2012}} → −13 godina, 11 meseci
  • {{age in years and months||Feb 20, 2012}} → −13 godina, 11 meseci
  • {{age in years and months|year1=2001|month1=1|day1=10}} → 25 godina
  • {{age in years and months|year=2001|month=1|day=10}} → 25 godina
  • {{age in years and months|2001|1|10}} → 25 godina
  • {{age in years and months|2001-1-10}} → 25 godina
  • {{age in years and months|10 Jan 2001}} → 25 godina
  • {{age in years and months|January 10, 2001}} → 25 godina

Parameters

The following options are available:

Parameter Description
duration=on The finishing date is included in the result; that adds one day to the age.
fix=on Adjust invalid time units. See Template:Extract#Fix.
format=commas A value of 1,000 or more is displayed with commas.
range=on Accept a year only, or a year and month only, and show a range of ages.
range=dash Accept a year or year/month, and show the range with a dash (–).
round=on The age is rounded to the nearest least-significant time unit.
sc=on A serial comma is used (only useful when three or more values are displayed).
sc=yes Same as sc=on.
show=hide The age is not displayed; may be useful with sortable=on.
sortable=on Insert a hidden sort key before the result (for use in sortable tables).
sortable=debug Same as sortable=on but the sort key is displayed for testing.
sortable=off No sort key (can override the default for a template like {{age nts}}).

Examples using the range parameter follow.

  • {{age in years and months|year=2001|month=1|year2=2012|month2=2|range=on}} → 11 godina, 0 ili 1 meseci
  • {{age in years and months|2001|1||2012|2|range=on}} → 11 godina, 0 ili 1 meseci
  • {{age in years and months|Jan 2001|Feb 2012|range=on}} → 11 godina, 0 ili 1 meseci
  • {{age in years and months|Jan 2001|Feb 2012|range=dash}} → 11 godina, 0—1 meseci

The sort key is based on the age in days, and fractions of a day if a time is specified.

  • {{age in years and months|10 Jan 2001|20 Feb 2012|sortable=debug}}7003405800000000000♠11 godina, 1 meseci
  • {{age in years and months|10 Jan 2001|6:00 am 20 Feb 2012|sortable=debug}}7003405825000000000♠11 godina, 1 meseci
  • {{age in years and months|10 Jan 2001|6:00 am 20 Feb 2012|sortable=debug|show=hide}}7003405825000000000♠

An extra day is added for a duration.

  • {{age in years and months|20 Jan 2001|19 Feb 2012}} → 11 godina (one day short of 11 years, 1 month)
  • {{age in years and months|20 Jan 2001|19 Feb 2012|duration=on}} → 11 godina, 1 meseci

The least-significant time unit can be rounded.

  • {{age in years and months|20 Jan 2001|10 Feb 2012}} → 11 godina
  • {{age in years and months|20 Jan 2001|10 Feb 2012|round=on}} → 11 godina, 1 meseci (round to nearest month)

Large numbers can be formatted with commas.

  • {{age in years and months|120|2012|format=commas|range=on}} → 1.891 ili 1.892 godina
  • {{age in years and months|120|2012|format=commas|range=dash}} → 1.891—1.892 godina

See also

  • {{time interval}} • This template supports all age/duration calculations and provides more options such as abbreviating or omitting units.

-- Implement various "age of" and other date-related templates.

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"></span>'
	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">Greška: ' ..
		mw.text.nowiki(msg) ..
		'</strong>' ..
		category
end

local function formatnumber(number)
	-- Return the given number formatted with commas as group separators,
	-- given that the number is an integer.
	local numstr = tostring(number)
	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_sort(value, sortable)
	-- Return a sort key in a span if specified.
	-- Assume value is a valid number which has not overflowed.
	if sortable == 'sortable_on' or sortable == 'sortable_debug' then
		local sortkey
		if value == 0 then
			sortkey = '5000000000000000000'
		else
			local mag = math.floor(math.log10(math.abs(value)) + 1e-14)
			local prefix
			if value > 0 then
				prefix = 7000 + mag
			else
				prefix = 2999 - mag
				value = value + 10^(mag+1)
			end
			sortkey = string.format('%d', prefix) .. string.format('%015.0f', math.floor(value * 10^(14-mag)))
		end
		local lhs = sortable == 'sortable_debug' and
			'<span style="border:1px solid;display:inline;" class="sortkey">' or
			'<span style="display:none" class="sortkey">'
		return lhs .. sortkey .. '♠</span>'
	end
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 = {
		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' },
		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',
		debug = 'sortable_debug',
	},
}

local function date_extract(frame)
	-- Return part of a date after performing an optional operation.
	local Date = get_exports(frame)
	local args = frame: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 date = Date(unpack(parms))
	if not date then
		return message('Potreban validan datum')
	end
	local add = strip_to_nil(args.add)
	if add then
		for item in add:gmatch('%S+') do
			date = date + item
			if not date then
				return message('Ne može se sabrati "' .. item .. '"')
			end
		end
	end
	local prefix, result
	local sortable = translate_parameters.sortable[args.sortable]
	if sortable then
		local value = (date.partial and date.partial.first or date).jdz
		prefix = make_sort(value, sortable)
	end
	local show = strip_to_nil(args.show) or 'dmy'
	if show ~= 'hide' then
		result = date[show]
		if result == nil then
			result = date:text(show)
		elseif type(result) == 'boolean' then
			result = result and '1' or '0'
		else
			result = tostring(result)
		end
	end
	return (prefix or '') .. (result or '')
end

local function make_text(values, components, names, options)
	-- Return wikitext representing an age or duration.
	local text = collection()
	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 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
				local join = options.range == 'dash' and '—' or '&nbsp;ili '
				vstr = fmt(v[1]) .. join .. fmt(v[2])
			else
				vstr = fmt(v)
			end
			local name = names[components[i]]
			if name then
				local plural = names.plural
				if not plural or (islist and v[2] or v) == 1 then
					plural = ''
				end
				text:add(vstr .. sep .. name .. 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 = ' i '
	else
		first = ', '
		last = ' i '
	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.prefix or '') ..
		sign ..
		text:join() ..
		(options.suffix or '')
end

local function date_difference(parms)
	-- Return a formatted date difference using the given parameters
	-- which have been validated.
	local names = {
		abbr_off = {
			plural = '',
			sep = '&nbsp;',
			y = 'godina',
			m = 'meseci',
			w = 'nedelja',
			d = 'dana',
			H = 'časova',
			M = 'minuta',
			S = 'sekundi',
		},
		abbr_on = {
			y = 'y',
			m = 'm',
			w = 'w',
			d = 'd',
			H = 'h',
			M = 'm',
			S = 's',
		},
		abbr_infant = {      -- for {{age for infant}}
			plural = 's',
			sep = '&nbsp;',
			y = 'g',
			m = 'm',
			w = 'n',
			d = 'd',
			H = 'č',
			M = 'm',
			S = 's',
		},
		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 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 diff_options = {
		round = parms.round,
		duration = parms.want_duration,
		range = parms.range and true or nil,
	}
	local prefix
	if parms.sortable then
		local value = diff.age_days + (parms.want_duration and 1 or 0)  -- days and fraction of a day
		if diff.isnegative then
			value = -value
		end
		prefix = make_sort(value, parms.sortable)
	end
	local text_options = {
		prefix = prefix,
		suffix = parms.suffix,  -- not currently used
		format = parms.format,
		join = parms.sep or default_join,
		isnegative = diff.isnegative,
		range = parms.range,
	}
	if show.id == 'hide' then
		return prefix or ''
	end
	local values = { diff:age(show.id, diff_options) }
	if values[1] then
		return make_text(values, show, names[abbr], text_options)
	end
	return message('Parametar show=' .. show.id .. ' ovde nije podržan')
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)
	getopt = getopt or {}
	local fix = getopt.fix and 'fix' or ''
	local partial = getopt.range and 'partial' or ''
	local args = frame:getParent().args
	local fields = {}
	local is_named = args.year or args.year1 or args.year2 or
		args.month or args.month1 or args.month2 or
		args.day or args.day1 or args.day2
	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
	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
			-- Cannot be partial since empty fields are set from current.
			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 (partial and y) or (y and m and d) then
					dates[i] = Date(fix, partial, 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(fix, partial, fields[1] or 'currentdate')
		if not single then
			dates[2] = Date(fix, partial, fields[2] or 'currentdate')
		end
	end
	if not dates[1] then
		return message('Potrebna validna godina, mesec, dan')
	end
	if single then
		return dates[1]
	end
	if not dates[2] then
		return message('Drugi datum treba da bude godina, mesec, dan')
	end
	return dates[1], dates[2]
end

local function age_generic(frame)
	-- Return the result required by the specified template.
	-- Can use sortable=x where x = on/off/debug in any supported template.
	-- Some templates default to sortable=on but can be overridden with sortable=off.
	local name = frame.args.template
	if not name then
		return message('Šablon koji ovo poziva mora imati "|template=x" gde je x željena operacija')
	end
	local args = frame:getParent().args
	local specs = {
		age_days = {                -- {{age in days}}
			show = 'd',
			disp = 'disp_raw',
		},
		age_days_nts = {            -- {{age in days nts}}
			show = 'd',
			disp = 'disp_raw',
			format = 'format_commas',
			sortable = 'on',
		},
		duration_days = {           -- {{duration in days}}
			show = 'd',
			disp = 'disp_raw',
			duration = true,
		},
		duration_days_nts = {       -- {{duration in days nts}}
			show = 'd',
			disp = 'disp_raw',
			format = 'format_commas',
			sortable = 'on',
			duration = true,
		},
		age_full_years = {          -- {{age}}
			show = 'y',
			abbr = 'abbr_raw',
		},
		age_full_years_nts = {      -- {{age nts}}
			show = 'y',
			abbr = 'abbr_raw',
			format = 'format_commas',
			sortable = 'on',
		},
		age_in_years = {            -- {{age in years}}
			show = 'y',
			abbr = 'abbr_raw',
			negative = 'error',
			range = 'dash',
		},
		age_in_years_nts = {        -- {{age in years nts}}
			show = 'y',
			abbr = 'abbr_raw',
			negative = 'error',
			range = 'dash',
			format = 'format_commas',
			sortable = 'on',
		},
		age_infant = {              -- {{age for 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',
			sortable = 'on',
		},
		age_m = {                   -- {{age in months}}
			show = 'm',
			disp = 'disp_raw',
		},
		age_w = {                   -- {{age in weeks}}
			show = 'w',
			disp = 'disp_raw',
		},
		age_wd = {                  -- {{age in weeks and days}}
			show = 'wd',
		},
		age_yd = {                  -- {{age in years and days}}
			show = 'yd',
			sep = args.sep ~= 'and' and 'sep_comma' or nil,
			sortable = 'on',
		},
		age_ym = {                  -- {{age in years and months}}
			show = 'ym',
			sep = 'sep_comma',
		},
		age_ymd = {                 -- {{age in years, months and days}}
			show = 'ymd',
			range = true,
		},
		age_ymwd = {                -- {{age in years, months, weeks and days}}
			show = 'ymwd',
			want_mixture = true,
		},
	}
	local spec = specs[name]
	if not spec then
		return message('Odabrani šablon nije validan')
	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 range = spec.range or yes(args.range) or (args.range == 'dash' and 'dash' or nil)
	local getopt = {
		fix = yes(args.fix),
		range = range,
		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 = spec.duration or yes(args.duration),
		range = range,
		want_sc = yes(args.sc),
		show = args.show == 'hide' and 'hide' or spec.show,
		abbr = spec.abbr,
		disp = spec.disp,
		format = format or spec.format,
		round = yes(args.round),
		sep = spec.sep,
		sortable = translate_parameters.sortable[args.sortable or spec.sortable],
	}
	if (spec.negative or frame.args.negative) == 'error' and parms.diff.isnegative then
		return message('Drugi datum ne sme da bude pre prvog datuma')
	end
	return date_difference(parms)
end

local function date_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 jd_to_date(frame)
	-- Return formatted date from a Julian date.
	-- The result includes a time if the 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('Potreban validan broj za datum iz julijanskog kalendara')
end

local function date_to_jd(frame)
	-- Return Julian date (a number) from a date which may include a time,
	-- 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('Potrebna validna godina/mesec/dan ili „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),
		range = yes(args.range) or (args.range == 'dash' and 'dash' or nil),
		want_sc = yes(args.sc),
	}
	local fix = yes(args.fix) and 'fix' or ''
	local date1 = Date(fix, 'partial', strip_to_nil(args[1]) or 'currentdatetime')
	if not date1 then
		return message('Nevalidan početni datum u prvom parametru')
	end
	local date2 = Date(fix, 'partial', strip_to_nil(args[2]) or 'currentdatetime')
	if not date2 then
		return message('Nevalidan krajnji datum u drugom parametru')
	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 parm == nil then  -- test for nil because false is a valid setting
				return message('Parametar ' .. argname .. '=' .. args[argname] .. ' nije validan')
			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('Parametar show=' .. args.show .. ' u sukobu sa 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 = date_to_gsd,                 -- Template:Gregorian_serial_date
	extract = date_extract,            -- Template:Extract
	jd_to_date = jd_to_date,           -- Template:?
	JULIANDAY = date_to_jd,            -- Template:JULIANDAY
	time_interval = time_interval,     -- Template:Time_interval
}