--- Modern Hindu solar and lunar calendar conversions (traditional and astronomical). -- 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-modern-hindu -- @release 0.1 2026-07-19 local M = {} local basic = require("calendrica-basic") local gregorian = require("calendrica-gregorian") local julian = require("calendrica-julian") local astro = require("calendrica-astro") local old_hindu = require("calendrica-old-hindu") -- === Hindu astronomical constants === -- Mean length of Hindu sidereal year in days. local HINDU_SIDEREAL_YEAR = 365 + 279457/1080000 -- Fixed date of Hindu creation. local HINDU_CREATION = old_hindu.HINDU_EPOCH - 1955880000 * HINDU_SIDEREAL_YEAR -- Mean length of Hindu sidereal month in days. local HINDU_SIDEREAL_MONTH = 27 + 4644439/14438334 -- Mean time from new moon to new moon. local HINDU_SYNODIC_MONTH = 29 + 7087771/13358334 -- Time from aphelion to aphelion. local HINDU_ANOMALISTIC_YEAR = 1577917828000 / (4320000000 - 387) -- Time from apogee to apogee, with bija correction. local HINDU_ANOMALISTIC_MONTH = 1577917828 / (57753336 - 488199) -- Years from Kali Yuga until Saka era. local HINDU_SOLAR_ERA = 3179 -- Years from Kali Yuga until Vikrama era. local HINDU_LUNAR_ERA = 3044 -- Location of Ujjain. local UJJAIN = astro.location( astro.angle(23, 9, 0), astro.angle(75, 46, 6), astro.mt(0), astro.hr(5 + 461/9000) ) -- Location (Ujjain) for determining Hindu calendar. local HINDU_LOCATION = UJJAIN -- === Sidereal start (lazy, computed once) === local mesha_samkranti -- forward declaration (defined after hindu_solar_longitude_at_or_after) local _sidereal_start -- Sidereal start angle: precession at Mesha Samkranti in 285 CE. local function sidereal_start() if not _sidereal_start then _sidereal_start = astro.precession( astro.universal_from_local( mesha_samkranti(julian.ce(285)), HINDU_LOCATION ) ) end return _sidereal_start end -- === Hindu date constructor and accessors === -- Hindu lunar date constructor. local function hindu_lunar_date(year, month, leap_month, day, leap_day) return {year, month, leap_month, day, leap_day} end -- Hindu lunar date accessors. local function hindu_lunar_year(date) return date[1] end local function hindu_lunar_month(date) return date[2] end local function hindu_lunar_leap_month(date) return date[3] end local function hindu_lunar_day(date) return date[4] end local function hindu_lunar_leap_day(date) return date[5] end -- === Hindu sine table === -- Simulated Hindu sine table for entry (angle as multiple of 225 arcminutes). local function hindu_sine_table(entry) local exact = 3438 * astro.sin_degrees(entry * astro.angle(0, 225, 0)) local error = 0.215 * basic.sign(exact) * basic.sign(math.abs(exact) - 1716) return math.floor(exact + error + 0.5) / 3438 end -- Linear interpolation of Hindu sine for theta. local function hindu_sine(theta) local entry = theta / astro.angle(0, 225, 0) local fraction = entry % 1 return fraction * hindu_sine_table(math.ceil(entry)) + (1 - fraction) * hindu_sine_table(math.floor(entry)) end -- Inverse of Hindu sine function. local function hindu_arcsin(amp) if amp < 0 then return -hindu_arcsin(-amp) end local pos = basic.next(0, function(k) return amp <= hindu_sine_table(k) end) local below = hindu_sine_table(pos - 1) return astro.angle(0, 225, 0) * ( pos - 1 + (amp - below) / (hindu_sine_table(pos) - below) ) end -- === Hindu mean and true position === -- Position in degrees at moment tee in uniform circular orbit of period days. local function hindu_mean_position(tee, period) return 360 * ((tee - HINDU_CREATION) / period % 1) end -- Longitudinal position at moment tee in epicyclic orbit. local function hindu_true_position(tee, period, size, anomalistic, change) local lambda = hindu_mean_position(tee, period) local offset = hindu_sine(hindu_mean_position(tee, anomalistic)) local contraction = math.abs(offset) * change * size local equation = hindu_arcsin(offset * (size - contraction)) return (lambda - equation) % 360 end -- Solar longitude at moment tee (Hindu sidereal). local function hindu_solar_longitude(tee) return hindu_true_position( tee, HINDU_SIDEREAL_YEAR, 14/360, HINDU_ANOMALISTIC_YEAR, 1/42 ) end -- Zodiacal sign of the sun (1..12) at moment tee. local function hindu_zodiac(tee) return 1 + basic.quotient(hindu_solar_longitude(tee), 30) end -- Lunar longitude at moment tee (Hindu sidereal). local function hindu_lunar_longitude(tee) return hindu_true_position( tee, HINDU_SIDEREAL_MONTH, 32/360, HINDU_ANOMALISTIC_MONTH, 1/96 ) end --- Longitudinal distance between sun and moon in degrees at moment `tee`. -- Used as the angular function for invert_angular in lunar_day_at_or_after. -- @tparam number tee Moment. -- @treturn number Phase angle in degrees [0, 360). function M.hindu_lunar_phase(tee) return (hindu_lunar_longitude(tee) - hindu_solar_longitude(tee)) % 360 end local hindu_lunar_phase = M.hindu_lunar_phase -- Phase of moon (tithi, 1..30) at moment tee. local function hindu_lunar_day_from_moment(tee) return 1 + basic.quotient(hindu_lunar_phase(tee), 12) end -- Approximate moment of last new moon preceding tee. local function hindu_new_moon_before(tee) local varepsilon = 2^-1000 local tau = tee - (1/360) * hindu_lunar_phase(tee) * HINDU_SYNODIC_MONTH return basic.binary_search( tau - 1, math.min(tee, tau + 1), function(x) return hindu_lunar_phase(x) < 180 end, function(l, u) return hindu_zodiac(l) == hindu_zodiac(u) or (u - l) < varepsilon end ) end -- Moment lunar day k begins at or after tee (k may be fractional). local function hindu_lunar_day_at_or_after(k, tee) local phase = (k - 1) * 12 local tau = tee + (1/360) * ((phase - hindu_lunar_phase(tee)) % 360) * HINDU_SYNODIC_MONTH local a = math.max(tee, tau - 2) local b = tau + 2 return basic.invert_angular( hindu_lunar_phase, phase, basic.interval_closed(a, b) ) end -- Hindu solar year at moment tee. local function hindu_calendar_year(tee) return basic.round_half_to_even( (tee - old_hindu.HINDU_EPOCH) / HINDU_SIDEREAL_YEAR - hindu_solar_longitude(tee) / 360 ) end -- === Equation of time and sunrise helpers === -- Sidereal daily motion of sun on date. local function hindu_daily_motion(date) local mean_motion = 360 / HINDU_SIDEREAL_YEAR local anomaly = hindu_mean_position(date, HINDU_ANOMALISTIC_YEAR) local epicycle = 14/360 - math.abs(hindu_sine(anomaly)) / 1080 local entry = basic.quotient(anomaly, astro.angle(0, 225, 0)) local sine_table_step = hindu_sine_table(entry + 1) - hindu_sine_table(entry) local factor = -3438/225 * sine_table_step * epicycle return mean_motion * (1 + factor) end -- Hindu tropical longitude on fixed date. local function hindu_tropical_longitude(date) local days = date - old_hindu.HINDU_EPOCH local precession = 27 - math.abs( 108 * basic.mod3(600/1577917828 * days - 1/4, -1/2, 1/2) ) return (hindu_solar_longitude(date) - precession) % 360 end -- Tabulated speed of rising of current zodiacal sign on date. local function hindu_rising_sign(date) local i = basic.quotient(hindu_tropical_longitude(date), 30) local table = {1670/1800, 1795/1800, 1935/1800, 1935/1800, 1795/1800, 1670/1800} return table[i % 6 + 1] end -- Difference between solar and sidereal day on date. local function hindu_solar_sidereal_difference(date) return hindu_daily_motion(date) * hindu_rising_sign(date) end -- Difference between right and oblique ascension of sun on date at location. local function hindu_ascensional_difference(date, location) local sin_delta = (1397/3438) * hindu_sine(hindu_tropical_longitude(date)) local phi = astro.latitude(location) local diurnal_radius = hindu_sine(90 + hindu_arcsin(sin_delta)) local tan_phi = hindu_sine(phi) / hindu_sine(90 + phi) local earth_sine = sin_delta * tan_phi return hindu_arcsin(-(earth_sine / diurnal_radius)) end -- Time from true to mean midnight of date (gross approximation). local function hindu_equation_of_time(date) local offset = hindu_sine(hindu_mean_position(date, HINDU_ANOMALISTIC_YEAR)) local equation_sun = offset * astro.angle(57, 18, 0) * (14/360 - math.abs(offset) / 1080) return (hindu_daily_motion(date) / 360) * (equation_sun / 360) * HINDU_SIDEREAL_YEAR end -- Sunrise at HINDU_LOCATION on date (traditional). local function hindu_sunrise(date) return date + astro.hr(6) + (astro.longitude(UJJAIN) - astro.longitude(HINDU_LOCATION)) / 360 - hindu_equation_of_time(date) + (1577917828/1582237828 / 360) * (hindu_ascensional_difference(date, HINDU_LOCATION) + (1/4) * hindu_solar_sidereal_difference(date)) end -- Sunset at HINDU_LOCATION on date (traditional). local function hindu_sunset(date) return date + astro.hr(18) + (astro.longitude(UJJAIN) - astro.longitude(HINDU_LOCATION)) / 360 - hindu_equation_of_time(date) + (1577917828/1582237828 / 360) * (-hindu_ascensional_difference(date, HINDU_LOCATION) + (3/4) * hindu_solar_sidereal_difference(date)) end -- Hindu local time of temporal moment tee. local function hindu_standard_from_sundial(tee) local date = basic.fixed_from_moment(tee) local time = basic.time_from_moment(tee) local q = math.floor(4 * time) local a, b if q == 0 then a = hindu_sunset(date - 1) b = hindu_sunrise(date) elseif q == 3 then a = hindu_sunset(date) b = hindu_sunrise(date + 1) else a = hindu_sunrise(date) b = hindu_sunset(date) end local offset if q == 3 then offset = astro.hr(18) elseif q == 0 then offset = astro.hr(-6) else offset = astro.hr(6) end return a + 2 * (b - a) * (time - offset) end -- Astronomical sunrise at HINDU_LOCATION on date (Lahiri; rounded to minute). local function alt_hindu_sunrise(date) local rise = astro.dawn(date, HINDU_LOCATION, astro.angle(0, 47, 0)) return math.floor(rise * 24 * 60 + 0.5) / 24 / 60 end -- Geometrical sunset at HINDU_LOCATION on date (astronomical). local function astro_hindu_sunset(date) return astro.dusk(date, HINDU_LOCATION, 0) end -- === Hindu solar calendar === --- Hindu (Orissa) solar date {year, month, day} equivalent to fixed `date`. -- @tparam number date Fixed date. -- @treturn table {year, month, day} function M.hindu_solar_from_fixed(date) local critical = hindu_sunrise(date + 1) local month = hindu_zodiac(critical) local year = hindu_calendar_year(critical) - HINDU_SOLAR_ERA local approx = date - 3 - (math.floor(hindu_solar_longitude(critical)) % 30) local start = basic.next( approx, function(i) return hindu_zodiac(hindu_sunrise(i + 1)) == month end ) local day = date - start + 1 return old_hindu.hindu_solar_date(year, month, day) end --- Fixed date corresponding to Hindu solar date `s_date` (Saka era; Orissa rule). -- @tparam table s_date Hindu solar date {year, month, day}. -- @treturn number Fixed date. function M.fixed_from_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) local start = math.floor( (year + HINDU_SOLAR_ERA + (month - 1) / 12) * HINDU_SIDEREAL_YEAR ) + old_hindu.HINDU_EPOCH return day - 1 + basic.next( start - 3, function(d) return hindu_zodiac(hindu_sunrise(d + 1)) == month end ) end -- === Hindu lunar calendar (traditional) === --- Hindu lunar date {year,month,leap_month,day,leap_day} equivalent to fixed `date`. -- @tparam number date Fixed date. -- @treturn table {year, month, leap_month, day, leap_day} function M.hindu_lunar_from_fixed(date) local critical = hindu_sunrise(date) local day = hindu_lunar_day_from_moment(critical) local leap_day = day == hindu_lunar_day_from_moment( hindu_sunrise(date - 1) ) local last_new_moon = hindu_new_moon_before(critical) local next_new_moon = hindu_new_moon_before(math.floor(last_new_moon) + 35) local solar_month = hindu_zodiac(last_new_moon) local leap_month = solar_month == hindu_zodiac(next_new_moon) local month = basic.amod(solar_month + 1, 12) local year = hindu_calendar_year( month <= 2 and date + 180 or date ) - HINDU_LUNAR_ERA return hindu_lunar_date(year, month, leap_month, day, leap_day) end local hindu_lunar_from_fixed = M.hindu_lunar_from_fixed --- Fixed date corresponding to Hindu lunar date `l_date`. -- @tparam table l_date Hindu lunar date {year, month, leap_month, day, leap_day}. -- @treturn number Fixed date. function M.fixed_from_hindu_lunar(l_date) local year = hindu_lunar_year(l_date) local month = hindu_lunar_month(l_date) local leap_month = hindu_lunar_leap_month(l_date) local day = hindu_lunar_day(l_date) local leap_day = hindu_lunar_leap_day(l_date) local approx = old_hindu.HINDU_EPOCH + HINDU_SIDEREAL_YEAR * (year + HINDU_LUNAR_ERA + (month - 1) / 12) local s = math.floor( approx - HINDU_SIDEREAL_YEAR * basic.mod3( hindu_solar_longitude(approx) / 360 - (month - 1) / 12, -1/2, 1/2 ) ) local k = hindu_lunar_day_from_moment(s + astro.hr(6)) local mid = hindu_lunar_from_fixed(s - 15) local est if k > 3 and k < 27 then est = s - day + k elseif hindu_lunar_month(mid) ~= month or (hindu_lunar_leap_month(mid) and not leap_month) then est = s - day + basic.mod3(k, -15, 15) else est = s - day + basic.mod3(k, 15, 45) end local tau = est - basic.mod3( hindu_lunar_day_from_moment(est + astro.hr(6)) - day, -15, 15 ) local result = basic.next( tau - 1, function(d) local ld = hindu_lunar_day_from_moment(hindu_sunrise(d)) return ld == day or ld == basic.amod(day + 1, 30) end ) return leap_day and result + 1 or result end local fixed_from_hindu_lunar = M.fixed_from_hindu_lunar -- === Hindu lunar calendar (full-moon scheme) === -- Hindu lunar date (full-moon scheme) equivalent to fixed date. local function hindu_fullmoon_from_fixed(date) local l_date = hindu_lunar_from_fixed(date) local year = hindu_lunar_year(l_date) local month = hindu_lunar_month(l_date) local leap_month = hindu_lunar_leap_month(l_date) local day = hindu_lunar_day(l_date) local leap_day = hindu_lunar_leap_day(l_date) local m = day >= 16 and hindu_lunar_month(hindu_lunar_from_fixed(date + 20)) or month return hindu_lunar_date(year, m, leap_month, day, leap_day) end -- True if Hindu lunar month l_month in l_year is expunged. local function hindu_expunged(l_year, l_month) return l_month ~= hindu_lunar_month( hindu_lunar_from_fixed( fixed_from_hindu_lunar( hindu_lunar_date(l_year, l_month, false, 15, false) ) ) ) end -- Fixed date equivalent to Hindu lunar l_date in full-moon scheme. local function fixed_from_hindu_fullmoon(l_date) -- luacheck: ignore local year = hindu_lunar_year(l_date) local month = hindu_lunar_month(l_date) local leap_month = hindu_lunar_leap_month(l_date) local day = hindu_lunar_day(l_date) local leap_day = hindu_lunar_leap_day(l_date) local m if leap_month or day <= 15 then m = month elseif hindu_expunged(year, basic.amod(month - 1, 12)) then m = basic.amod(month - 2, 12) else m = basic.amod(month - 1, 12) end return fixed_from_hindu_lunar( hindu_lunar_date(year, m, leap_month, day, leap_day) ) end -- === Astronomical Hindu solar calendar === -- Astronomical Hindu solar year KY at moment tee. local function astro_hindu_calendar_year(tee) return basic.round_half_to_even( (tee - old_hindu.HINDU_EPOCH) / astro.MEAN_SIDEREAL_YEAR - astro.sidereal_solar_longitude(tee, sidereal_start()) / 360 ) end -- Sidereal zodiacal sign of sun (1..12) at moment tee. local function sidereal_zodiac(tee) return 1 + basic.quotient( astro.sidereal_solar_longitude(tee, sidereal_start()), 30 ) end --- Astronomical Hindu (Tamil) solar date {year, month, day} equivalent to fixed `date`. -- @tparam number date Fixed date. -- @treturn table {year, month, day} function M.astro_hindu_solar_from_fixed(date) local critical = astro_hindu_sunset(date) local month = sidereal_zodiac(critical) local year = astro_hindu_calendar_year(critical) - HINDU_SOLAR_ERA local approx = date - 3 - (math.floor(astro.sidereal_solar_longitude(critical, sidereal_start())) % 30) local start = basic.next( approx, function(i) return sidereal_zodiac(astro_hindu_sunset(i)) == month end ) local day = date - start + 1 return old_hindu.hindu_solar_date(year, month, day) end --- Fixed date corresponding to astronomical Hindu solar date `s_date` (Tamil rule; Saka era). -- @tparam table s_date Hindu solar date {year, month, day}. -- @treturn number Fixed date. function M.fixed_from_astro_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) local approx = old_hindu.HINDU_EPOCH - 3 + math.floor( (year + HINDU_SOLAR_ERA + (month - 1) / 12) * astro.MEAN_SIDEREAL_YEAR ) local start = basic.next( approx, function(i) return sidereal_zodiac(astro_hindu_sunset(i)) == month end ) return start + day - 1 end -- === Astronomical Hindu lunar calendar === -- Phase of moon (tithi, 1..30) at moment tee using astronomical lunar phase. local function astro_lunar_day_from_moment(tee) return 1 + basic.quotient(astro.lunar_phase(tee), 12) end --- Astronomical Hindu lunar date {year,month,leap_month,day,leap_day} equivalent to fixed `date`. -- @tparam number date Fixed date. -- @treturn table {year, month, leap_month, day, leap_day} function M.astro_hindu_lunar_from_fixed(date) local critical = alt_hindu_sunrise(date) local day = astro_lunar_day_from_moment(critical) local leap_day = day == astro_lunar_day_from_moment( alt_hindu_sunrise(date - 1) ) local last_new_moon = astro.new_moon_before(critical) local next_new_moon = astro.new_moon_at_or_after(critical) local solar_month = sidereal_zodiac(last_new_moon) local leap_month = solar_month == sidereal_zodiac(next_new_moon) local month = basic.amod(solar_month + 1, 12) local year = astro_hindu_calendar_year( month <= 2 and date + 180 or date ) - HINDU_LUNAR_ERA return hindu_lunar_date(year, month, leap_month, day, leap_day) end local astro_hindu_lunar_from_fixed = M.astro_hindu_lunar_from_fixed --- Fixed date corresponding to astronomical Hindu lunar date `l_date`. -- @tparam table l_date Hindu lunar date {year, month, leap_month, day, leap_day}. -- @treturn number Fixed date. function M.fixed_from_astro_hindu_lunar(l_date) local year = hindu_lunar_year(l_date) local month = hindu_lunar_month(l_date) local leap_month = hindu_lunar_leap_month(l_date) local day = hindu_lunar_day(l_date) local leap_day = hindu_lunar_leap_day(l_date) local approx = old_hindu.HINDU_EPOCH + astro.MEAN_SIDEREAL_YEAR * (year + HINDU_LUNAR_ERA + (month - 1) / 12) local s = math.floor( approx - HINDU_SIDEREAL_YEAR * basic.mod3( astro.sidereal_solar_longitude(approx, sidereal_start()) / 360 - (month - 1) / 12, -1/2, 1/2 ) ) local k = astro_lunar_day_from_moment(s + astro.hr(6)) local mid = astro_hindu_lunar_from_fixed(s - 15) local est if k > 3 and k < 27 then est = s - day + k elseif hindu_lunar_month(mid) ~= month or (hindu_lunar_leap_month(mid) and not leap_month) then est = s - day + basic.mod3(k, -15, 15) else est = s - day + basic.mod3(k, 15, 45) end local tau = est - basic.mod3( astro_lunar_day_from_moment(est + astro.hr(6)) - day, -15, 15 ) local result = basic.next( tau - 1, function(d) local ld = astro_lunar_day_from_moment(alt_hindu_sunrise(d)) return ld == day or ld == basic.amod(day + 1, 30) end ) return leap_day and result + 1 or result end -- === Hindu solar longitude helpers === -- Hindu lunar station (nakshatra, 1..27) at sunrise on date. local function hindu_lunar_station(date) local critical = hindu_sunrise(date) return 1 + basic.quotient(hindu_lunar_longitude(critical), astro.angle(0, 800, 0)) end -- Moment of first time at or after tee when Hindu solar longitude is lambda degrees. local function hindu_solar_longitude_at_or_after(lambda, tee) local tau = tee + HINDU_SIDEREAL_YEAR * (1/360) * ((lambda - hindu_solar_longitude(tee)) % 360) local a = math.max(tee, tau - 5) local b = tau + 5 return basic.invert_angular( hindu_solar_longitude, lambda, basic.interval_closed(a, b) ) end --- Fixed moment of Mesha Samkranti (Hindu solar new year, sun enters Aries) in Gregorian year `g_year`. -- @tparam number g_year Gregorian year. -- @treturn number Fixed moment. mesha_samkranti = function(g_year) return hindu_solar_longitude_at_or_after( 0, gregorian.gregorian_new_year(g_year) ) end M.mesha_samkranti = mesha_samkranti -- Difference between tropical and sidereal solar longitude at moment tee. local function ayanamsha(tee) -- luacheck: ignore return astro.solar_longitude(tee) - astro.sidereal_solar_longitude(tee, sidereal_start()) end -- === Hindu new year === -- Fixed date of Hindu lunisolar new year in Gregorian year g_year. --- Fixed date of Hindu Lunar New Year in Gregorian year `g_year`. -- @tparam number g_year Gregorian year. -- @treturn number Fixed date. function M.hindu_lunar_new_year(g_year) local jan1 = gregorian.gregorian_new_year(g_year) local mina = hindu_solar_longitude_at_or_after(330, jan1) local new_moon = hindu_lunar_day_at_or_after(1, mina) local h_day = math.floor(new_moon) local critical = hindu_sunrise(h_day) return h_day + ( (new_moon < critical or hindu_lunar_day_from_moment(hindu_sunrise(h_day + 1)) == 2) and 0 or 1 ) end -- === Hindu lunar date ordering === -- True if Hindu lunar date l_date1 is on or before l_date2. local function hindu_lunar_on_or_before(l_date1, l_date2) local month1 = hindu_lunar_month(l_date1) local month2 = hindu_lunar_month(l_date2) local leap1 = hindu_lunar_leap_month(l_date1) local leap2 = hindu_lunar_leap_month(l_date2) local day1 = hindu_lunar_day(l_date1) local day2 = hindu_lunar_day(l_date2) local leap_day1 = hindu_lunar_leap_day(l_date1) local leap_day2 = hindu_lunar_leap_day(l_date2) local year1 = hindu_lunar_year(l_date1) local year2 = hindu_lunar_year(l_date2) return year1 < year2 or (year1 == year2 and ( month1 < month2 or (month1 == month2 and ( (leap1 and not leap2) or (leap1 == leap2 and ( day1 < day2 or (day1 == day2 and ( not leap_day1 or leap_day2 )) )) )) )) end -- === Hindu date occurrence and holidays === -- Fixed date of occurrence of l_month, l_day in l_year (leap/expunged aware). local function hindu_date_occur(l_year, l_month, l_day) local lunar = hindu_lunar_date(l_year, l_month, false, l_day, false) local try = fixed_from_hindu_lunar(lunar) local mid = hindu_lunar_from_fixed(l_day > 15 and try - 5 or try) local expunged = l_month ~= hindu_lunar_month(mid) local l_date = hindu_lunar_date( hindu_lunar_year(mid), hindu_lunar_month(mid), hindu_lunar_leap_month(mid), l_day, false ) if expunged then return basic.next(try, function(d) return not hindu_lunar_on_or_before( hindu_lunar_from_fixed(d), l_date ) end) - 1 elseif l_day ~= hindu_lunar_day(hindu_lunar_from_fixed(try)) then return try - 1 else return try end end -- List of fixed dates of occurrences of lunar month/day in Gregorian g_year. local function hindu_lunar_holiday(l_month, l_day, g_year) local l_year = hindu_lunar_year( hindu_lunar_from_fixed(gregorian.gregorian_new_year(g_year)) ) local date0 = hindu_date_occur(l_year, l_month, l_day) local date1 = hindu_date_occur(l_year + 1, l_month, l_day) return basic.list_range( {date0, date1}, gregorian.gregorian_year_range(g_year) ) end -- List of fixed dates of Diwali in Gregorian year g_year. --- Fixed dates of Diwali (Hindu lunar month 8, day 1) in Gregorian year `g_year`. -- @tparam number g_year Gregorian year. -- @treturn {number,...} Fixed dates. function M.diwali(g_year) return hindu_lunar_holiday(8, 1, g_year) end -- Fixed date of occurrence of tithi at sundial time tee in l_month, l_year. local function hindu_tithi_occur(l_month, tithi, tee, l_year) local approx = hindu_date_occur(l_year, l_month, math.floor(tithi)) local lunar = hindu_lunar_day_at_or_after(tithi, approx - 2) local try = basic.fixed_from_moment(lunar) local tee_h = hindu_standard_from_sundial(try + tee, UJJAIN) if lunar <= tee_h or hindu_lunar_phase( hindu_standard_from_sundial(try + 1 + tee, UJJAIN) ) > 12 * tithi then return try else return try + 1 end end -- List of fixed dates of tithi at sundial time tee in l_month in Gregorian g_year. local function hindu_lunar_event(l_month, tithi, tee, g_year) local l_year = hindu_lunar_year( hindu_lunar_from_fixed(gregorian.gregorian_new_year(g_year)) ) local date0 = hindu_tithi_occur(l_month, tithi, tee, l_year) local date1 = hindu_tithi_occur(l_month, tithi, tee, l_year + 1) return basic.list_range( {date0, date1}, gregorian.gregorian_year_range(g_year) ) end --- Fixed dates of Maha Shivaratri (Night of Shiva) in Gregorian year `g_year`. -- @tparam number g_year Gregorian year. -- @treturn {number,...} Fixed dates. function M.shiva(g_year) return hindu_lunar_event(11, 29, astro.hr(24), g_year) end --- Fixed dates of Rama Navami (Rama's Birthday) in Gregorian year `g_year`. -- @tparam number g_year Gregorian year. -- @treturn {number,...} Fixed dates. function M.rama(g_year) return hindu_lunar_event(1, 9, astro.hr(12), g_year) end -- === Karana, yoga, sacred Wednesdays === -- Number (0-10) of the name of the n-th (1-60) Hindu karana. local function karana(n) -- luacheck: ignore if n == 1 then return 0 elseif n > 57 then return n - 50 else return basic.amod(n - 1, 7) end end -- Hindu yoga (1..27) on date. local function yoga(date) -- luacheck: ignore return 1 + math.floor( (hindu_solar_longitude(date) + hindu_lunar_longitude(date)) / astro.angle(0, 800, 0) % 27 ) end -- List of Wednesdays within range that are day 8 of Hindu lunar months. local function sacred_wednesdays_in_range(range) local result = {} local wed = gregorian.kday_on_or_after(basic.WEDNESDAY, basic.begin(range)) while basic.in_range(wed, range) do local h_date = hindu_lunar_from_fixed(wed) if hindu_lunar_day(h_date) == 8 then result[#result + 1] = wed end wed = wed + 7 end return result end --- Fixed dates of Sacred Wednesdays (lunar day 8) in Gregorian year `g_year`. -- @tparam number g_year Gregorian year. -- @treturn {number,...} Fixed dates. function M.sacred_wednesdays(g_year) return sacred_wednesdays_in_range(gregorian.gregorian_year_range(g_year)) end -- === Exports === --- Moment of sunrise at HINDU_LOCATION on fixed `date` (traditional calculation). -- @function hindu_sunrise -- @tparam number date Fixed date. -- @treturn number Moment. M.hindu_sunrise = hindu_sunrise --- Moment of sunset at HINDU_LOCATION on fixed `date` (traditional calculation). -- @function hindu_sunset -- @tparam number date Fixed date. -- @treturn number Moment. M.hindu_sunset = hindu_sunset --- Moment of geometrical sunset at HINDU_LOCATION on fixed `date` (astronomical). -- @function astro_hindu_sunset -- @tparam number date Fixed date. -- @treturn number Moment. M.astro_hindu_sunset = astro_hindu_sunset --- Nakshatra (lunar station, 1..27) at sunrise on fixed `date`. -- @function hindu_lunar_station -- @tparam number date Fixed date. -- @treturn number Nakshatra index. M.hindu_lunar_station = hindu_lunar_station --- Hindu lunar date in full-moon scheme for fixed `date`. -- @function hindu_fullmoon_from_fixed -- @tparam number date Fixed date. -- @treturn table Hindu lunar date {year, month, leap_month, day, leap_day}. M.hindu_fullmoon_from_fixed = hindu_fullmoon_from_fixed --- True if Hindu lunar month `l_month` in year `l_year` is expunged (skipped). -- @function hindu_expunged -- @tparam number l_year Hindu lunar year. -- @tparam number l_month Hindu lunar month. -- @treturn boolean M.hindu_expunged = hindu_expunged --- Fixed dates of Hindu lunar `l_month`/`l_day` falling in Gregorian year `g_year`. -- @function hindu_lunar_holiday -- @tparam number l_month Hindu lunar month. -- @tparam number l_day Hindu lunar day. -- @tparam number g_year Gregorian year. -- @treturn {number,...} Fixed dates. M.hindu_lunar_holiday = hindu_lunar_holiday return M