Module:Sandbox/RexxS/CalcDate

--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:	   – 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 --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