Modul:Age
Udseende
-- Implement various "age of" and other date-related templates.
local_Date,_current_date
localfunctionget_exports(frame)
-- Return objects exported from the date module or its sandbox.
ifnot_Datethen
localsandbox=frame:getTitle():find('sandbox',1,true)and'/sandbox'or''
localdatemod=require('Module:Date'..sandbox)
_Date=datemod._Date
_current_date=datemod._current
end
return_Date,_current_date
end
localfunctioncollection()
-- Return a table to hold items.
return{
n=0,
add=function(self,item)
self.n=self.n+1
self[self.n]=item
end,
join=function(self,sep)
returntable.concat(self,sep)
end,
}
end
localfunctionstrip_to_nil(text)
-- If text is a string, return its trimmed content, or nil if empty.
-- Otherwise return text (which may, for example, be nil).
iftype(text)=='string'then
text=text:match('(%S.-)%s*$')
end
returntext
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,nocat)
-- Return formatted message text for an error.
-- Can append "#FormattingError" to URL of a page with a problem to find it.
localanchor='<span id= "FormattingError" ></span>'
localcategory
ifnotnocatandmw.title.getCurrentTitle():inNamespaces(0,10)then
-- Category only in namespaces: 0=article, 10=template.
category='[[Category:Age error]]'
else
category=''
end
returnanchor..
'<strong class= "error" >Error: '..
mw.text.nowiki(msg)..
'</strong>'..
category
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()
localpos=0
repeat
places:add(pos)
pos=pos+3
untilpos>=length
places:add(length)
localgroups=collection()
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
localfunctionmake_sort(value,sortable)
-- Return a sort key in a span if specified.
-- Assume value is a valid number which has not overflowed.
ifsortable=='sortable_on'orsortable=='sortable_debug'then
localsortkey
ifvalue==0then
sortkey='5000000000000000000'
else
localmag=math.floor(math.log10(math.abs(value))+1e-14)
localprefix
ifvalue>0then
prefix=7000+mag
else
prefix=2999-mag
value=value+10^(mag+1)
end
sortkey=string.format('%d',prefix)..string.format('%015.0f',math.floor(value*10^(14-mag)))
end
locallhs=sortable=='sortable_debug'and
'<span style= "border:1px solid;display:inline;" class= "sortkey" >'or
'<span style= "display:none" class= "sortkey" >'
returnlhs..sortkey..'♠</span>'
end
end
localtranslate_parameters={
abbr={
off='abbr_off',
on='abbr_on',
},
disp={
age='disp_age',
raw='disp_raw',
},
format={
raw='format_raw',
commas='format_commas',
},
round={
on='on',
yes='on',
months='ym',
weeks='ymw',
days='ymd',
hours='ymdh',
},
sep={
comma='sep_comma',
[',']='sep_comma',
serialcomma='sep_serialcomma',
space='sep_space',
},
show={
hide={id='hide'},
y={'y',id='y'},
ym={'y','m',id='ym'},
ymd={'y','m','d',id='ymd'},
ymw={'y','m','w',id='ymw'},
ymwd={'y','m','w','d',id='ymwd'},
yd={'y','d',id='yd',keepzero=true},
m={'m',id='m'},
md={'m','d',id='md'},
w={'w',id='w'},
wd={'w','d',id='wd'},
h={'H',id='h'},
hm={'H','M',id='hm'},
hms={'H','M','S',id='hms'},
d={'d',id='d'},
dh={'d','H',id='dh'},
dhm={'d','H','M',id='dhm'},
dhms={'d','H','M','S',id='dhms'},
ymdh={'y','m','d','H',id='ymdh'},
ymdhm={'y','m','d','H','M',id='ymdhm'},
ymwdh={'y','m','w','d','H',id='ymwdh'},
ymwdhm={'y','m','w','d','H','M',id='ymwdhm'},
},
sortable={
off=false,
on='sortable_on',
debug='sortable_debug',
},
}
localfunctiondate_extract(frame)
-- Return part of a date after performing an optional operation.
localDate=get_exports(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
localdate=Date(unpack(parms))
ifnotdatethen
returnmessage('Need valid date')
end
localadd=strip_to_nil(args.add)
ifaddthen
foriteminadd:gmatch('%S+')do
date=date+item
ifnotdatethen
returnmessage('Cannot add "'..item..' "')
end
end
end
localprefix,result
localsortable=translate_parameters.sortable[args.sortable]
ifsortablethen
localvalue=(date.partialanddate.partial.firstordate).jdz
prefix=make_sort(value,sortable)
end
localshow=strip_to_nil(args.show)or'dmy'
ifshow~='hide'then
result=date[show]
ifresult==nilthen
result=date:text(show)
elseiftype(result)=='boolean'then
result=resultand'1'or'0'
else
result=tostring(result)
end
end
return(prefixor'')..(resultor'')
end
localfunctionmake_text(values,components,names,options)
-- Return wikitext representing an age or duration.
localtext=collection()
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
ifi==1andoptions.format=='format_commas'then
-- Numbers after the first should be small and not need formatting.
fmt=formatnumber
else
fmt=tostring
end
ifislistthen
localjoin=options.range=='dash'and'–'or' or '
vstr=fmt(v[1])..join..fmt(v[2])
else
vstr=fmt(v)
end
localname=names[components[i]]
ifnamethen
localplural=names.plural
ifnotpluralor(islistandv[2]orv)==1then
plural=''
end
text:add(vstr..sep..name..plural)
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=', and '
else
first=', '
last=' 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.prefixor'')..
sign..
text:join()..
(options.suffixor'')
end
localfunctiondate_difference(parms)
-- Return a formatted date difference using the given parameters
-- which have been validated.
localnames={
abbr_off={
plural='s',
sep=' ',
y='year',
m='month',
w='week',
d='day',
H='hour',
M='minute',
S='second',
},
abbr_on={
y='y',
m='m',
w='w',
d='d',
H='h',
M='m',
S='s',
},
abbr_infant={-- for {{age for infant}}
plural='s',
sep=' ',
y='yr',
m='mo',
w='wk',
d='day',
H='hr',
M='min',
S='sec',
},
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'
localdefault_join
ifabbr~='abbr_off'then
default_join='sep_space'
end
ifnotshowthen
show='ymd'
ifparms.disp=='disp_age'then
ifdiff.years<3then
default_join='sep_space'
ifdiff.years>=1then
show='ym'
else
show='md'
end
else
show='y'
end
end
end
iftype(show)~='table'then
show=translate_parameters.show[show]
end
ifparms.disp=='disp_raw'then
default_join='sep_space'
abbr='abbr_raw'
elseifparms.want_scthen
default_join='sep_serialcomma'
end
localdiff_options={
round=parms.round,
duration=parms.want_duration,
range=parms.rangeandtrueornil,
}
localprefix
ifparms.sortablethen
localvalue=diff.age_days+(parms.want_durationand1or0)-- days and fraction of a day
ifdiff.isnegativethen
value=-value
end
prefix=make_sort(value,parms.sortable)
end
localtext_options={
prefix=prefix,
suffix=parms.suffix,-- not currently used
format=parms.format,
join=parms.sepordefault_join,
isnegative=diff.isnegative,
range=parms.range,
}
ifshow.id=='hide'then
returnprefixor''
end
localvalues={diff:age(show.id,diff_options)}
ifvalues[1]then
returnmake_text(values,show,names[abbr],text_options)
end
returnmessage('Parameter show='..show.id..' is not supported here')
end
localfunctionget_dates(frame,getopt)
-- Parse template parameters and return one of:
-- * date (a date table, if single)
-- * date1, date2 (two date tables, if not single)
-- * text (a string error message)
-- A missing date is replaced with the current date.
-- If want_mixture is true, a missing date component is replaced
-- from the current date, so can get a bizarre mixture of
-- specified/current y/m/d as has been done by some "age" templates.
-- Some results may be placed in table getopt.
localDate,current_date=get_exports(frame)
getopt=getoptor{}
localfix=getopt.fixand'fix'or''
localpartial=getopt.rangeand'partial'or''
localargs=frame:getParent().args
localfields={}
localis_named=args.yearorargs.year1orargs.year2or
args.monthorargs.month1orargs.month2or
args.dayorargs.day1orargs.day2
ifis_namedthen
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]=strip_to_nil(fields[i])
iffields[i]then
imax=i
end
end
localsingle=getopt.single
localdates={}
ifis_namedorimax>2then
localnr_dates=singleand1or2
ifgetopt.want_mixturethen
-- Cannot be partial since empty fields are set from current.
localcomponents={'year','month','day'}
fori=1,nr_dates*3do
fields[i]=fields[i]orcurrent_date[components[i>3andi-3ori]]
end
fori=1,nr_datesdo
localindex=i==1and1or4
dates[i]=Date(fields[index],fields[index+1],fields[index+2])
end
else
fori=1,nr_datesdo
localindex=i==1and1or4
localy,m,d=fields[index],fields[index+1],fields[index+2]
if(partialandy)or(yandmandd)then
dates[i]=Date(fix,partial,y,m,d)
elseifnot(yormord)then
dates[i]=Date('currentdate')
end
end
end
else
getopt.textdates=true
dates[1]=Date(fix,partial,fields[1]or'currentdate')
ifnotsinglethen
dates[2]=Date(fix,partial,fields[2]or'currentdate')
end
end
ifnotdates[1]then
returnmessage('Need valid year, month, day')
end
ifsinglethen
returndates[1]
end
ifnotdates[2]then
returnmessage('Second date should be year, month, day')
end
returndates[1],dates[2]
end
localfunctionage_generic(frame)
-- Return the result required by the specified template.
-- Can use sortable=x where x = on/off/debug in any supported template.
-- Some templates default to sortable=on but can be overridden with sortable=off.
localname=frame.args.template
ifnotnamethen
returnmessage('The template invoking this must have "|template=x" where x is the wanted operation')
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',
},
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,
sortable='on',-- temporarily use sortable for compatibility with old template; talk proposes removing this
},
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',
want_mixture=true,
},
}
localspec=specs[name]
ifnotspecthen
returnmessage('The specified template name is not valid')
end
ifname=='age_days'then
localsu=strip_to_nil(args['show unit'])
ifsuthen
ifsu=='abbr'orsu=='full'then
spec.disp=nil
spec.abbr=su=='abbr'and'abbr_on'ornil
end
end
end
localrange=spec.rangeoryes(args.range)or(args.range=='dash'and'dash'ornil)
localgetopt={
fix=yes(args.fix),
range=range,
want_mixture=spec.want_mixture,
}
localdate1,date2=get_dates(frame,getopt)
iftype(date1)=='string'then
returndate1
end
localformat=strip_to_nil(args.format)
ifformatthen
format='format_'..format
elseifname=='age_days'andgetopt.textdatesthen
format='format_commas'
end
localparms={
diff=date2-date1,
want_duration=spec.durationoryes(args.duration),
range=range,
want_sc=yes(args.sc),
show=args.show=='hide'and'hide'orspec.show,
abbr=spec.abbr,
disp=spec.disp,
format=formatorspec.format,
round=yes(args.round),
sep=spec.sep,
sortable=translate_parameters.sortable[args.sortableorspec.sortable],
}
if(spec.negativeorframe.args.negative)=='error'andparms.diff.isnegativethen
returnmessage('The second date should not be before the first date')
end
returndate_difference(parms)
end
localfunctiondate_to_gsd(frame)
-- This implements {{gregorian serial date}}.
-- Return Gregorian serial date of the given date, or the current date.
-- The returned value is negative for dates before 1 January 1 AD
-- despite the fact that GSD is not defined for such dates.
localdate=get_dates(frame,{want_mixture=true,single=true})
iftype(date)=='string'then
returndate
end
returntostring(date.gsd)
end
localfunctionjd_to_date(frame)
-- Return formatted date from a Julian date.
-- The result includes a time if the input includes a fraction.
-- The word 'Julian' is accepted for the Julian calendar.
localDate=get_exports(frame)
localargs=frame:getParent().args
localdate=Date('juliandate',args[1],args[2])
ifdatethen
returndate:text()
end
returnmessage('Need valid Julian date number')
end
localfunctiondate_to_jd(frame)
-- Return Julian date (a number) from a date which may include a time,
-- or the current date ('currentdate') or current date and time ('currentdatetime').
-- The word 'Julian' is accepted for the Julian calendar.
localDate=get_exports(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('Need valid year/month/day or "currentdate" ')
end
localfunctiontime_interval(frame)
-- This implements {{time interval}}.
-- There are two positional arguments: date1, date2.
-- The default for each is the current date and time.
-- Result is date2 - date1 formatted.
localDate=get_exports(frame)
localargs=frame:getParent().args
localparms={
want_duration=yes(args.duration),
range=yes(args.range)or(args.range=='dash'and'dash'ornil),
want_sc=yes(args.sc),
}
localfix=yes(args.fix)and'fix'or''
localdate1=Date(fix,'partial',strip_to_nil(args[1])or'currentdatetime')
ifnotdate1then
returnmessage('Invalid start date in first parameter')
end
localdate2=Date(fix,'partial',strip_to_nil(args[2])or'currentdatetime')
ifnotdate2then
returnmessage('Invalid end date in second parameter')
end
parms.diff=date2-date1
forargname,translateinpairs(translate_parameters)do
localparm=strip_to_nil(args[argname])
ifparmthen
parm=translate[parm]
ifparm==nilthen-- test for nil because false is a valid setting
returnmessage('Parameter '..argname..'='..args[argname]..' is invalid')
end
parms[argname]=parm
end
end
ifparms.roundthen
localround=parms.round
localshow=parms.show
ifround~='on'then
ifshowthen
ifshow.id~=roundthen
returnmessage('Parameter show='..args.show..' conflicts with round='..args.round)
end
else
parms.show=translate_parameters.show[round]
end
end
parms.round=true
end
returndate_difference(parms)
end
return{
age_generic=age_generic,-- can emulate several age templates
gsd=date_to_gsd,-- Template:Gregorian_serial_date
extract=date_extract,-- Template:Extract
jd_to_date=jd_to_date,-- Template:?
JULIANDAY=date_to_jd,-- Template:JULIANDAY
time_interval=time_interval,-- Template:Time_interval
}