--- Old Hindu solar and lunar calendar conversions. -- Ported from "Calendrical Calculations" (4th edition) -- by Nachum Dershowitz and Edward M. Reingold. -- Original Lisp code (CALENDRICA 4.0) is Apache 2.0 licensed. -- @module calendrica-old-hindu -- @release 0.1 2026-07-19 local M = {} local basic = require("calendrica-basic") local julian = require("calendrica-julian") local astro = require("calendrica-astro") -- === Epoch and day count === local HINDU_EPOCH = julian.fixed_from_julian( julian.julian_date(julian.bce(3102), julian.FEBRUARY, 18) ) -- Elapsed days (Ahargana) to date since Hindu epoch. local function hindu_day_count(date) return date - HINDU_EPOCH end -- === Old Hindu solar constants === -- Length of Old Hindu solar year in days. local ARYA_SOLAR_YEAR = 1577917500 / 4320000 -- Length of Old Hindu solar month in days. local ARYA_SOLAR_MONTH = ARYA_SOLAR_YEAR / 12 -- === Hindu solar date constructor === --- Construct a Hindu solar date {year, month, day}. -- @tparam number year Hindu solar year. -- @tparam number month Hindu solar month. -- @tparam number day Hindu solar day. -- @treturn table {year, month, day} function M.hindu_solar_date(year, month, day) return {year, month, day} end local hindu_solar_date = M.hindu_solar_date -- === Old Hindu solar conversion === --- Old Hindu solar date {year, month, day} equivalent to fixed `date`. -- @tparam number date Fixed date. -- @treturn table {year, month, day} function M.old_hindu_solar_from_fixed(date) local sun = hindu_day_count(date) + astro.hr(6) local year = basic.quotient(sun, ARYA_SOLAR_YEAR) local month = 1 + (basic.quotient(sun, ARYA_SOLAR_MONTH) % 12) local day = 1 + math.floor(sun % ARYA_SOLAR_MONTH) return hindu_solar_date(year, month, day) end --- Fixed date corresponding to Old Hindu solar date `s_date`. -- @tparam table s_date Hindu solar date {year, month, day}. -- @treturn number Fixed date. function M.fixed_from_old_hindu_solar(s_date) local month = basic.standard_month(s_date) local day = basic.standard_day(s_date) local year = basic.standard_year(s_date) return math.ceil( HINDU_EPOCH + year * ARYA_SOLAR_YEAR + (month - 1) * ARYA_SOLAR_MONTH + day + astro.hr(-30) ) end -- === Old Hindu lunar constants === -- Length of Old Hindu lunar month in days. local ARYA_LUNAR_MONTH = 1577917500 / 53433336 -- Length of Old Hindu lunar day (tithi) in days. local ARYA_LUNAR_DAY = ARYA_LUNAR_MONTH / 30 -- === Old Hindu lunar date constructor and accessors === -- Old Hindu lunar date constructor. local function old_hindu_lunar_date(year, month, leap, day) return {year, month, leap, day} end -- Old Hindu lunar date accessors. local function old_hindu_lunar_year(date) return date[1] end local function old_hindu_lunar_month(date) return date[2] end local function old_hindu_lunar_leap(date) return date[3] end local function old_hindu_lunar_day(date) return date[4] end -- === Old Hindu lunar conversion === -- True if l_year is an Old Hindu lunar leap year. local function old_hindu_lunar_leap_year(l_year) return (l_year * ARYA_SOLAR_YEAR - ARYA_SOLAR_MONTH) % ARYA_LUNAR_MONTH >= 23902504679 / 1282400064 end --- Old Hindu lunar date {year, month, leap, day} equivalent to fixed `date`. -- @tparam number date Fixed date. -- @treturn table {year, month, leap, day} function M.old_hindu_lunar_from_fixed(date) local sun = hindu_day_count(date) + astro.hr(6) local new_moon = sun - (sun % ARYA_LUNAR_MONTH) local leap = ARYA_SOLAR_MONTH - ARYA_LUNAR_MONTH >= (new_moon % ARYA_SOLAR_MONTH) and (new_moon % ARYA_SOLAR_MONTH) > 0 local month = 1 + (math.ceil(new_moon / ARYA_SOLAR_MONTH) % 12) local day = 1 + (basic.quotient(sun, ARYA_LUNAR_DAY) % 30) local year = math.ceil((new_moon + ARYA_SOLAR_MONTH) / ARYA_SOLAR_YEAR) - 1 return old_hindu_lunar_date(year, month, leap, day) end --- Fixed date corresponding to Old Hindu lunar date `l_date`. -- @tparam table l_date Old Hindu lunar date {year, month, leap, day}. -- @treturn number Fixed date. function M.fixed_from_old_hindu_lunar(l_date) local year = old_hindu_lunar_year(l_date) local month = old_hindu_lunar_month(l_date) local leap = old_hindu_lunar_leap(l_date) local day = old_hindu_lunar_day(l_date) local mina = (12 * year - 1) * ARYA_SOLAR_MONTH local lunar_new_year = ARYA_LUNAR_MONTH * (1 + basic.quotient(mina, ARYA_LUNAR_MONTH)) local month_offset if not leap and math.ceil( (lunar_new_year - mina) / (ARYA_SOLAR_MONTH - ARYA_LUNAR_MONTH) ) <= month then month_offset = month else month_offset = month - 1 end return math.ceil( HINDU_EPOCH + lunar_new_year + ARYA_LUNAR_MONTH * month_offset + (day - 1) * ARYA_LUNAR_DAY + astro.hr(-6) ) end -- === Jovian cycle === -- Number of days in one revolution of Jupiter around the Sun. local ARYA_JOVIAN_PERIOD = 1577917500 / 364224 -- Year of Jupiter cycle at fixed date. local function jovian_year(date) return basic.amod( 27 + basic.quotient(hindu_day_count(date), ARYA_JOVIAN_PERIOD / 12), 60 ) end -- === Exports === --- True if Old Hindu lunar year `l_year` is a leap year. -- @function old_hindu_lunar_leap_year -- @tparam number l_year Old Hindu lunar year. -- @treturn boolean M.old_hindu_lunar_leap_year = old_hindu_lunar_leap_year --- Year in Jupiter's 60-year cycle for fixed `date`. -- @function jovian_year -- @tparam number date Fixed date. -- @treturn number Year (1..60). M.jovian_year = jovian_year -- Exposed for use by calendrica-modern-hindu; not part of the public API. M.HINDU_EPOCH = HINDU_EPOCH return M