Module:Calendar
Appearance
Convert dates between Gregorian and Julian Calendars. Used by Module:Complex date.
--[[
__ __ _ _ ____ _ _
| \/ | ___ __| |_ _| | ___ _ / ___|__ _| | ___ _ __ __| | __ _ _ __
| |\/| |/ _ \ / _` | | | | |/ _ (_) | / _` | |/ _ \ '_ \ / _` |/ _` | '__|
| | | | (_) | (_| | |_| | | __/_| |__| (_| | | __/ | | | (_| | (_| | |
|_| |_|\___/ \__,_|\__,_|_|\___(_)\____\__,_|_|\___|_| |_|\__,_|\__,_|_|
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