A modult a Modul:calendar/doc lapon tudod dokumentálni

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