Module:Age
![]() | This module issubject to page protection.It is ahighly visible modulein use by a very large number of pages, or issubstitutedvery frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it isprotectedfrom editing. |
![]() | This Lua module is used onapproximately 1,250,000 pages, or roughly 2% of all pages. To avoid major disruption and server load, any changes should be tested in the module's/sandboxor/testcasessubpages, or in your ownmodule sandbox.The tested changes can be added to this page in a single edit. Consider discussing changes on thetalk pagebefore implementing them. |
Templates supported
Module:Ageimplements the following templates:
Template | Required wikitext |
---|---|
{{extract}} | {{#invoke:age|extract}}
|
{{gregorian serial date}} | {{#invoke:age|gsd}}
|
{{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 days nts}} | {{#invoke:age|age_generic|template=age_yd_nts}}
|
{{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}}
|
{{birth date and age}} | {{#invoke:age|birth_date_and_age}}
|
{{death date and age}} | {{#invoke:age|death_date_and_age}}
|
Redirects
Template | Redirects to |
---|---|
{{ayd}} | {{age in years and days nts}} |
{{hla}} | {{age in years, months and days}} |
{{age in months, weeks and days}} | {{age in years, months, weeks and days}} |
{{bda}} | {{birth date and age}} |
{{dda}} | {{death date and age}} |
{{gsd}} | {{gregorian serial date}} |
The age templates expect the older date to be first. The implementations ofage_in_years
andage_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}}
Ifnegative=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 years, 1 month{{age in years and months|year=2001|month=1|day=10|year2=2012|month2=2|day2=20}}
→ 11 years, 1 month{{age in years and months|2001|1|10|2012|2|20}}
→ 11 years, 1 month{{age in years and months|2001-1-10|2012-2-20}}
→ 11 years, 1 month{{age in years and months|10 Jan 2001|20 Feb 2012}}
→ 11 years, 1 month{{age in years and months|January 10, 2001|Feb 20, 2012}}
→ 11 years, 1 month
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}}
→ −12 years, 6 months{{age in years and months||||2012|2|20}}
→ −12 years, 6 months{{age in years and months||2012-2-20}}
→ −12 years, 6 months{{age in years and months||20 Feb 2012}}
→ −12 years, 6 months{{age in years and months||Feb 20, 2012}}
→ −12 years, 6 months{{age in years and months|year1=2001|month1=1|day1=10}}
→ 23 years, 7 months{{age in years and months|year=2001|month=1|day=10}}
→ 23 years, 7 months{{age in years and months|2001|1|10}}
→ 23 years, 7 months{{age in years and months|2001-1-10}}
→ 23 years, 7 months{{age in years and months|10 Jan 2001}}
→ 23 years, 7 months{{age in years and months|January 10, 2001}}
→ 23 years, 7 months
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. SeeTemplate:Extract#Fix. |
format=commas |
A value of 1,000 or more is displayed with commas. |
format=raw |
Numbers are displayed without commas and negative numbers are displayed with a hyphen for{{#expr}} .In addition,{{age}}outputs a plain number and will not include a span to indicate if the result relies on the current date.
|
format=cardinal |
Display the resulting number using words such as "five" instead of 5. Seebelow. |
format=ordinal |
Display the resulting number using words such as "fifth" instead of 5. Seebelow. |
prefix=text |
Insert the giventextbefore the result but after any sort key. For example,{{age|23 July 1910|14 July 1976|prefix=about|sortable=on}} outputs a hidden sort key followed by "about 65".
|
range=dash |
Accept a year only, or a year and month only, and show a range of ages with anen dash(–). |
range=yes |
Accept a year or year/month, and show the range with "or". |
range=no |
Accept a year only, or year/month, but show only a single age as if full dates had been entered. |
round=on |
The age is rounded to the nearest least-significant time unit. |
sc=on |
Aserial commais used (only useful when three or more values are displayed). |
sc=yes |
Same assc=on .
|
show=hide |
The age is not displayed; may be useful withsortable=on .
|
sortable=on |
Insert a hidden sort key before the result (for use insortable tables). |
sortable=table |
Insert a sort key using table syntaxdata-sort-value= "value"| .
|
sortable=debug |
Same assortable=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 therange
parameter follow.
{{age in years and months|year=2001|month=1|year2=2012|month2=2|range=yes}}
→ 11 years, 0 or 1 month{{age in years and months|2001|1||2012|2|range=yes}}
→ 11 years, 0 or 1 month{{age in years and months|Jan 2001|Feb 2012|range=yes}}
→ 11 years, 0 or 1 month{{age in years and months|Jan 2001|Feb 2012|range=dash}}
→ 11 years, 0–1 month{{age in years and months|Jan 2001|Feb 2012|range=no}}
→ 11 years, 1 month(assume 1 Jan 2001 to 1 Feb 2012){{age in years and months|12 Jan 2001|Feb 2012|range=no}}
→ 11 years, 1 month(assume 12 Jan 2001 to 12 Feb 2012){{age in years and months|2001|2012|range=no}}
→ 11 years(assume 1 Jan 2001 to 1 Jan 2012){{age in years and months|2001|23 Feb 2012|range=no}}
→ 11 years(assume 23 Feb 2001 to 23 Feb 2012)
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 years, 1 month{{age in years and months|10 Jan 2001|6:00 am 20 Feb 2012|sortable=debug}}
→7003405825000000000♠11 years, 1 month{{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 years(one day short of 11 years, 1 month){{age in years and months|20 Jan 2001|19 Feb 2012|duration=on}}
→ 11 years, 1 month
The least-significant time unit can be rounded.
{{age in years and months|20 Jan 2001|10 Feb 2012}}
→ 11 years{{age in years and months|20 Jan 2001|10 Feb 2012|round=on}}
→ 11 years, 1 month(round to nearest month)
Large numbers can be formatted with commas.
{{age in years and months|120|2012|format=commas|range=yes}}
→ 1,891 or 1,892 years{{age in years and months|120|2012|format=commas|range=dash}}
→ 1,891–1,892 years
Spelling numbers
The templates that useage_generic
can display numbers in words rather than using numerals. The result can be acardinal number(such as "five" ) or anordinal number(such as "fifth" ). The first letter can be in uppercase, and US spelling of numbers can be used. Examples:
{{age|1898|01|01|2018|02|01|format=cardinal}}
→ one hundred and twenty{{age|1898|01|01|2018|02|01|format=cardinal_us}}
→ one hundred twenty{{age|1898|01|01|2018|02|01|format=Cardinal}}
→ One hundred and twenty{{age|1898|01|01|2018|02|01|format=Cardinal_us}}
→ One hundred twenty{{age|1898|01|01|2018|02|01|format=Ordinal}}
→ One hundred and twentieth{{age|1898|01|01|2018|02|01|format=Ordinal_us}}
→ One hundred twentieth{{age|1898|01|01|2018|02|01|format=ordinal}}
→ one hundred and twentieth{{age|1898|01|01|2018|02|01|format=ordinal_us}}
→ one hundred twentieth{{age|1980|1990|range=yes|format=Cardinal}}
→ Nine or ten{{age in years, months and days|April 1980|1995|format=Cardinal|range=yes}}
→ Fourteen or fifteen years
Tracking category
Localization
Inputs and outputs can be localized to suit the language used. Examples are atbnwikiandbswiki.
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.
localmtext={
-- Message and other text that should be localized.
-- Also need to localize text in table names in function dateDifference.
['mt-bad-param1']='Invalid parameter $1',
['mt-bad-param2']='Parameter $1=$2 is invalid',
['mt-bad-show']='Parameter show=$1 is not supported here',
['mt-cannot-add']='Cannot add "$1" ',
['mt-conflicting-show']='Parameter show=$1 conflicts with round=$2',
['mt-date-wrong-order']='The second date must be later in time than the first date',
['mt-dd-future']='Death date (first date) must not be in the future',
['mt-dd-wrong-order']='Death date (first date) must be later in time than the birth date (second date)',
['mt-invalid-bd-age']='Invalid birth date for calculating age',
['mt-invalid-dates-age']='Invalid dates for calculating age',
['mt-invalid-end']='Invalid end date in second parameter',
['mt-invalid-start']='Invalid start date in first parameter',
['mt-need-jdn']='Need valid Julian date number',
['mt-need-valid-bd']='Need valid birth date: year, month, day',
['mt-need-valid-bd2']='Need valid birth date (second date): year, month, day',
['mt-need-valid-date']='Need valid date',
['mt-need-valid-dd']='Need valid death date (first date): year, month, day',
['mt-need-valid-ymd']='Need valid year, month, day',
['mt-need-valid-ymd-current']='Need valid year|month|day or "currentdate" ',
['mt-need-valid-ymd2']='Second date should be year, month, day',
['mt-template-bad-name']='The specified template name is not valid',
['mt-template-x']='The template invoking this must have "|template=x" where x is the wanted operation',
['txt-and']=' and ',
['txt-or']=' or ',
['txt-category']='Category:Age error',
['txt-comma-and']=', and ',
['txt-error']='Error: ',
['txt-format-default']='mf',-- 'df' (day first = dmy) or 'mf' (month first = mdy)
['txt-module-convertnumeric']='Module:ConvertNumeric',
['txt-module-date']='Module:Date',
['txt-sandbox']='sandbox',
['txt-bda']='<span style= "display:none" > (<span class= "bday" >$1</span>) </span>$2<span class= "noprint ForceAgeToShow" > (age $3)</span>',
['txt-dda']='$2<span style= "display:none" >($1)</span> (aged $3)',
['txt-bda-disp']='disp_raw',-- disp_raw → age is a number only; disp_age → age is a number and unit (normally years but months or days if very young)
['txt-dda-disp']='disp_raw',
['txt-dmy']='%-d %B %-Y',
['txt-mdy']='%B %-d, %-Y',
}
localisWarning={
['mt-bad-param1']=true,
}
localtranslate,from_en,to_en,isZero
iftranslatethen
-- Functions to translate from en to local language and reverse go here.
-- See example at [[:bn:Module:বয়স]].
else
from_en=function(text)
returntext
end
isZero=function(text)
returntonumber(text)==0
end
end
local_Date,_currentDate
localfunctiongetExports(frame)
-- Return objects exported from the date module or its sandbox.
ifnot_Datethen
localsandbox=frame:getTitle():find(mtext['txt-sandbox'],1,true)and('/'..mtext['txt-sandbox'])or''
localdatemod=require(mtext['txt-module-date']..sandbox)
localrealDate=datemod._Date
_currentDate=datemod._current
ifto_enthen
_Date=function(...)
localargs={}
fori,vinipairs({...})do
args[i]=to_en(v)
end
returnrealDate(unpack(args))
end
else
_Date=realDate
end
end
return_Date,_currentDate
end
localCollection-- a table to hold items
Collection={
add=function(self,item)
ifitem~=nilthen
self.n=self.n+1
self[self.n]=item
end
end,
join=function(self,sep)
returntable.concat(self,sep)
end,
remove=function(self,pos)
ifself.n>0and(pos==nilor(0<posandpos<=self.n))then
self.n=self.n-1
returntable.remove(self,pos)
end
end,
sort=function(self,comp)
table.sort(self,comp)
end,
new=function()
returnsetmetatable({n=0},Collection)
end
}
Collection.__index=Collection
localfunctionstripToNil(text)
-- If text is a string, return its trimmed content, or nil if empty.
-- Otherwise return text (which may, for example, be nil).
iftype(text)=='string'then
text=text:match('(%S.-)%s*$')
end
returntext
end
localfunctiondateFormat(args)
-- Return string for wanted date format.
localdefault=mtext['txt-format-default']
localother=default=='df'and'mf'or'df'
localwanted=stripToNil(args[other])andotherordefault
returnwanted=='df'andmtext['txt-dmy']ormtext['txt-mdy']
end
localfunctionsubstituteParameters(text,...)
-- Return text after substituting any given parameters for $1, $2, etc.
returnmw.message.newRawMessage(text,...):plain()
end
localfunctionyes(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
localfunctionmessage(msg,...)
-- Return formatted message text for an error or warning.
localfunctiongetText(msg)
returnmtext[msg]orerror('Bug: message "'..tostring(msg)..' "not defined')
end
localcategories={
error=mtext['txt-category'],
warning=mtext['txt-category'],
}
locala,b,k,category
localtext=substituteParameters(getText(msg),...)
ifisWarning[msg]then
a='<sup>[<i>'
b='</i>]</sup>'
k='warning'
else
a='<strong class= "error" >'..getText('txt-error')
b='</strong>'
k='error'
end
ifmw.title.getCurrentTitle():inNamespaces(0)then
-- Category only in namespaces: 0=article.
category='[['..categories[k]..']]'
end
return
a..
mw.text.nowiki(text)..
b..
(categoryor'')
end
localfunctionformatNumber(number)
-- Return the given number formatted with commas as group separators,
-- given that the number is an integer.
localnumstr=tostring(number)
locallength=#numstr
localplaces=Collection.new()
localpos=0
repeat
places:add(pos)
pos=pos+3
untilpos>=length
places:add(length)
localgroups=Collection.new()
fori=places.n,2,-1do
localp1=length-places[i]+1
localp2=length-places[i-1]
groups:add(numstr:sub(p1,p2))
end
returngroups:join(',')
end
localfunctionspellNumber(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)
returnrequire(mtext['txt-module-convertnumeric']).spell_number(
number,
nil,-- fraction numerator
nil,-- fraction denominator
i==1andoptions.upper,-- true: 'One' instead of 'one'
notoptions.us,-- true: use 'and' between tens/ones etc
options.adj,-- true: hyphenated
options.ordinal-- true: 'first' instead of 'one'
)ornumber
end
localfunctionmakeExtra(args,flagCurrent)
-- Return extra text that will be inserted before the visible result
-- but after any sort key.
localextra=args.prefixor''
ifmw.ustring.len(extra)>1then
-- Parameter "~" gives "~3" whereas "over" gives "over 3".
ifextra:sub(-6,-1)~=' 'then
extra=extra..' '
end
end
ifflagCurrentthen
extra='<span class= "currentage" ></span>'..extra
end
returnextra
end
localfunctionmakeSort(value,sortable)
-- Return a sort key if requested.
-- Assume value is a valid number which has not overflowed.
ifsortable=='sortable_table'orsortable=='sortable_on'orsortable=='sortable_debug'then
localsortKey
ifvalue==0then
sortKey='5000000000000000000'
else
localmag=math.floor(math.log10(math.abs(value))+1e-14)
ifvalue>0then
sortKey=7000+mag
else
sortKey=2999-mag
value=value+10^(mag+1)
end
sortKey=string.format('%d',sortKey)..string.format('%015.0f',math.floor(value*10^(14-mag)))
end
localresult
ifsortable=='sortable_table'then
result='data-sort-value= "_SORTKEY_" |'
elseifsortable=='sortable_debug'then
result='<span data-sort-value= "_SORTKEY_♠" ><span style= "border:1px solid" >_SORTKEY_♠</span></span>'
else
result='<span data-sort-value= "_SORTKEY_♠" ></span>'
end
return(result:gsub('_SORTKEY_',sortKey))
end
end
localtranslateParameters={
abbr={
off='abbr_off',
on='abbr_on',
},
disp={
age='disp_age',
raw='disp_raw',
},
format={
raw='format_raw',
commas='format_commas',
},
round={
on='on',
yes='on',
months='ym',
weeks='ymw',
days='ymd',
hours='ymdh',
},
sep={
comma='sep_comma',
[',']='sep_comma',
serialcomma='sep_serialcomma',
space='sep_space',
},
show={
hide={id='hide'},
y={'y',id='y'},
ym={'y','m',id='ym'},
ymd={'y','m','d',id='ymd'},
ymw={'y','m','w',id='ymw'},
ymwd={'y','m','w','d',id='ymwd'},
yd={'y','d',id='yd',keepZero=true},
m={'m',id='m'},
md={'m','d',id='md'},
w={'w',id='w'},
wd={'w','d',id='wd'},
h={'H',id='h'},
hm={'H','M',id='hm'},
hms={'H','M','S',id='hms'},
M={'M',id='M'},
s={'S',id='s'},
d={'d',id='d'},
dh={'d','H',id='dh'},
dhm={'d','H','M',id='dhm'},
dhms={'d','H','M','S',id='dhms'},
ymdh={'y','m','d','H',id='ymdh'},
ymdhm={'y','m','d','H','M',id='ymdhm'},
ymwdh={'y','m','w','d','H',id='ymwdh'},
ymwdhm={'y','m','w','d','H','M',id='ymwdhm'},
},
sortable={
off=false,
on='sortable_on',
table='sortable_table',
debug='sortable_debug',
},
}
localspellOptions={
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},
}
localfunctiondateExtract(frame)
-- Return part of a date after performing an optional operation.
localDate=getExports(frame)
localargs=frame:getParent().args
localparms={}
fori,vinipairs(args)do
parms[i]=v
end
ifyes(args.fix)then
table.insert(parms,'fix')
end
ifyes(args.partial)then
table.insert(parms,'partial')
end
localshow=stripToNil(args.show)or'dmy'
localdate=Date(unpack(parms))
ifnotdatethen
ifshow=='format'then
return'error'
end
returnmessage('mt-need-valid-date')
end
localadd=stripToNil(args.add)
ifaddthen
foriteminadd:gmatch('%S+')do
date=date+item
ifnotdatethen
returnmessage('mt-cannot-add',item)
end
end
end
localsortKey,result
localsortable=translateParameters.sortable[args.sortable]
ifsortablethen
localvalue=(date.partialanddate.partial.firstordate).jdz
sortKey=makeSort(value,sortable)
end
ifshow~='hide'then
result=date[show]
ifresult==nilthen
result=from_en(date:text(show))
elseiftype(result)=='boolean'then
result=resultand'1'or'0'
else
result=from_en(tostring(result))
end
end
return(sortKeyor'')..makeExtra(args)..(resultor'')
end
localfunctionrangeJoin(range)
-- Return text to be used between a range of ages.
returnrange=='dash'and'–'ormtext['txt-or']
end
localfunctionmakeText(values,components,names,options,noUpper)
-- Return wikitext representing an age or duration.
localtext=Collection.new()
localcount=#values
localsep=names.sepor''
fori,vinipairs(values)do
-- v is a number (say 4 for 4 years), or a table ({4,5} for 4 or 5 years).
localislist=type(v)=='table'
if(islistorv>0)or(text.n==0andi==count)or(text.n>0andcomponents.keepZero)then
localfmt,vstr
ifoptions.spellthen
fmt=function(number)
returnspellNumber(number,options.spell,noUpperori)
end
elseifi==1andoptions.format=='format_commas'then
-- Numbers after the first should be small and not need formatting.
fmt=formatNumber
else
fmt=tostring
end
ifislistthen
vstr=fmt(v[1])..rangeJoin(options.range)
noUpper=true
vstr=vstr..fmt(v[2])
else
vstr=fmt(v)
end
localname=names[components[i]]
ifnamethen
iftype(name)=='table'then
name=mw.getContentLanguage():plural(islistandv[2]orv,name)
end
text:add(vstr..sep..name)
else
text:add(vstr)
end
end
end
localfirst,last
ifoptions.join=='sep_space'then
first=' '
last=' '
elseifoptions.join=='sep_comma'then
first=', '
last=', '
elseifoptions.join=='sep_serialcomma'andtext.n>2then
first=', '
last=mtext['txt-comma-and']
else
first=', '
last=mtext['txt-and']
end
fori,vinipairs(text)do
ifi<text.nthen
text[i]=v..(i+1<text.nandfirstorlast)
end
end
localsign=''
ifoptions.isnegativethen
-- Do not display negative zero.
iftext.n>1or(text.n==1andtext[1]:sub(1,1)~='0')then
ifoptions.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.sortKeyor'')..
(options.extraor'')..
sign..
text:join()..
(options.suffixor'')
end
localfunctiondateDifference(parms)
-- Return a formatted date difference using the given parameters
-- which have been validated.
localnames={
-- Each name is:
-- * a string if no plural form of the name is used; or
-- * a table of strings, one of which is selected using the rules at
-- https://translatewiki.net/wiki/Plural/Mediawiki_plural_rules
abbr_off={
sep=' ',
y={'year','years'},
m={'month','months'},
w={'week','weeks'},
d={'day','days'},
H={'hour','hours'},
M={'minute','minutes'},
S={'second','seconds'},
},
abbr_on={
y='y',
m='m',
w='w',
d='d',
H='h',
M='m',
S='s',
},
abbr_infant={-- for {{age for infant}}
sep=' ',
y={'yr','yrs'},
m={'mo','mos'},
w={'wk','wks'},
d={'day','days'},
H={'hr','hrs'},
M={'min','mins'},
S={'sec','secs'},
},
abbr_raw={},
}
localdiff=parms.diff-- must be a valid date difference
localshow=parms.show-- may be nil; default is set below
localabbr=parms.abbror'abbr_off'
localdefaultJoin
ifabbr~='abbr_off'then
defaultJoin='sep_space'
end
ifnotshowthen
show='ymd'
ifparms.disp=='disp_age'then
ifdiff.years<3then
defaultJoin='sep_space'
ifdiff.years>=1then
show='ym'
else
show='md'
end
else
show='y'
end
end
end
iftype(show)~='table'then
show=translateParameters.show[show]
end
ifparms.disp=='disp_raw'then
defaultJoin='sep_space'
abbr='abbr_raw'
elseifparms.wantScthen
defaultJoin='sep_serialcomma'
end
localdiffOptions={
round=parms.round,
duration=parms.wantDuration,
range=parms.rangeandtrueornil,
}
localsortKey
ifparms.sortablethen
localvalue=diff.age_days+(parms.wantDurationand1or0)-- days and fraction of a day
ifdiff.isnegativethen
value=-value
end
sortKey=makeSort(value,parms.sortable)
end
localtextOptions={
extra=parms.extra,
format=parms.format,
join=parms.sepordefaultJoin,
isnegative=diff.isnegative,
range=parms.range,
sortKey=sortKey,
spell=parms.spell,
suffix=parms.suffix,-- not currently used
}
ifshow.id=='hide'then
returnsortKeyor''
end
localvalues={diff:age(show.id,diffOptions)}
ifvalues[1]then
returnmakeText(values,show,names[abbr],textOptions)
end
ifdiff.partialthen
-- Handle a more complex range such as
-- {{age_yd|20 Dec 2001|2003|range=yes}} → 1 year, 12 days or 2 years, 11 days
localopt={
format=textOptions.format,
join=textOptions.join,
isnegative=textOptions.isnegative,
spell=textOptions.spell,
}
return
(textOptions.sortKeyor'')..
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.suffixor'')
end
returnmessage('mt-bad-show',show.id)
end
localfunctiongetDates(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 optionally replaced with the current date.
-- If wantMixture 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.
localDate,currentDate=getExports(frame)
getopt=getoptor{}
localfunctionflagCurrent(text)
-- This allows the calling template to detect if the current date has been used,
-- 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.
ifnottextthen
ifgetopt.noMissingthen
returnnil-- this gives a nil date which gives an error
end
text='currentdate'
ifgetopt.flag=='usesCurrent'then
getopt.usesCurrent=true
end
end
returntext
end
localargs=frame:getParent().args
localfields={}
localisNamed=args.yearorargs.year1orargs.year2or
args.monthorargs.month1orargs.month2or
args.dayorargs.day1orargs.day2
ifisNamedthen
fields[1]=args.year1orargs.year
fields[2]=args.month1orargs.month
fields[3]=args.day1orargs.day
fields[4]=args.year2
fields[5]=args.month2
fields[6]=args.day2
else
fori=1,6do
fields[i]=args[i]
end
end
localimax=0
fori=1,6do
fields[i]=stripToNil(fields[i])
iffields[i]then
imax=i
end
ifgetopt.omitZeroandi%3~=1then-- omit zero months and days as unknown values but keep year 0 which is 1 BCE
ifisZero(fields[i])then
fields[i]=nil
getopt.partial=true
end
end
end
localfix=getopt.fixand'fix'or''
localpartialText=getopt.partialand'partial'or''
localdates={}
ifisNamedorimax>=3then
localnrDates=getopt.singleand1or2
ifgetopt.wantMixturethen
-- Cannot be partial since empty fields are set from current.
localcomponents={'year','month','day'}
fori=1,nrDates*3do
fields[i]=fields[i]orcurrentDate[components[i>3andi-3ori]]
end
fori=1,nrDatesdo
localindex=i==1and1or4
localy,m,d=fields[index],fields[index+1],fields[index+2]
if(m==2orm=='2')and(d==29ord=='29')then
-- Workaround error with following which attempt to use invalid date 2001-02-29.
-- {{age_ymwd|year1=2001|year2=2004|month2=2|day2=29}}
-- {{age_ymwd|year1=2001|month1=2|year2=2004|month2=1|day2=29}}
-- TODO Get rid of wantMixture because even this ugly code does not handle
-- 'Feb' or 'February' or 'feb' or 'february'.
ifnot((y%4==0andy%100~=0)ory%400==0)then
d=28
end
end
dates[i]=Date(y,m,d)
end
else
-- If partial dates are allowed, accept
-- 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).
fori=1,nrDatesdo
localindex=i==1and1or4
localy,m,d=fields[index],fields[index+1],fields[index+2]
if(getopt.partialandyand(mornotd))or(yandmandd)then
dates[i]=Date(fix,partialText,y,m,d)
elseifnotyandnotmandnotdthen
dates[i]=Date(flagCurrent())
end
end
end
else
getopt.textdates=true-- have parsed each date from a single text field
dates[1]=Date(fix,partialText,flagCurrent(fields[1]))
ifnotgetopt.singlethen
dates[2]=Date(fix,partialText,flagCurrent(fields[2]))
end
end
ifnotdates[1]then
returnmessage(getopt.missing1or'mt-need-valid-ymd')
end
ifgetopt.singlethen
returndates[1]
end
ifnotdates[2]then
returnmessage(getopt.missing2or'mt-need-valid-ymd2')
end
returndates[1],dates[2]
end
localfunctionageGeneric(frame)
-- Return the result required by the specified template.
-- Can use sortable=x where x = on/table/off/debug in any supported template.
-- Some templates default to sortable=on but can be overridden.
localname=frame.args.template
ifnotnamethen
returnmessage('mt-template-x')
end
localargs=frame:getParent().args
localspecs={
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',
flag='usesCurrent',
omitZero=true,
range='dash',
},
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',
format='format_commas',
sep=args.sep~='and'and'sep_comma'ornil,
},
age_yd_nts={-- {{age in years and days nts}}
show='yd',
format='format_commas',
sep=args.sep~='and'and'sep_comma'ornil,
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',
wantMixture=true,
},
}
localspec=specs[name]
ifnotspecthen
returnmessage('mt-template-bad-name')
end
ifname=='age_days'then
localsu=stripToNil(args['show unit'])
ifsuthen
ifsu=='abbr'orsu=='full'then
spec.disp=nil
spec.abbr=su=='abbr'and'abbr_on'ornil
end
end
end
localpartial,autofill
localrange=stripToNil(args.range)orspec.range
ifrangethen
-- 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]oryes(range)
ifrangethen
partial=true-- accept partial dates with a possible age range for the result
ifrange=='no'then
autofill=true-- missing month/day in first or second date are filled from other date or 1
range=nil
end
end
end
localgetopt={
fix=yes(args.fix),
flag=stripToNil(args.flag)orspec.flag,
omitZero=spec.omitZero,
partial=partial,
wantMixture=spec.wantMixture,
}
localdate1,date2=getDates(frame,getopt)
iftype(date1)=='string'then
returndate1
end
localformat=stripToNil(args.format)
localspell=spellOptions[format]
ifformatthen
format='format_'..format
elseifname=='age_days'andgetopt.textdatesthen
format='format_commas'
end
localparms={
diff=date2:subtract(date1,{fill=autofill}),
wantDuration=spec.durationoryes(args.duration),
range=range,
wantSc=yes(args.sc),
show=args.show=='hide'and'hide'orspec.show,
abbr=spec.abbr,
disp=spec.disp,
extra=makeExtra(args,getopt.usesCurrentandformat~='format_raw'),
format=formatorspec.format,
round=yes(args.round),
sep=spec.sep,
sortable=translateParameters.sortable[args.sortableorspec.sortable],
spell=spell,
}
if(spec.negativeorframe.args.negative)=='error'andparms.diff.isnegativethen
returnmessage('mt-date-wrong-order')
end
returnfrom_en(dateDifference(parms))
end
localfunctionisFake(args)
-- Some templates have TemplateData with an auto value like "{{Birth date and age|YYYY|MM|DD}}".
-- Return true if that appears to be the case so the caller can output nothing rather than an error.
returnargs[1]=='YYYY'
end
localfunctionbda(frame)
-- Implement [[Template:Birth date and age]].
localargs=frame:getParent().args
ifisFake(args)then
return''
end
localoptions={
missing1='mt-need-valid-bd',
noMissing=true,
single=true,
}
localdate=getDates(frame,options)
iftype(date)=='string'then
returndate-- error text
end
localDate=getExports(frame)
localdiff=Date('currentdate')-date
ifdiff.isnegativeordiff.years>150then
returnmessage('mt-invalid-bd-age')
end
localdisp=mtext['txt-bda-disp']
localshow='y'
ifdiff.years<2then
disp='disp_age'
ifdiff.years==0anddiff.months==0then
show='d'
else
show='m'
end
end
localresult=substituteParameters(
mtext['txt-bda'],
date:text('%-Y-%m-%d'),
from_en(date:text(dateFormat(args))),
from_en(dateDifference({
diff=diff,
show=show,
abbr='abbr_off',
disp=disp,
sep='sep_space',
}))
)
localwarnings=tonumber(frame.args.warnings)
ifwarningsandwarnings>0then
localgood={
df=true,
mf=true,
day=true,
day1=true,
month=true,
month1=true,
year=true,
year1=true,
}
localinvalid
localimax=options.textdatesand1or3
fork,_inpairs(args)do
iftype(k)=='number'then
ifk>imaxthen
invalid=tostring(k)
break
end
else
ifnotgood[k]then
invalid=k
break
end
end
end
ifinvalidthen
result=result..message('mt-bad-param1',invalid)
end
end
returnresult
end
localfunctiondda(frame)
-- Implement [[Template:Death date and age]].
localargs=frame:getParent().args
ifisFake(args)then
return''
end
localoptions={
missing1='mt-need-valid-dd',
missing2='mt-need-valid-bd2',
noMissing=true,
partial=true,
}
localdate1,date2=getDates(frame,options)
iftype(date1)=='string'then
returndate1
end
localdiff=date1-date2
ifdiff.isnegativethen
returnmessage('mt-dd-wrong-order')
end
localDate=getExports(frame)
localtoday=Date('currentdate')+1-- one day in future allows for timezones
ifdate1>todaythen
returnmessage('mt-dd-future')
end
localyears
ifdiff.partialthen
years=diff.partial.years
years=type(years)=='table'andyears[2]oryears
else
years=diff.years
end
ifyears>150then
returnmessage('mt-invalid-dates-age')
end
localfmt_date,fmt_ymd
ifdate1.daythen-- y, m, d known
fmt_date=dateFormat(args)
fmt_ymd='%-Y-%m-%d'
elseifdate1.monththen-- y, m known; d unknown
fmt_date='%B %-Y'
fmt_ymd='%-Y-%m-00'
else-- y known; m, d unknown
fmt_date='%-Y'
fmt_ymd='%-Y-00-00'
end
localsortKey
localsortable=translateParameters.sortable[args.sortable]
ifsortablethen
localvalue=(date1.partialanddate1.partial.firstordate1).jdz
sortKey=makeSort(value,sortable)
end
localresult=(sortKeyor'')..substituteParameters(
mtext['txt-dda'],
date1:text(fmt_ymd),
from_en(date1:text(fmt_date)),
from_en(dateDifference({
diff=diff,
show='y',
abbr='abbr_off',
disp=mtext['txt-dda-disp'],
range='dash',
sep='sep_space',
}))
)
localwarnings=tonumber(frame.args.warnings)
ifwarningsandwarnings>0then
localgood={
df=true,
mf=true,
}
localinvalid
localimax=options.textdatesand2or6
fork,_inpairs(args)do
iftype(k)=='number'then
ifk>imaxthen
invalid=tostring(k)
break
end
else
ifnotgood[k]then
invalid=k
break
end
end
end
ifinvalidthen
result=result..message('mt-bad-param1',invalid)
end
end
returnresult
end
localfunctiondateToGsd(frame)
-- Implement [[Template: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.
localdate=getDates(frame,{wantMixture=true,single=true})
iftype(date)=='string'then
returndate
end
returntostring(date.gsd)
end
localfunctionjdToDate(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.
localDate=getExports(frame)
localargs=frame:getParent().args
localdate=Date('juliandate',args[1],args[2])
ifdatethen
returnfrom_en(date:text())
end
returnmessage('mt-need-jdn')
end
localfunctiondateToJd(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.
localDate=getExports(frame)
localargs=frame:getParent().args
localdate=Date(args[1],args[2],args[3],args[4],args[5],args[6],args[7])
ifdatethen
returntostring(date.jd)
end
returnmessage('mt-need-valid-ymd-current')
end
localfunctiontimeInterval(frame)
-- Implement [[Template:Time interval]].
-- There are two positional arguments: date1, date2.
-- The default for each is the current date and time.
-- Result is date2 - date1 formatted.
localDate=getExports(frame)
localargs=frame:getParent().args
localparms={
extra=makeExtra(args),
wantDuration=yes(args.duration),
range=yes(args.range)or(args.range=='dash'and'dash'ornil),
wantSc=yes(args.sc),
}
localfix=yes(args.fix)and'fix'or''
localdate1=Date(fix,'partial',stripToNil(args[1])or'currentdatetime')
ifnotdate1then
returnmessage('mt-invalid-start')
end
localdate2=Date(fix,'partial',stripToNil(args[2])or'currentdatetime')
ifnotdate2then
returnmessage('mt-invalid-end')
end
parms.diff=date2-date1
forargname,translateinpairs(translateParameters)do
localparm=stripToNil(args[argname])
ifparmthen
parm=translate[parm]
ifparm==nilthen-- test for nil because false is a valid setting
returnmessage('mt-bad-param2',argname,args[argname])
end
parms[argname]=parm
end
end
ifparms.roundthen
localround=parms.round
localshow=parms.show
ifround~='on'then
ifshowthen
ifshow.id~=roundthen
returnmessage('mt-conflicting-show',args.show,args.round)
end
else
parms.show=translateParameters.show[round]
end
end
parms.round=true
end
returnfrom_en(dateDifference(parms))
end
return{
age_generic=ageGeneric,-- can emulate several age templates
birth_date_and_age=bda,-- Template:Birth_date_and_age
death_date_and_age=dda,-- Template:Death_date_and_age
gsd=dateToGsd,-- Template:Gregorian_serial_date
extract=dateExtract,-- Template:Extract
jd_to_date=jdToDate,-- Template:?
JULIANDAY=dateToJd,-- Template:JULIANDAY
time_interval=timeInterval,-- Template:Time_interval
}