Module:Calendar
From WWII Archives
Documentation for this module may be created at Module:Calendar/doc
--[[ __ __ _ _ ____ _ _ | \/ | ___ __| |_ _| | ___ _ / ___|__ _| | ___ _ __ __| | __ _ _ __ | |\/| |/ _ \ / _` | | | | |/ _ (_) | / _` | |/ _ \ '_ \ / _` |/ _` | '__| | | | | (_) | (_| | |_| | | __/_| |__| (_| | | __/ | | | (_| | (_| | | |_| |_|\___/ \__,_|\__,_|_|\___(_)\____\__,_|_|\___|_| |_|\__,_|\__,_|_| This module is intended for date conversion between Gregorian and Julian calendars Please do not modify this code without applying the changes first at Module:Calendar/sandbox and testing at Module:Calendar/sandbox/testcases Maintainers: * Jarekt ]] require('strict') -- ================================================== -- === External functions =========================== -- ================================================== local p = {} -- =========================================================================== -- === Version of the function to be called from other LUA codes -- =========================================================================== ----------------------------------------------------------------------------- --[[ Convert calendar date to "Julian day number" (jdn) code based on https://en.wikipedia.org/wiki/Julian_day#Converting_Julian_or_Gregorian_calendar_date_to_Julian_day_number explanation based on http://www.cs.utsa.edu/~cs1063/projects/Spring2011/Project1/project1.html Example usage: jdn = _date2jdn("2017-09-01", 1) Inputs: ISOdate: date in YYYY-MM-DD format gregorian: int - "1" for gregorian calendar and "0" for Julian (optional with "1" as default) Outputs: 1: "Julian day number" as integer or number of days since Monday, January 1, 4713 BC ]] function p._date2jdn(ISOdate, gregorian) ISOdate = mw.text.trim(ISOdate) if (#ISOdate<10 or #ISOdate>11) then return nil end -- date string has to start with YYYY-MM-DD date, possibly with a "-" in front local year, month, day = ISOdate:match( "^(-?%d%d%d%d)-(%d%d)-(%d%d)" ) if not year then return nil elseif tonumber(year) < 0 then -- If year is less than 0, add one to convert from the common era system in which -- the year -1 (1 B.C.E) is followed by year 1 (1 C.E.) to a zero based date system year = year + 1 end local a, b, c, d, y, m a = math.floor((14-month) / 12) -- will be 1 for January and February, and 0 for other months. y = year + 4800 - a -- years since year –4800 m = month + 12*a - 3 -- month number where 10 for January, 11 for February, 0 for March, 1 for April c = math.floor((153*m + 2)/5) -- number of days since March 1 if (gregorian or 1) >0 then b = math.floor(y/4) - math.floor(y/100) + math.floor(y/400) -- number of leap years since y==0 (year –4800) d = 32045 -- offset so the result will be 0 for January 1, 4713 BCE else b = math.floor(y/4) -- number of leap years since y==0 (year –4800) d = 32083 -- offset so the result will be 0 for January 1, 4713 BCE end return day + c + 365*y + b - d end ----------------------------------------------------------------------------- --[[ Convert "Julian day number" (jdn) to a calendar date code based on https://en.wikipedia.org/wiki/Julian_day#Converting_Julian_or_Gregorian_calendar_date_to_Julian_day_number explanation based on http://www.cs.utsa.edu/~cs1063/projects/Spring2011/Project1/project1.html Example usage: jdn = _date2jdn("2017-09-01", 1) Inputs: jdn: integer - Julian day number or number of days since Monday, January 1, 4713 BC gregorian: int - "1" for gregorian calendar and "0" for Julian (optional with "1" as default) Outputs: 1: date in YYYY-MM-DD forma ]] -- Convert "Julian day number" (jdn) to a calendar date -- "gregorian" is a 1 for gregorian calendar and 0 for Julian -- based on https://en.wikipedia.org/wiki/Julian_day#Converting_Julian_or_Gregorian_calendar_date_to_Julian_day_number function p._jdn2date(jdn, gregorian) local f, e, g, h, year, month, day f = jdn + 1401 if (gregorian or 1) >0 then f = f + math.floor((math.floor((4*jdn + 274277) / 146097) * 3) / 4) - 38 end e = 4*f + 3 g = math.floor(math.fmod(e, 1461) / 4) h = 5*g + 2 day = math.floor(math.fmod (h,153) / 5) + 1 month = math.fmod (math.floor(h/153) + 2, 12) + 1 year = math.floor(e/1461) - 4716 + math.floor((14 - month) / 12) -- If year is less than 1, subtract one to convert from a zero based date system to the -- common era system in which the year -1 (1 B.C.E) is followed by year 1 (1 C.E.). if year < 1 then year = year - 1 end return string.format('%04i-%02i-%02i', year, month, day) end --[[ Tests if a string is a valid Gregorian date Example usage: jdn = _valid_date("2017-02-30") Inputs: ISOdate: date in YYYY-MM-DD string format Outputs: true or false ]] function p._valid_date(ISOdate) local jdn = p._date2jdn(ISOdate, 1) if jdn then return (p._jdn2date(jdn, 1)==ISOdate) else return true -- date is not in YYYY-MM-DD format do not flag as invalid end end -- =========================================================================== -- === Versions of the function to be called from template namespace -- =========================================================================== --[[ Gregorian2Julian Convert a date from Gregorian to Julian calendar Usage: {{#invoke:Calendar|Gregorian2Julian|YYYY-MM-DD}} Parameters: 1: Gregorian date in YYYY-MM-DD format Output: 1: Julian date in YYYY-MM-DD format or error message "Error parsing input date: ....." ]] function p.Gregorian2Julian(frame) local jdn = p._date2jdn(frame.args[1], 1) if jdn then return p._jdn2date(jdn, 0) else return "Error parsing input date: " .. frame.args[1] end end ----------------------------------------------------------------------------- --[[ Julian2Gregorian Convert a date from Julian to Gregorian calendar Usage: {{#invoke:Calendar|Julian2Gregorian|YYYY-MM-DD}} Parameters: 1: Julian date in YYYY-MM-DD format Output: 1: Gregorian date in YYYY-MM-DD format or error message "Error parsing input date: ....." ]] function p.Julian2Gregorian(frame) local jdn = p._date2jdn(frame.args[1], 0) if jdn then return p._jdn2date(jdn, 1) else return "Error parsing input date: " .. frame.args[1] end end ----------------------------------------------------------------------------- --[[ DayOfWeek Return day of week based on gregorian date. Returned value is in English. However one can easily combine it with #Switch parser function [w:Help:Switch parser function] to translate to other languages Usage: {{#invoke:Calendar|DayOfWeek|date|lang=}} Parameters: 1: date in YYYY-MM-DD format using Gregorian calendar Output: 1: day of the week in English or error message "Error parsing input date: ....." ]] function p.DayOfWeek(frame) local jdn = p._date2jdn(frame.args[1], 1) local day = math.fmod(jdn, 7) + 1 if day then local LUT = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" } return LUT[day] else return "Error parsing input date: " .. frame.args[1] end end ----------------------------------------------------------------------------- --[[ date2jdn Convert calendar date to "Julian day number" (jdn) Usage: {{#invoke:Calendar|date2jdn|date}} Parameters: 1: date in YYYY-MM-DD format (optional, if missing than today's date will be used) 2: calendar: "1" for Gregorian calendar and "0" for Julian (optional with "1" as default) Output: 1: "Julian day number" as integer or number of days since Monday, January 1, 4713 BC ]] function p.date2jdn(frame) return p._date2jdn(frame.args[1] or os.date('%F'), frame.args[2] or 1) end ----------------------------------------------------------------------------- --[[ jdn2date Convert "Julian day number" (jdn) to calendar date Usage: {{#invoke:Calendar|jdn2date|jdn}} Parameters: 1: "Julian day number" as integer or number of days since Monday, January 1, 4713 BC 2: calendar: "1" for gregorian calendar and "0" for Julian (optional with "1" as default) Output: 1: date in YYYY-MM-DD format ]] function p.jdn2date(frame) return p._jdn2date(frame.args[1], frame.args[2] or 1) end --[[ Tests if a string is a valid Gregorian date Example usage: {{#invoke:Calendar|valid_date|"2017-02-30"}} Inputs: ISOdate: date in YYYY-MM-DD string format Outputs: true or false ]] function p.valid_date(frame) if p._valid_date(frame.args[1]) then return 'date is valid' else return 'date is not valid' end end return p