Module:Age/sandbox
See also the companion subpage for test cases (run).
To avoid major disruption and server load, any changes should be tested in the module's /sandbox or /testcases subpages, or in your own module sandbox. The tested changes can be added to this page in a single edit. Consider discussing changes on the talk page before implementing them.
Templates supported
[edit ]Module:Age implements 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
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
[edit ]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}}→ −13 years, 9 months{{age in years and months||||2012|2|20}}→ −13 years, 9 months{{age in years and months||2012年2月20日}}→ −13 years, 9 months{{age in years and months||20 Feb 2012}}→ −13 years, 9 months{{age in years and months||Feb 20, 2012}}→ −13 years, 9 months{{age in years and months|year1=2001|month1=1|day1=10}}→ 24 years, 11 months{{age in years and months|year=2001|month=1|day=10}}→ 24 years, 11 months{{age in years and months|2001|1|10}}→ 24 years, 11 months{{age in years and months|2001年1月10日}}→ 24 years, 11 months{{age in years and months|10 Jan 2001}}→ 24 years, 11 months{{age in years and months|January 10, 2001}}→ 24 years, 11 months
Parameters
[edit ]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. |
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. See below. |
format=ordinal |
Display the resulting number using words such as "fifth" instead of 5. See below. |
prefix=text |
Insert the given text before 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 an en 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 |
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=table |
Insert a sort key using table syntax data-sort-value="value"|.
|
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=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
[edit ]The templates that use age_generic can display numbers in words rather than using numerals. The result can be a cardinal number (such as "five") or an ordinal 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
[edit ]Localization
[edit ]Inputs and outputs can be localized to suit the language used. Examples are at bnwiki and bswiki.
See also
[edit ]- {{time interval }} • This template supports all age/duration calculations and provides more options such as abbreviating or omitting units.
Editors can experiment in this module's sandbox (edit | diff) and testcases (edit | run) pages.
Add categories to the /doc subpage. Subpages of this module.
-- 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-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', ['mt-warn-param1']='Invalid parameter 1ドル', ['mt-warn-param2']='Parameter 1ドル=2ドル is invalid', ['txt-affirmative']={y=true,yes=true,Y=true,Yes=true,YES=true},-- valid values for df + mf parameters ['txt-yes']={y=true,yes=true,on=true},-- valid values for parameters introduced with this module ['txt-and']=' and ', ['txt-or']=' or ', ['txt-category']='Category:Pages using age template with invalid date', ['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-warn-param1']=true, ['mt-warn-param2']=true, } -- yes[parameter] is true if parameter should be interpreted as "yes". -- Do not want to accept mixed upper/lowercase unless done by previously used templates. -- Need to accept "on" because "round=on" is wanted. localyes=mtext['txt-yes'] -- Max valid age. localMAX_AGE=130 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 localfunctionsubstituteParameters(text,...) -- Return text after substituting any given parameters for 1,ドル 2,ドル etc. returnmw.message.newRawMessage(text,...):plain() 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 localfunctiondateFormat(args,options) -- Return -- nil, f if parameter is valid -- m, f otherwise -- where -- m = string for warning message with category -- f = string for wanted date format localwanted ifoptions.suppliedformat=='dmy'then wanted='df' elseifoptions.suppliedformat=='mdy'then wanted='mf' else wanted=mtext['txt-format-default'] end localproblem localother=wanted=='df'and'mf'or'df' localparm=args[other]or'' ifmtext['txt-affirmative'][parm]then wanted=other elseifparm~=''then problem=message('mt-warn-param2',other,parm) end returnproblem,wanted=='df'andmtext['txt-dmy']ormtext['txt-mdy'] 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.textdatesthen getopt.suppliedformat=dates[1].format 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>MAX_AGEthen 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 localproblem,format=dateFormat(args,options) localresult=substituteParameters( mtext['txt-bda'], date:text('%-Y-%m-%d'), from_en(date:text(format)), from_en(dateDifference({ diff=diff, show=show, abbr='abbr_off', disp=disp, sep='sep_space', })) )..(problemor'') 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-warn-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>MAX_AGEthen returnmessage('mt-invalid-dates-age') end localfmt_date,fmt_ymd,problem ifdate1.daythen-- y, m, d known problem,fmt_date=dateFormat(args,options) 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', })) )..(problemor'') 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-warn-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 localfunctiontemplateGeneric(frame) -- Example: {{#invoke:age||template=age_days|1 Apr 1980|1 Apr 2080|format=raw}} → 36525 localname=frame.args.template ifnotnamethen returnmessage('mt-template-x') end returnageGeneric(frame:newChild({title=mw.title.new(name,10),args=frame.args})) 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 ['']=templateGeneric,-- same as age_generic, but can be invoked directly }