Editing Module:Age
The edit can be undone. Please check the comparison below to verify that this is what you want to do, and then publish the changes below to finish undoing the edit.
Latest revision | Your text | ||
Line 1: | Line 1: | ||
-- Implement various "age of" and other date-related templates. | -- Implement various "age of" and other date-related templates. | ||
local _Date, _currentDate | local _Date, _currentDate | ||
Line 64: | Line 5: | ||
-- 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 | ||
local sandbox = frame:getTitle():find( | local sandbox = frame:getTitle():find('sandbox', 1, true) and '/sandbox' or '' | ||
local datemod = require( | local datemod = require('Module:Date' .. sandbox) | ||
_Date = datemod._Date | |||
_currentDate = datemod._current | _currentDate = datemod._current | ||
end | end | ||
return _Date, _currentDate | return _Date, _currentDate | ||
Line 116: | Line 46: | ||
end | end | ||
return text | return text | ||
end | end | ||
Line 138: | Line 55: | ||
end | end | ||
local function message(msg, | local function message(msg, id) | ||
-- Return formatted message text for an error or warning. | -- Return formatted message text for an error or warning. | ||
local categories = { | local categories = { | ||
error = | error = '[[Category:Age error]]', | ||
warning = | warning = '[[Category:Age error]]', -- same as error until determine whether 'Age warning' would be worthwhile | ||
} | } | ||
local a, b | local a, b, category | ||
if id == 'warning' then | |||
a = '<sup>[<i>' | a = '<sup>[<i>' | ||
b = '</i>]</sup>' | b = '</i>]</sup>' | ||
else | else | ||
a = '<strong class="error">' | a = '<strong class="error">Error: ' | ||
b = '</strong>' | b = '</strong>' | ||
end | end | ||
if mw.title.getCurrentTitle():inNamespaces(0) then | if mw.title.getCurrentTitle():inNamespaces(0) then | ||
-- Category only in namespaces: 0=article. | -- Category only in namespaces: 0=article. | ||
category = | category = categories[id or 'error'] | ||
end | end | ||
return | return | ||
a .. | a .. | ||
mw.text.nowiki( | mw.text.nowiki(msg) .. | ||
b .. | b .. | ||
(category or '') | (category or '') | ||
Line 188: | Line 99: | ||
end | end | ||
return groups:join(',') | return groups:join(',') | ||
end | end | ||
Line 226: | Line 105: | ||
-- 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_table' or sortable == 'sortable_on' or sortable == 'sortable_debug' then | ||
local | local sortkey | ||
if value == 0 then | if value == 0 then | ||
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 | ||
prefix = 7000 + mag | |||
else | else | ||
prefix = 2999 - mag | |||
value = value + 10^(mag+1) | value = value + 10^(mag+1) | ||
end | end | ||
sortkey = string.format('%d', prefix) .. string.format('%015.0f', math.floor(value * 10^(14-mag))) | |||
end | end | ||
local | local lhs, rhs | ||
if sortable == 'sortable_table' then | if sortable == 'sortable_table' then | ||
lhs = 'data-sort-value="' | |||
rhs = '"|' | |||
else | 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 | end | ||
return | return lhs .. sortkey .. rhs | ||
end | end | ||
end | end | ||
Line 293: | Line 175: | ||
hm = { 'H', 'M', id = 'hm' }, | hm = { 'H', 'M', id = 'hm' }, | ||
hms = { 'H', 'M', 'S', id = 'hms' }, | hms = { 'H', 'M', 'S', id = 'hms' }, | ||
d = { 'd', id = 'd' }, | d = { 'd', id = 'd' }, | ||
dh = { 'd', 'H', id = 'dh' }, | dh = { 'd', 'H', id = 'dh' }, | ||
Line 310: | Line 190: | ||
debug = 'sortable_debug', | debug = 'sortable_debug', | ||
}, | }, | ||
} | } | ||
Line 343: | Line 212: | ||
return 'error' | return 'error' | ||
end | end | ||
return message(' | return message('Need valid date') | ||
end | end | ||
local add = stripToNil(args.add) | local add = stripToNil(args.add) | ||
Line 350: | Line 219: | ||
date = date + item | date = date + item | ||
if not date then | if not date then | ||
return message(' | return message('Cannot add "' .. item .. '"') | ||
end | end | ||
end | end | ||
end | end | ||
local | local prefix, result | ||
local sortable = translateParameters.sortable[args.sortable] | local sortable = translateParameters.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 | ||
prefix = makeSort(value, sortable) | |||
end | end | ||
if show ~= 'hide' then | if show ~= 'hide' then | ||
result = date[show] | result = date[show] | ||
if result == nil then | if result == nil then | ||
result = | result = date:text(show) | ||
elseif type(result) == 'boolean' then | elseif type(result) == 'boolean' then | ||
result = result and '1' or '0' | result = result and '1' or '0' | ||
else | else | ||
result = | result = tostring(result) | ||
end | end | ||
end | end | ||
return ( | return (prefix or '') .. (result or '') | ||
end | end | ||
local function rangeJoin(range) | local function rangeJoin(range) | ||
-- Return text to be used between a range of ages. | -- Return text to be used between a range of ages. | ||
return range == 'dash' and '–' or | return range == 'dash' and '–' or ' or ' | ||
end | end | ||
local function makeText(values, components, names, options | local function makeText(values, components, names, options) | ||
-- Return wikitext representing an age or duration. | -- Return wikitext representing an age or duration. | ||
local text = Collection.new() | local text = Collection.new() | ||
Line 388: | Line 257: | ||
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 | if 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 | ||
Line 399: | Line 264: | ||
end | end | ||
if islist then | if islist then | ||
vstr = fmt(v[1]) .. rangeJoin(options.range) | vstr = fmt(v[1]) .. rangeJoin(options.range) .. fmt(v[2]) | ||
else | else | ||
vstr = fmt(v) | vstr = fmt(v) | ||
Line 407: | Line 270: | ||
local name = names[components[i]] | local name = names[components[i]] | ||
if name then | if name then | ||
local plural = names.plural | |||
if not plural or (islist and v[2] or v) == 1 then | |||
plural = '' | |||
end | end | ||
text:add(vstr .. sep .. name) | text:add(vstr .. sep .. name .. plural) | ||
else | else | ||
text:add(vstr) | text:add(vstr) | ||
Line 425: | Line 289: | ||
elseif options.join == 'sep_serialcomma' and text.n > 2 then | elseif options.join == 'sep_serialcomma' and text.n > 2 then | ||
first = ', ' | first = ', ' | ||
last = | last = ', and ' | ||
else | else | ||
first = ', ' | first = ', ' | ||
last = | last = ' and ' | ||
end | end | ||
for i, v in ipairs(text) do | for i, v in ipairs(text) do | ||
Line 447: | Line 311: | ||
end | end | ||
return | return | ||
(options. | (options.prefix or '') .. | ||
sign .. | sign .. | ||
text:join() .. | text:join() .. | ||
Line 458: | Line 321: | ||
-- which have been validated. | -- which have been validated. | ||
local names = { | local names = { | ||
abbr_off = { | abbr_off = { | ||
plural = 's', | |||
sep = ' ', | sep = ' ', | ||
y = | y = 'year', | ||
m = | m = 'month', | ||
w = | w = 'week', | ||
d = | d = 'day', | ||
H = | H = 'hour', | ||
M = | M = 'minute', | ||
S = | S = 'second', | ||
}, | }, | ||
abbr_on = { | abbr_on = { | ||
Line 482: | Line 342: | ||
}, | }, | ||
abbr_infant = { -- for {{age for infant}} | abbr_infant = { -- for {{age for infant}} | ||
plural = 's', | |||
sep = ' ', | sep = ' ', | ||
y = | y = 'yr', | ||
m = | m = 'mo', | ||
w = | w = 'wk', | ||
d = | d = 'day', | ||
H = | H = 'hr', | ||
M = | M = 'min', | ||
S = | S = 'sec', | ||
}, | }, | ||
abbr_raw = {}, | abbr_raw = {}, | ||
Line 529: | Line 390: | ||
range = parms.range and true or nil, | range = parms.range and true or nil, | ||
} | } | ||
local | 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.wantDuration and 1 or 0) -- days and fraction of a day | ||
Line 535: | Line 396: | ||
value = -value | value = -value | ||
end | end | ||
prefix = makeSort(value, parms.sortable) | |||
end | end | ||
local textOptions = { | local textOptions = { | ||
prefix = prefix, | |||
suffix = parms.suffix, -- not currently used | |||
format = parms.format, | format = parms.format, | ||
join = parms.sep or defaultJoin, | join = parms.sep or defaultJoin, | ||
isnegative = diff.isnegative, | isnegative = diff.isnegative, | ||
range = parms.range, | range = parms.range, | ||
} | } | ||
if show.id == 'hide' then | if show.id == 'hide' then | ||
return | return prefix or '' | ||
end | end | ||
local values = { diff:age(show.id, diffOptions) } | local values = { diff:age(show.id, diffOptions) } | ||
Line 561: | Line 420: | ||
join = textOptions.join, | join = textOptions.join, | ||
isnegative = textOptions.isnegative, | isnegative = textOptions.isnegative, | ||
} | } | ||
return | return | ||
(textOptions. | (textOptions.prefix or '') .. | ||
makeText({ diff.partial.mindiff:age(show.id, diffOptions) }, show, names[abbr], opt) .. | makeText({ diff.partial.mindiff:age(show.id, diffOptions) }, show, names[abbr], opt) .. | ||
rangeJoin(textOptions.range) .. | rangeJoin(textOptions.range) .. | ||
makeText({ diff.partial.maxdiff:age(show.id, diffOptions) }, show, names[abbr], opt | makeText({ diff.partial.maxdiff:age(show.id, diffOptions) }, show, names[abbr], opt) .. | ||
(textOptions.suffix or '') | (textOptions.suffix or '') | ||
end | end | ||
return message(' | return message('Parameter show=' .. show.id .. ' is not supported here') | ||
end | end | ||
Line 585: | Line 443: | ||
local Date, currentDate = getExports(frame) | local Date, currentDate = getExports(frame) | ||
getopt = getopt or {} | getopt = getopt or {} | ||
local args = frame:getParent().args | local args = frame:getParent().args | ||
local fields = {} | local fields = {} | ||
Line 623: | Line 465: | ||
if fields[i] then | if fields[i] then | ||
imax = i | imax = i | ||
end | end | ||
end | end | ||
local fix = getopt.fix and 'fix' or '' | local fix = getopt.fix and 'fix' or '' | ||
local noDefault = imax == 0 and getopt.noMissing | |||
local partialText = getopt.partial and 'partial' or '' | local partialText = getopt.partial and 'partial' or '' | ||
local dates = {} | local dates = {} | ||
Line 644: | Line 481: | ||
for i = 1, nrDates do | for i = 1, nrDates 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]) | |||
end | end | ||
else | else | ||
Line 668: | Line 494: | ||
if (getopt.partial and y and (m or not d)) or (y and m and d) then | if (getopt.partial and y and (m or not d)) or (y and m and d) then | ||
dates[i] = Date(fix, partialText, y, m, d) | dates[i] = Date(fix, partialText, y, m, d) | ||
elseif not y and not m and not d then | elseif not y and not m and not d and not noDefault then | ||
dates[i] = Date( | dates[i] = Date('currentdate') | ||
end | end | ||
end | end | ||
end | end | ||
elseif not noDefault then | |||
getopt.textdates = true -- have parsed each date from a single text field | getopt.textdates = true -- have parsed each date from a single text field | ||
dates[1] = Date(fix, partialText, | dates[1] = Date(fix, partialText, fields[1] or 'currentdate') | ||
if not getopt.single then | if not getopt.single then | ||
dates[2] = Date(fix, partialText, | dates[2] = Date(fix, partialText, fields[2] or 'currentdate') | ||
end | end | ||
end | end | ||
if not dates[1] then | if not dates[1] then | ||
return message( | return message('Need valid year, month, day') | ||
end | end | ||
if getopt.single then | if getopt.single then | ||
Line 687: | Line 513: | ||
end | end | ||
if not dates[2] then | if not dates[2] then | ||
return message( | return message('Second date should be year, month, day') | ||
end | end | ||
return dates[1], dates[2] | return dates[1], dates[2] | ||
Line 698: | Line 524: | ||
local name = frame.args.template | local name = frame.args.template | ||
if not name then | if not name then | ||
return message(' | return message('The template invoking this must have "|template=x" where x is the wanted operation') | ||
end | end | ||
local args = frame:getParent().args | local args = frame:getParent().args | ||
Line 727: | Line 553: | ||
show = 'y', | show = 'y', | ||
abbr = 'abbr_raw', | abbr = 'abbr_raw', | ||
}, | }, | ||
age_full_years_nts = { -- {{age nts}} | age_full_years_nts = { -- {{age nts}} | ||
Line 795: | Line 618: | ||
local spec = specs[name] | local spec = specs[name] | ||
if not spec then | if not spec then | ||
return message(' | return message('The specified template name is not valid') | ||
end | end | ||
if name == 'age_days' then | if name == 'age_days' then | ||
Line 827: | Line 650: | ||
local getopt = { | local getopt = { | ||
fix = yes(args.fix), | fix = yes(args.fix), | ||
partial = partial, | partial = partial, | ||
wantMixture = spec.wantMixture, | wantMixture = spec.wantMixture, | ||
Line 837: | Line 658: | ||
end | end | ||
local format = stripToNil(args.format) | local format = stripToNil(args.format) | ||
if format then | if format then | ||
format = 'format_' .. format | format = 'format_' .. format | ||
Line 851: | Line 671: | ||
abbr = spec.abbr, | abbr = spec.abbr, | ||
disp = spec.disp, | disp = spec.disp, | ||
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 = translateParameters.sortable[args.sortable or spec.sortable], | ||
} | } | ||
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('The second date should not be before the first date') | ||
end | end | ||
return | return dateDifference(parms) | ||
end | end | ||
Line 867: | Line 685: | ||
-- Implement [[Template:Birth date and age]]. | -- Implement [[Template:Birth date and age]]. | ||
local args = frame:getParent().args | local args = frame:getParent().args | ||
local options = { | local options = { noMissing=true, single=true } | ||
local date = getDates(frame, options) | local date = getDates(frame, options) | ||
if type(date) == 'string' then | if type(date) == 'string' then | ||
Line 879: | Line 693: | ||
local diff = Date('currentdate') - date | local diff = Date('currentdate') - date | ||
if diff.isnegative or diff.years > 150 then | if diff.isnegative or diff.years > 150 then | ||
return message(' | return message('Invalid birth date for calculating age') | ||
end | end | ||
local disp = | local disp, show = 'disp_raw', 'y' | ||
if diff.years < 2 then | if diff.years < 2 then | ||
disp = 'disp_age' | disp = 'disp_age' | ||
Line 891: | Line 704: | ||
end | end | ||
end | end | ||
local result = | local df = stripToNil(args.df) -- day first (dmy); default is month first (mdy) | ||
local result = df and | |||
'%-d %B %-Y' or | |||
'%B %-d, %-Y' | |||
result = '(<span class="bday">%-Y-%m-%d</span>) </span>' .. result | |||
result = '<span style="display:none"> ' .. | |||
date:text(result) .. | |||
'<span class="noprint ForceAgeToShow"> ' .. | |||
'(age ' .. | |||
dateDifference({ | |||
diff = diff, | diff = diff, | ||
show = show, | show = show, | ||
Line 901: | Line 719: | ||
disp = disp, | disp = disp, | ||
sep = 'sep_space', | sep = 'sep_space', | ||
}) | }) .. | ||
')</span>' | |||
local warnings = tonumber(frame.args.warnings) | local warnings = tonumber(frame.args.warnings) | ||
if warnings and warnings > 0 then | if warnings and warnings > 0 then | ||
Line 908: | Line 726: | ||
df = true, | df = true, | ||
mf = true, | mf = true, | ||
template = true, | |||
day = true, | day = true, | ||
day1 = true, | day1 = true, | ||
Line 931: | Line 750: | ||
end | end | ||
if invalid then | if invalid then | ||
result = result .. message(' | result = result .. message('invalid parameter ' .. invalid, 'warning') | ||
end | end | ||
end | end | ||
Line 1,041: | Line 776: | ||
local date = Date('juliandate', args[1], args[2]) | local date = Date('juliandate', args[1], args[2]) | ||
if date then | if date then | ||
return | return date:text() | ||
end | end | ||
return message(' | return message('Need valid Julian date number') | ||
end | end | ||
Line 1,056: | Line 791: | ||
return tostring(date.jd) | return tostring(date.jd) | ||
end | end | ||
return message(' | return message('Need valid year/month/day or "currentdate"') | ||
end | end | ||
Line 1,067: | Line 802: | ||
local args = frame:getParent().args | local args = frame:getParent().args | ||
local parms = { | local parms = { | ||
wantDuration = 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), | ||
Line 1,075: | Line 809: | ||
local date1 = Date(fix, 'partial', stripToNil(args[1]) or 'currentdatetime') | local date1 = Date(fix, 'partial', stripToNil(args[1]) or 'currentdatetime') | ||
if not date1 then | if not date1 then | ||
return message(' | return message('Invalid start date in first parameter') | ||
end | end | ||
local date2 = Date(fix, 'partial', stripToNil(args[2]) or 'currentdatetime') | local date2 = Date(fix, 'partial', stripToNil(args[2]) or 'currentdatetime') | ||
if not date2 then | if not date2 then | ||
return message(' | return message('Invalid end date in second parameter') | ||
end | end | ||
parms.diff = date2 - date1 | parms.diff = date2 - date1 | ||
Line 1,087: | Line 821: | ||
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(' | return message('Parameter ' .. argname .. '=' .. args[argname] .. ' is invalid') | ||
end | end | ||
parms[argname] = parm | parms[argname] = parm | ||
Line 1,098: | Line 832: | ||
if show then | if show then | ||
if show.id ~= round then | if show.id ~= round then | ||
return message(' | return message('Parameter show=' .. args.show .. ' conflicts with round=' .. args.round) | ||
end | end | ||
else | else | ||
Line 1,106: | Line 840: | ||
parms.round = true | parms.round = true | ||
end | end | ||
return | return dateDifference(parms) | ||
end | end | ||
Line 1,112: | Line 846: | ||
age_generic = ageGeneric, -- can emulate several age templates | age_generic = ageGeneric, -- can emulate several age templates | ||
birth_date_and_age = bda, -- Template:Birth_date_and_age | birth_date_and_age = bda, -- Template:Birth_date_and_age | ||
gsd = dateToGsd, -- Template:Gregorian_serial_date | gsd = dateToGsd, -- Template:Gregorian_serial_date | ||
extract = dateExtract, -- Template:Extract | extract = dateExtract, -- Template:Extract |