Module:Sandbox/RexxS/CalcDate
< Module:Sandbox | RexxS
--[[ CalcDate
Modified from chunks of Module:Time
Main function: getYearMonthDay
takes a date string like "second Friday in June" as parameter |date=
and a year as parameter |year= (uses current year if omitted)
and returns the date in YYYY-MM-DD format, e.g. 2018-06-08
Other formats are possible.
]]
require ('strict')
--[[ is_set(var)
Whether variable is set or not. A variable is set when it is not nil and not empty.
]]
local function is_set(var)
return var and var ~= ''
end
--[[ current_year()
returns the current year
]]
local function current_year()
return os.date('%Y', os.time())
end
--[[ decode_date_event(date_event_string)
extract ordinal, day-name, and month from daylight saving start/end definition string as digits:
Second Sunday in March
returns
2 0 3
Case doesn't matter but the form of the string does:
<ordinal> <day> <any single word> <month> – all are separated by spaces
]]
local function decode_date_event(date_event_string)
local ordinals = {
['1st'] = 1, ['first'] = 1, ['2nd'] = 2, ['second'] = 2, ['3rd'] = 3, ['third'] = 3,
['4th'] = 4, ['fourth'] = 4, ['5th'] = 5, ['fifth'] = 5, ['last'] = -1
}
local days = {
['sunday'] = 0, ['monday'] = 1, ['tuesday'] = 2, ['wednesday'] = 3, ['thursday'] = 4, ['friday'] = 5, ['saturday'] = 6
}
local months = {
['january'] = 1, ['february'] = 2, ['march'] = 3, ['april'] = 4, ['may'] = 5,
['june'] = 6, ['july'] = 7, ['august'] = 8, ['september'] = 9, ['october'] = 10,
['november'] = 11, ['december'] = 12
}
date_event_string = date_event_string:lower()
local ord, day, month = date_event_string:match ('([%a%d]+)%s+(%a+)%s+%a+%s+(%a+)')
if is_set(ord) and is_set(day) and is_set(month) then
return ordinals[ord], days[day], months[month]
else
-- if one or more of these not set, then pattern didn't match
return nil
end
end
--[[ get_days_in_month(year, month)
Returns the number of days in the month where month is a number 1–12
and year is four-digit Gregorian calendar.
Accounts for leap year. Throws an error if month and year are not numbers.
]]
local function get_days_in_month(year, month)
local days_in_month = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
year = tonumber (year)
month = tonumber (month)
if month == 2 and year%4 == 0 and (year%100 ~= 0 or year%400 == 0) then
return 29
end
return days_in_month [month]
end
--[[ get_date_month_day(date_event_string, year)
Return the date for the day that is the ordinal (nth) day-name in month of the given year
e.g. "second Friday in June", 2018 -> 2018-06-08
]]
local function get_date_month_day(date_event_string, year)
local ord, weekday_num, month = decode_date_event(date_event_string)
if not (is_set (ord) and is_set (weekday_num) and is_set (month)) then
return nil
end
if ord == -1 then
-- event occurs on the last day-name of the month
-- j = t + 7×(n + 1) - (wt - w) mod 7
local days_in_month = get_days_in_month (year, month)
local ostime = os.time ({['year']=year, ['month']=month, ['day']=days_in_month})
local last_day_of_month = os.date('%w', ostime)
return month, days_in_month + 7 * (ord + 1) - ((last_day_of_month - weekday_num) % 7)
else
-- j = 7×n - 6 + (w - w1) mod 7
local ostime = os.time({['year']=year, ['month']=month, ['day']=1})
local first_day_of_month = os.date('%w', ostime)
return month, 7 * ord - 6 + (weekday_num - first_day_of_month) % 7
end
end
-- set up public functions for debugging
local p = {}
p.currentYear = current_year
p.getDateMonth = function(frame)
local m, d = get_date_month_day(frame.args.date, frame.args.year)
return m .. "-" .. d
end
--[[ getYearMonthDay
Takes a date string like "second Friday in June" as parameter |date=
and a year as parameter |year=
and returns the date in YYYY-MM-DD format, e.g. 2018-06-08
Other formats are easy to implement.
]]
p.getYearMonthDay = function(frame)
local args = frame.args or frame:getParent().args
local date_event = args.date or mw.text.trim(args[1])
local year = args.year or args[2] or current_year()
local month, day = get_date_month_day(date_event, year)
month = (month<10 and "0" or "") .. month
day = (day<10 and "0" or "") .. day
return year .. "-" .. month .. "-" .. day
end
return p