Module:calendar

-- The Lunar Hijri conversion functions are translated from -- https://github.com/GeniusTS/hijri-dates/blob/master/src/Converter.php -- which is under the MIT license, and the Solar Hijri functions are translated -- from https://github.com/xsoh/solarHijri-js/blob/master/index.js, -- which is under the MIT license. -- convert and _convert are wrappers unique to -- this module.

local export = {}

do local floor = math.floor function export.Gregorian_to_Julian(year, month, day) if month < 3 then year = year - 1 month = month + 12 end local a = floor(year / 100.0) local b	   if year == 1582 and (month > 10 or (month == 10 and day > 4)) then b = -10 elseif year == 1582 and month == 10 then b = 0 elseif year < 1583 then b = 0 else b = 2 - a + floor(a / 4.0) end return floor(365.25 * (year + 4716)) + floor(30.6001 * (month + 1)) + day + b - 1524 end function export.Lunar_Hijri_to_Julian(year, month, day) return floor((11 * year + 3) / 30) + floor(354 * year) + floor(30 * month) - floor((month - 1) / 2) + day + 1948440 - 386 end function export.Julian_to_Gregorian(Julian_day) local b = 0 if Julian_day > 2299160 then local a = floor((Julian_day - 1867216.25) / 36524.25) b = 1 + a - floor(a / 4.0) end local bb = Julian_day + b + 1524 local cc = floor((bb - 122.1) / 365.25) local dd = floor(365.25 * cc) local ee = floor((bb - dd) / 30.6001) local day = (bb - dd) - floor(30.6001 * ee) local month = ee - 1 if ee > 13 then cc = cc + 1 month = ee - 13 end local year = cc - 4716 return { year = year, month = month, day = day } end function export.Julian_to_Lunar_Hijri(Julian_day) local y = 10631.0 / 30.0 local epoch_astro = 1948084 local shift1 = 8.01 / 60.0 local z = Julian_day - epoch_astro local cyc = floor(z / 10631.0) z = z - 10631 * cyc local j = floor((z - shift1) / y)	   z = z - floor(j * y + shift1) local year = 30 * cyc + j	   local month = floor((z + 28.5001) / 29.5) if month == 13 then month = 12 end local day = z - floor(29.5001 * month - 29) return { year = year, month = month, day = day } end end

