Module:Age: Difference between revisions

3,898 bytes added ,  11 years ago
add support for {{Age in days}} and {{Gregorian serial date}}
(implement age in years and months; could readily extend for other similar functions)
 
(add support for {{Age in days}} and {{Gregorian serial date}})
Line 1:
--[[ Code for some date functions, including implementations of:
-- Code to implement {{age in years and months}}.
{{Age in days}} age_days
-- This could be extended to handle the many other {{age}} templates.
{{Age in years and months}} age_ym
-- However, calendar functions will be needed in many areas, so this may be
{{Gregorian serial date}} gsd_ymd
-- superseded by some other system, perhaps using functions from the PHP host.
Calendar functions will be needed in many areas, so this may be superseded
by some other system, perhaps using PHP functions accessed via mw.
]]
 
local MINUS = '−' -- Unicode U+2212 MINUS SIGN (UTF-8: e2 88 92)
 
local function number_name(number, singular, plural, sep)
-- Return the given number, converted to a string, with the
-- separator (default space) and singular or plural name appended.
plural = plural or (singular .. 's')
sep = sep or ' '
Line 12 ⟶ 17:
end
 
local function stripstrip_to_nil(text)
-- If text is a non-blank string, return its content with no leading/trailing
-- or trailing whitespace.
-- whitespace. Otherwise return nil (a nil argument gives a nil result).
-- Otherwise return nil (a nil or empty string argument gives a nil
-- result, as does a string argument of only whitespace).
if type(text) == 'string' then
returnlocal result = text:match("^%s*(.-)%s*$")
if result ~= '' then
return result
end
end
return nil
end
 
Line 32 ⟶ 43:
end
return month_days[month]
end
 
-- A table to get current year/month/day (UTC), but only if needed.
local current = setmetatable({}, {
__index = function (self, key)
local d = os.date('!*t')
self.year = d.year
self.month = d.month
self.day = d.day
return rawget(self, key)
end
})
 
local function date_component(named, positional, component)
-- Return the first of the two arguments that is not nil and is not empty.
-- If both are nil, return the current date component, if specified.
-- The returned value is nil or is a number.
-- This translates empty arguments passed to the template to nil, and
-- optionally replaces a nil argument with a value from the current date.
named = strip_to_nil(named)
if named then
return tonumber(named)
end
positional = strip_to_nil(positional)
if positional then
return tonumber(positional)
end
if component then
return current[component]
end
return nil
end
 
local function gsd(year, month, day)
-- Return the Gregorian serial day (an integer >= 1) for the given date,
-- or return nil if the date is invalid (only check that year >= 1).
-- This is the number of days from the start of 1 AD (there is no year 0).
-- This code implements the logic in [[Template:Gregorian serial date]].
if year < 1 then
return nil
end
local floor = math.floor
local days_this_year = (month - 1) * 30.5 + day
if month > 2 then
if is_leap_year(year) then
days_this_year = days_this_year - 1
else
days_this_year = days_this_year - 2
end
if month > 8 then
days_this_year = days_this_year + 0.9
end
end
days_this_year = floor(days_this_year + 0.5)
year = year - 1
local days_from_past_years = year * 365
+ floor(year / 4)
- floor(year / 100)
+ floor(year / 400)
return days_from_past_years + days_this_year
end
 
Line 47 ⟶ 118:
end
}
 
function Date:__lt(rhs)
-- Return true if self < rhs.
if self.year < rhs.year then
return true
end
if self.year == rhs.year then
if self.month < rhs.month then
return true
end
if self.month == rhs.month then
return self.day < rhs.day
end
end
return false
end
 
function Date:set_current()
-- Set date from current time (UTC) and return self.
local dself.year = oscurrent.date('!*t')year
self.yearmonth = dcurrent.yearmonth
self.monthday = dcurrent.monthday
self.day = d.day
self.isvalid = true
return self
Line 72 ⟶ 158:
end
return self
end
 
