Module:Calendar

--[[ __  __           _       _         ____      _                _             |  \/  | ___   __| |_   _| | ___ _ / ___|__ _| | ___ _ __   __| | __ _ _ __  | |\/| |/ _ \ / _` | | | | |/ _ (_) |   / _` | |/ _ \ '_ \ / _` |/ _` | '__| | |  | | (_) | (_| | |_| | |  __/_| |__| (_| | |  __/ | | | (_| | (_| | |    |_|  |_|\___/ \__,_|\__,_|_|\___(_)\____\__,_|_|\___|_| |_|\__,_|\__,_|_|   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: ]] require('strict')
 * Jarekt

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

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:

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:

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:

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:

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:

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