Module:Age
- अंगिका
- العربية
- Արեւմտահայերէն
- অসমীয়া
- Авар
- Azərbaycanca
- تۆرکجه
- Basa Bali
- বাংলা
- 閩南語 / Bân-lâm-gí
- Беларуская (тарашкевіца)
- भोजपुरी
- Bikol Central
- Български
- Bosanski
- Буряад
- Cebuano
- Chavacano de Zamboanga
- Cymraeg
- Dansk
- الدارجة
- Eesti
- Ελληνικά
- Español
- فارسی
- Føroyskt
- Français
- Gĩkũyũ
- ગુજરાતી
- गोंयची कोंकणी / Gõychi Konknni
- 한국어
- Hausa
- Հայերեն
- हिन्दी
- Hrvatski
- Ilokano
- Bahasa Indonesia
- Íslenska
- Jawa
- ಕನ್ನಡ
- ქართული
- کٲشُر
- Қазақша
- Ikinyarwanda
- Kiswahili
- Kurdî
- Magyar
- Madhurâ
- मैथिली
- Македонски
- മലയാളം
- मराठी
- ဘာသာမန်
- مازِرونی
- Bahasa Melayu
- Mfantse
- Мокшень
- Монгол
- မြန်မာဘာသာ
- Nederlands
- नेपाली
- 日本語
- Norsk bokmål
- Norsk nynorsk
- ଓଡ଼ିଆ
- Oʻzbekcha / ўзбекча
- ਪੰਜਾਬੀ
- Pangcah
- ပအိုဝ်ႏဘာႏသာႏ
- Papiamentu
- پښتو
- Português
- Qaraqalpaqsha
- Română
- Русиньскый
- Русский
- संस्कृतम्
- Scots
- Shqip
- සිංහල
- Simple English
- سنڌي
- Slovenščina
- کوردی
- Српски / srpski
- Srpskohrvatski / српскохрватски
- Suomi
- Tagalog
- தமிழ்
- Taqbaylit
- တႆး
- తెలుగు
- ไทย
- ತುಳು
- Türkçe
- Türkmençe
- Українська
- اردو
- Vahcuengh
- Tiếng Việt
- 吴语
- Yorùbá
- 粵語
- 中文
- Kumoring
See the protection policy and protection log for more details. Please discuss any changes on the talk page; you may submit an edit request to ask an administrator to make an edit if it is uncontroversial or supported by consensus. You may also request that this page be unprotected.
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
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
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, 8 months{{age in years and months||||2012|2|20}}→ −13 years, 8 months{{age in years and months||2012年2月20日}}→ −13 years, 8 months{{age in years and months||20 Feb 2012}}→ −13 years, 8 months{{age in years and months||Feb 20, 2012}}→ −13 years, 8 months{{age in years and months|year1=2001|month1=1|day1=10}}→ 24 years, 10 months{{age in years and months|year=2001|month=1|day=10}}→ 24 years, 10 months{{age in years and months|2001|1|10}}→ 24 years, 10 months{{age in years and months|2001年1月10日}}→ 24 years, 10 months{{age in years and months|10 Jan 2001}}→ 24 years, 10 months{{age in years and months|January 10, 2001}}→ 24 years, 10 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. 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
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
Localization
Inputs and outputs can be localized to suit the language used. Examples are at bnwiki and bswiki.
See also
- {{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.
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) -- Return -- nil, f if parameter is valid -- m, f otherwise -- where -- m = string for warning message with category -- f = string for wanted date format localproblem localwanted=mtext['txt-format-default'] 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.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) 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) 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) 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 }