function Date:less_than(rhs)
-- Set true if self < rhs.
if self.year < rhs.year then
return true
end
if self.year == rhs.year then
if self.month < rhs.month then
return true
end
if self.month == rhs.month then
return self.day < rhs.day
end
end
return false
end
 
Line 108 ⟶ 178:
-- Difference is negative if the second date is older than the first.
local isnegative
if date2:less_than( < date1) then
isnegative = true
date1, date2 = date2, date1
Line 153 ⟶ 223:
local function error_wikitext(text)
-- Return message for display when template parameters are invalid.
-- TODO Get opinions on whether the prefix and category are suitable.
local prefix = '[[Module talk:Age|Module error]]:'
local cat = '[[Category:Age error]]'
Line 160 ⟶ 229:
end
 
local function get_parmsage_days(pframeframe)
-- Return date1,age date2in fromdays parametersbetween totwo given dates, template.or
-- between given date and current date.
-- If date1 is missing or invalid, the returned date1 is nil.
-- IfThis date2code is missing,implements the returnedlogic date2in is the[[Template:Age currentin datedays]].
-- IfLike date2{{Age isin invaliddays}}, thea returnedmissing date2argument is nil.replaced from the current
-- date, so can get a bizarre mixture of specified/current y/m/d.
local args = {} -- arguments passed to template
local args = frame:getParent().args
local year1 = date_component(args.year1 , args[1], 'year' )
local month1 = date_component(args.month1, args[2], 'month')
local day1 = date_component(args.day1 , args[3], 'day' )
local year2 = date_component(args.year2 , args[4], 'year' )
local month2 = date_component(args.month2, args[5], 'month')
local day2 = date_component(args.day2 , args[6], 'day' )
local gsd1 = gsd(year1, month1, day1)
local gsd2 = gsd(year2, month2, day2)
if gsd1 and gsd2 then
local sign = ''
local result = gsd2 - gsd1
if result < 0 then
sign = MINUS
result = -result
end
return sign .. tostring(result)
end
return error_wikitext('Cannot handle dates before the year 1 AD')
end
 
local function age_ym(frame)
-- Return age in years and months between two given dates, or
-- between given date and current date.
local args = frame:getParent().args
local fields = {}
for i = 1, 6 do
argsfields[i] = stripstrip_to_nil(pframe.args[i])
end
local date1, date2
if argsfields[1] and argsfields[2] and argsfields[3] then
date1 = Date:new():set_ymd(argsfields[1], argsfields[2], argsfields[3])
if not date1.isvalid then
date1 = nil
end
end
if args[4]not and args[5](date1 and args[6]date1.isvalid) then
return error_wikitext('Need date: year, month, day')
date2 = Date:new():set_ymd(args[4], args[5], args[6])
end
if fields[4] and fields[5] and fields[6] then
date2 = Date:new():set_ymd(fields[4], fields[5], fields[6])
if not date2.isvalid then
return error_wikitext('Second date should be year, month, day')
date2 = nil
end
else
date2 = Date:new():set_current()
end
return DateDiff:new():set(date1, date2):age_ym()
end
 
local function age_ymgsd_ymd(frame)
-- Return Gregorian serial day of the given date, or the current date.
local date1, date2 = get_parms(frame:getParent())
-- Like {{Gregorian serial date}}, a missing argument is replaced from the
if date1 then
-- current date, so can get a bizarre mixture of specified/current y/m/d.
if not date2 then
-- This accepts positional arguments, although the original template does not.
return error_wikitext('Second date should be year, month, day')
local args = frame:getParent().args
end
local diffyear = DateDiff:newdate_component():set(date1args.year , date2args[1], 'year' )
local month = date_component(args.month, args[2], 'month')
return diff:age_ym()
local day = date_component(args.day , args[3], 'day' )
local result = gsd(year, month, day)
if result then
return tostring(result)
end
return error_wikitext('NeedCannot date:handle dates before the year, month,1 dayAD')
end
 
return { age_days = age_days, age_ym = age_ym, gsd = gsd_ymd }
Anonymous user