do -- translated from here: -- https://github.com/xsoh/solarHijri-js/blob/master/index.js	local floor, ceil = math.floor, math.ceil -- Utility helper functions. local function idiv(a, b)		local c = a / b		return (c > 0 and floor or ceil)(c) end local function mod(a, b)		return a - idiv(a, b) * b	end --	local function is_valid_Solar_Hijri_date(year, month, day)		return	year >= -61 and year <= 3177 and						month >= 1 and month <= 12 and						day >= 1 and day <= Solar_Hijri_month_length(year, month)	end	-- local function is_leap_Solar_Hijri_year(year) return Solar_Hijri_Cal(year).leap == true end local function Solar_Hijri_month_length(year, month) if month <= 5 then return 30 elseif month >= 7 then return 31 end -- here where month = 6. if is_leap_Solar_Hijri_year(year) then return 30 else return 29 end end local function Gregorian_to_Julian(year, month, day) local d = idiv((year + idiv(month - 8, 6) + 100100) * 1461, 4) + idiv(153 * mod(month + 9, 12) + 2, 5) + day - 34840408 d = d - idiv(idiv(year + 100100 + idiv(month - 8, 6), 100) * 3, 4) + 752 return d	end local function Julian_to_Gregorian(Julian_day) local j = 4 * Julian_day + 139361631 j = j + idiv(idiv(4 * Julian_day + 183187720, 146097) * 3, 4) * 4 - 3908 local i = idiv(mod(j, 1461), 4) * 5 + 308 local month = mod(idiv(i, 153), 12) + 1 return { year = idiv(j, 1461) - 100100 + idiv(8 - month, 6), month = month, day = idiv(mod(i, 153), 5) + 1, }	end local function Solar_Hijri_Cal(year) local Gregorian_year = year + 621 local pGregorian_year = Gregorian_year + 1 return	{ leap = (pGregorian_year % 4 == 0 and pGregorian_year % 100 ~= 0) or pGregorian_year % 400 == 0, Gregorian_year = Gregorian_year, start_date = 23, -- September 23 }	end local function Julian_to_Solar_Hijri(Julian_day) local Gregorian_year = Julian_to_Gregorian(Julian_day).year local year = Gregorian_year - 621 local r = Solar_Hijri_Cal(year) local Julian_day_1st_Libra = Gregorian_to_Julian(Gregorian_year, 9, r.start_date) local k = Julian_day - Julian_day_1st_Libra if k >= 0 and k <= 99 then local leap = r.leap and 1 or 0 return	{ year = year, month = 1 + idiv(k, 30), day = mod(k, 30) + 1, }		else k = k + 365 year = year - 1 r = Solar_Hijri_Cal(year) leap = r.leap and 1 or 0 if k <= 178 then k = k + leap return	{ year = year, month = 1 + idiv(k, 30), day = mod(k, 30) + 1, }			else k = k - 179 return	{ year = year, month = 7 + idiv(k, 31), day = mod(k, 31) + 1, }			end end end local function Solar_Hijri_to_Julian(year, month, day) local r = Solar_Hijri_Cal(year); local leap = r.leap and 0 or -1 return Gregorian_to_Julian(r.Gregorian_year, 9, r.start_date) + (month - 1) * 30 + idiv(month, 7) * (month - 7) + day - 1 + (idiv(month, 7) * leap) end function export.Gregorian_to_Solar_Hijri(year, month, day) return Julian_to_Solar_Hijri(Gregorian_to_Julian(year, month, day)) end function export.Solar_Hijri_to_Gregorian(year, month, day) return Julian_to_Gregorian(Solar_Hijri_to_Julian(year, month, day)) end end

function export._convert(from, to, year) local f1, f2	if from == "gregorian" then if to == "lunar_hijri" then f1 = "Gregorian_to_Julian" f2 = "Julian_to_Lunar_Hijri" elseif to == "solar_hijri" then f1 = "Gregorian_to_Solar_Hijri" else error("not implemented") end elseif from == "lunar_hijri" then if to == "gregorian" then f1 = "Lunar_Hijri_to_Julian" f2 = "Julian_to_Gregorian" else error("not implemented") end elseif from == "solar_hijri" then if to == "gregorian" then f1 = "Solar_Hijri_to_Gregorian" else error("not implemented") end else error("not implemented") end local converter if f2 then function converter(year, month, day) return export[f2](export[f1](year, month, day)) end else function converter(year, month, day) return export[f1](year, month, day) end end -- assuming month is 1-based return converter(year, 1, 1), converter(year, 12, 30) end

local aliases = { ah = "lunar_hijri", sh = "solar_hijri", gc = "gregorian", }

local function normalize_calendar_code(code) code = code:lower:gsub(" ", "_") return aliases[code] or code end

function export.convert(frame) assert(frame and frame.args and type(frame.args) == "table") local args = pairs(frame.args)(frame.args) and frame.args or frame:getParent.args local from = args[1] or error("Expected calendar name in parameter 1") local to = args[2] or error("Expected calendar name in parameter 2") local year = tonumber(args[3]) or error("Expected number in parameter 3") from, to = normalize_calendar_code(from), normalize_calendar_code(to) local date1, date2 = export._convert(from, to, year) local res = "" local date = date1 while date.year <= date2.year do		local year_string = os.date("%Y", os.time(date)) if res ~= "" then res = res .. "/"		end res = res .. year_string date.year = date.year + 1 end return res end

function export.debug(from, to, year) return export.convert { args = { from, to, year } } end

return export