--- Calendar conversion API. -- All dates are stored internally as RD (Rata Die) fixed integers. -- Calendar fields (`year`, `month`, `day`, …) are computed lazily on first -- access and cached on the date object. -- @module calendrica -- @release 0.1 2026-07-19 -- @usage -- local calendrica = require("calendrica") -- local d = calendrica.new_date({year=2024, month=4, day=23}, calendrica.cal_gregorian) -- local d2 = calendrica.as_date(d, calendrica.cal_hebrew) -- print(d2.year, d2.month, d2.day) local M = {} -- === Module loading === local basic = require("calendrica-basic") local gregorian = require("calendrica-gregorian") local julian = require("calendrica-julian") local egyptian = require("calendrica-egyptian-armenian") local akan = require("calendrica-akan") local coptic = require("calendrica-coptic-ethiopic") local iso = require("calendrica-iso") local icelandic = require("calendrica-icelandic") local islamic = require("calendrica-islamic") local hebrew = require("calendrica-hebrew") local eccl = require("calendrica-ecclesiastical") local mayan = require("calendrica-mayan") local balinese = require("calendrica-balinese") local persian = require("calendrica-persian") local bahai = require("calendrica-bahai") local french = require("calendrica-french-revolutionary") local chinese = require("calendrica-chinese") local old_hindu = require("calendrica-old-hindu") local hindu = require("calendrica-modern-hindu") local tibetan = require("calendrica-tibetan") local astro_lunar = require("calendrica-astronomical-lunar") local astro = require("calendrica-astro") -- === Date object === local date_mt = {} date_mt.__index = function(self, key) local fields = rawget(self, "_fields") if not fields then fields = rawget(self, "_cal").from_rd(rawget(self, "_rd")) rawset(self, "_fields", fields) end return fields[key] end date_mt.__tostring = function(self) local fields = rawget(self, "_fields") if not fields then fields = rawget(self, "_cal").from_rd(rawget(self, "_rd")) rawset(self, "_fields", fields) end local parts = {} for _, k in ipairs(rawget(self, "_cal").granularities) do parts[#parts + 1] = k .. "=" .. tostring(fields[k]) end return rawget(self, "_cal").name .. "(" .. table.concat(parts, ", ") .. ")" end local function make_date(rd, calendar) return setmetatable({_rd = rd, _cal = calendar}, date_mt) end -- === Calendar registry === local cal_registry = {} local function resolve_calendar(calendar) if type(calendar) == "string" then return cal_registry[calendar] or error("unknown calendar: " .. calendar, 3) end return calendar end local function normalize(raw, cal) cal = cal or cal_registry["gregorian"] if type(raw) == "number" then return {make_date(raw, cal)} elseif type(raw) == "table" then local out = {} for _, rd in ipairs(raw) do out[#out + 1] = make_date(rd, cal) end return out else return {} end end local function as_time(moment, zone) if not moment or moment == basic.BOGUS then return nil end local frac = moment % 1 local h = math.floor(frac * 24) local m = math.floor((frac * 24 - h) * 60) local s = ((frac * 24 - h) * 60 - m) * 3600 return {hour = h, minute = m, second = s, zone = zone} end -- === Core === --- @section Core --- Create a new calendar definition. -- `from_rd` and `to_rd` are the raw module functions that use positional tables; -- `new_calendar` wraps them automatically using `granularities` as the key list. -- @tparam string name Unique calendar name. -- @tparam {string,...} granularities Ordered field names, e.g. `{"year","month","day"}`. -- @tparam func from_rd Raw function: RD → positional table. -- @tparam[opt] func to_rd Raw function: positional table → RD. -- Omit (or pass `nil`) for read-only calendars that cannot be used as a source in `new_date`. -- @treturn table Calendar object for use with `new_date`, `as_date`, and `granularity_names`. function M.new_calendar(name, granularities, from_rd, to_rd) local function wrapped_from_rd(rd) local t, out = from_rd(rd), {} for i, k in ipairs(granularities) do out[k] = t[i] end return out end local wrapped_to_rd if to_rd then wrapped_to_rd = function(fields) local t = {} for i, k in ipairs(granularities) do t[i] = fields[k] end return to_rd(t) end end local cal = { name = name, granularities = granularities, from_rd = wrapped_from_rd, to_rd = wrapped_to_rd, } cal_registry[name] = cal return cal end local new_calendar = M.new_calendar --- Return the ordered granularity names of a calendar. -- @tparam table calendar A calendar object. -- @treturn {string,...} Ordered list of field name strings. function M.granularity_names(calendar) return calendar.granularities end --- Construct a date from named fields and a calendar. -- The calendar must have a `to_rd` function (i.e. must not be read-only). -- @tparam table fields Named field table; keys must match the calendar's granularity names. -- @tparam table|string calendar Calendar object or calendar name string (e.g. `"gregorian"`). -- @treturn table Date object. Fields are accessible by name (e.g. `d.year`). -- @raise If the calendar is unknown or read-only. function M.new_date(fields, calendar) calendar = resolve_calendar(calendar) assert(calendar, "calendar required") assert(calendar.to_rd, "calendar is read-only (no to_rd): " .. tostring(calendar.name)) return make_date(calendar.to_rd(fields), calendar) end --- Convert a date object to a different calendar. -- The underlying RD value is reused; no arithmetic is performed. -- @tparam table date A date object (as returned by `new_date`, `as_date`, or `today`). -- @tparam table|string calendar Target calendar object or name string. -- @treturn table New date object expressed in the target calendar. -- @raise If the calendar is unknown. function M.as_date(date, calendar) calendar = resolve_calendar(calendar) assert(calendar, "calendar required") return make_date(date._rd, calendar) end --- Return the fixed (RD) integer for a date object. -- @tparam table date A date object (as returned by `new_date`, `as_date`, or `today`). -- @treturn number Fixed date (Rata Die integer). function M.as_fixed(date) return date._rd end --- Return today's date in the given calendar. -- @tparam[opt] table|string calendar Calendar object or name string. Defaults to `cal_gregorian`. -- @treturn table Date object for today. -- @raise If the calendar name is unknown. function M.today(calendar) local t = os.date("*t") local rd = gregorian.fixed_from_gregorian({t.year, t.month, t.day}) local cal = calendar and resolve_calendar(calendar) or cal_registry["gregorian"] return make_date(rd, cal) end -- === Pre-built calendars === --- @section Calendars --- Proleptic Gregorian calendar. Granularities: `year`, `month`, `day`. M.cal_gregorian = new_calendar("gregorian", {"year","month","day"}, gregorian.gregorian_from_fixed, gregorian.fixed_from_gregorian) --- Proleptic Julian calendar. Granularities: `year`, `month`, `day`. M.cal_julian = new_calendar("julian", {"year","month","day"}, julian.julian_from_fixed, julian.fixed_from_julian) --- Roman calendar (Julian-based). Granularities: `year`, `month`, `event`, `count`, `leap`. -- `event` is one of `calendrica-julian`'s `KALENDS`, `NONES`, or `IDES` constants. M.cal_roman = new_calendar("roman", {"year","month","event","count","leap"}, julian.roman_from_fixed, julian.fixed_from_roman) --- ISO week-date calendar. Granularities: `year`, `week`, `day`. M.cal_iso = new_calendar("iso", {"year","week","day"}, iso.iso_from_fixed, iso.fixed_from_iso) --- Ancient Egyptian calendar. Granularities: `year`, `month`, `day`. M.cal_egyptian = new_calendar("egyptian", {"year","month","day"}, egyptian.egyptian_from_fixed, egyptian.fixed_from_egyptian) --- Armenian calendar (structurally identical to Egyptian). Granularities: `year`, `month`, `day`. M.cal_armenian = new_calendar("armenian", {"year","month","day"}, egyptian.armenian_from_fixed, egyptian.fixed_from_armenian) --- Coptic calendar. Granularities: `year`, `month`, `day`. M.cal_coptic = new_calendar("coptic", {"year","month","day"}, coptic.coptic_from_fixed, coptic.fixed_from_coptic) --- Ethiopic calendar. Granularities: `year`, `month`, `day`. M.cal_ethiopic = new_calendar("ethiopic", {"year","month","day"}, coptic.ethiopic_from_fixed, coptic.fixed_from_ethiopic) --- Arithmetic Islamic (Hijri) calendar. Granularities: `year`, `month`, `day`. M.cal_islamic = new_calendar("islamic", {"year","month","day"}, islamic.islamic_from_fixed, islamic.fixed_from_islamic) --- Observational Islamic calendar (astronomical). Granularities: `year`, `month`, `day`. M.cal_islamic_observational = new_calendar("islamic-observational", {"year","month","day"}, astro_lunar.observational_islamic_from_fixed, astro_lunar.fixed_from_observational_islamic) --- Saudi Islamic calendar (Umm al-Qura approximation). Granularities: `year`, `month`, `day`. --- Islamic TBLA calendar (Thursday epoch). Granularities: `year`, `month`, `day`. M.cal_islamic_tbla = new_calendar("islamic-tbla", {"year","month","day"}, islamic.islamic_tbla_from_fixed, islamic.fixed_from_islamic_tbla) --- Umm al-Qura Islamic calendar. Granularities: `year`, `month`, `day`. M.cal_islamic_umalqura = new_calendar("islamic-umalqura", {"year","month","day"}, islamic.islamic_umalqura_from_fixed, islamic.fixed_from_islamic_umalqura) --- Observational Saudi Islamic calendar. Granularities: `year`, `month`, `day`. M.cal_islamic_saudi = new_calendar("islamic-saudi", {"year","month","day"}, astro_lunar.saudi_islamic_from_fixed, astro_lunar.fixed_from_saudi_islamic) --- Astronomical Umm al-Qura calendar (conjunction before Mecca sunset). Granularities: `year`, `month`, `day`. M.cal_islamic_umalqura_astronomical = new_calendar("islamic-umalqura-astronomical", {"year","month","day"}, astro_lunar.astronomical_islamic_umalqura_from_fixed, astro_lunar.fixed_from_astronomical_islamic_umalqura) --- Turkish Islamic calendar (Diyanet crescent visibility from Ankara). Granularities: `year`, `month`, `day`. M.cal_islamic_turkish = new_calendar("islamic-turkish", {"year","month","day"}, astro_lunar.turkish_islamic_from_fixed, astro_lunar.fixed_from_turkish_islamic) --- Arithmetic Hebrew calendar. Granularities: `year`, `month`, `day`. M.cal_hebrew = new_calendar("hebrew", {"year","month","day"}, hebrew.hebrew_from_fixed, hebrew.fixed_from_hebrew) --- Observational Hebrew calendar (astronomical). Granularities: `year`, `month`, `day`. M.cal_hebrew_observational = new_calendar("hebrew-observational", {"year","month","day"}, astro_lunar.observational_hebrew_from_fixed, astro_lunar.fixed_from_observational_hebrew) --- Astronomical Persian calendar. Granularities: `year`, `month`, `day`. M.cal_persian = new_calendar("persian", {"year","month","day"}, persian.persian_from_fixed, persian.fixed_from_persian) --- Arithmetic Persian calendar. Granularities: `year`, `month`, `day`. M.cal_persian_arithmetic = new_calendar("persian-arithmetic", {"year","month","day"}, persian.arithmetic_persian_from_fixed, persian.fixed_from_arithmetic_persian) --- Astronomical French Revolutionary calendar. Granularities: `year`, `month`, `day`. M.cal_french = new_calendar("french", {"year","month","day"}, french.french_from_fixed, french.fixed_from_french) --- Arithmetic French Revolutionary calendar. Granularities: `year`, `month`, `day`. M.cal_french_arithmetic = new_calendar("french-arithmetic", {"year","month","day"}, french.arithmetic_french_from_fixed, french.fixed_from_arithmetic_french) --- Arithmetic Bahá'í calendar. Granularities: `major`, `cycle`, `year`, `month`, `day`. M.cal_bahai = new_calendar("bahai", {"major","cycle","year","month","day"}, bahai.bahai_from_fixed, bahai.fixed_from_bahai) --- Astronomical Bahá'í calendar. Granularities: `major`, `cycle`, `year`, `month`, `day`. M.cal_bahai_astronomical = new_calendar("bahai-astronomical", {"major","cycle","year","month","day"}, bahai.astro_bahai_from_fixed, bahai.fixed_from_astro_bahai) --- Mayan Long Count calendar. Granularities: `baktun`, `katun`, `tun`, `uinal`, `kin`. M.cal_mayan_long_count = new_calendar("mayan-long-count", {"baktun","katun","tun","uinal","kin"}, mayan.mayan_long_count_from_fixed, mayan.fixed_from_mayan_long_count) --- Mayan Haab calendar (read-only). Granularities: `month`, `day`. -- Cannot be used as a source for `new_date` as Haab alone does not uniquely determine a date. M.cal_mayan_haab = new_calendar("mayan-haab", {"month","day"}, mayan.mayan_haab_from_fixed) --- Mayan Tzolkin calendar (read-only). Granularities: `number`, `name`. -- Cannot be used as a source for `new_date` as Tzolkin alone does not uniquely determine a date. M.cal_mayan_tzolkin = new_calendar("mayan-tzolkin", {"number","name"}, mayan.mayan_tzolkin_from_fixed) --- Aztec Xihuitl calendar (read-only). Granularities: `month`, `day`. M.cal_aztec_xihuitl = new_calendar("aztec-xihuitl", {"month","day"}, mayan.aztec_xihuitl_from_fixed) --- Aztec Tonalpohualli calendar (read-only). Granularities: `number`, `name`. M.cal_aztec_tonalpohualli = new_calendar("aztec-tonalpohualli", {"number","name"}, mayan.aztec_tonalpohualli_from_fixed) --- Balinese Pawukon calendar (read-only). -- Granularities: `luang`, `dwiwara`, `triwara`, `caturwara`, `pancawara`, -- `sadwara`, `saptawara`, `asatawara`, `sangawara`, `dasawara`. -- The Pawukon cycle does not uniquely determine a fixed date. M.cal_balinese = new_calendar("balinese", {"luang","dwiwara","triwara","caturwara","pancawara", "sadwara","saptawara","asatawara","sangawara","dasawara"}, balinese.bali_pawukon_from_fixed) --- Chinese calendar. Granularities: `cycle`, `year`, `month`, `leap`, `day`. M.cal_chinese = new_calendar("chinese", {"cycle","year","month","leap","day"}, chinese.chinese_from_fixed, chinese.fixed_from_chinese) --- Japanese lunisolar calendar. Granularities: `cycle`, `year`, `month`, `leap`, `day`. M.cal_japanese = new_calendar("japanese", {"cycle","year","month","leap","day"}, chinese.japanese_from_fixed, chinese.fixed_from_japanese) --- Korean lunisolar calendar. Granularities: `year`, `month`, `leap`, `day`. M.cal_korean = new_calendar("korean", {"year","month","leap","day"}, chinese.korean_from_fixed, chinese.fixed_from_korean) --- Vietnamese lunisolar calendar. Granularities: `cycle`, `year`, `month`, `leap`, `day`. M.cal_vietnamese = new_calendar("vietnamese", {"cycle","year","month","leap","day"}, chinese.vietnamese_from_fixed, chinese.fixed_from_vietnamese) --- Old Hindu solar calendar (Arya Siddhanta). Granularities: `year`, `month`, `day`. M.cal_old_hindu_solar = new_calendar("old-hindu-solar", {"year","month","day"}, old_hindu.old_hindu_solar_from_fixed, old_hindu.fixed_from_old_hindu_solar) --- Old Hindu lunar calendar (Arya Siddhanta). Granularities: `year`, `month`, `leap`, `day`. M.cal_old_hindu_lunar = new_calendar("old-hindu-lunar", {"year","month","leap","day"}, old_hindu.old_hindu_lunar_from_fixed, old_hindu.fixed_from_old_hindu_lunar) --- Modern Hindu solar calendar (Orissa rule). Granularities: `year`, `month`, `day`. M.cal_hindu_solar = new_calendar("hindu-solar", {"year","month","day"}, hindu.hindu_solar_from_fixed, hindu.fixed_from_hindu_solar) --- Astronomical Hindu solar calendar (Tamil rule). Granularities: `year`, `month`, `day`. M.cal_hindu_solar_astronomical = new_calendar("hindu-solar-astronomical", {"year","month","day"}, hindu.astro_hindu_solar_from_fixed, hindu.fixed_from_astro_hindu_solar) --- Modern Hindu lunar calendar. Granularities: `year`, `month`, `leap_month`, `day`, `leap_day`. M.cal_hindu_lunar = new_calendar("hindu-lunar", {"year","month","leap_month","day","leap_day"}, hindu.hindu_lunar_from_fixed, hindu.fixed_from_hindu_lunar) --- Astronomical Hindu lunar calendar. Granularities: `year`, `month`, `leap_month`, `day`, `leap_day`. M.cal_hindu_lunar_astronomical = new_calendar("hindu-lunar-astronomical", {"year","month","leap_month","day","leap_day"}, hindu.astro_hindu_lunar_from_fixed, hindu.fixed_from_astro_hindu_lunar) --- Tibetan calendar. Granularities: `year`, `month`, `leap_month`, `day`, `leap_day`. M.cal_tibetan = new_calendar("tibetan", {"year","month","leap_month","day","leap_day"}, tibetan.tibetan_from_fixed, tibetan.fixed_from_tibetan) --- Icelandic calendar. Granularities: `year`, `season`, `week`, `weekday`. M.cal_icelandic = new_calendar("icelandic", {"year","season","week","weekday"}, icelandic.icelandic_from_fixed, icelandic.fixed_from_icelandic) --- Akan day-name cycle (read-only). Granularities: `prefix`, `stem`. -- The Akan name does not uniquely determine a fixed date. M.cal_akan = new_calendar("akan", {"prefix","stem"}, akan.akan_name_from_fixed) --- Ab Urbe Condita (AUC) year numbering on the Julian calendar. -- Granularities: `year`, `month`, `day`. Year 1 AUC = 753 BCE (Julian). M.cal_auc = new_calendar("auc", {"year","month","day"}, function(rd) local t = julian.julian_from_fixed(rd) return {julian.auc_year_from_julian(t[1]), t[2], t[3]} end, function(t) return julian.fixed_from_julian({julian.julian_year_from_auc(t[1]), t[2], t[3]}) end ) --- Greek Olympiad numbering on the Julian calendar. -- Granularities: `cycle`, `year`, `month`, `day`. -- Cycle 1, year 1 = 776 BCE (Julian). `year` is 1–4 within the 4-year cycle. M.cal_olympiad = new_calendar("olympiad", {"cycle","year","month","day"}, function(rd) local t = julian.julian_from_fixed(rd) local o = julian.olympiad_from_julian_year(t[1]) return {o[1], o[2], t[2], t[3]} end, function(t) return julian.fixed_from_julian({julian.julian_year_from_olympiad({t[1], t[2]}), t[3], t[4]}) end ) --- Samaritan calendar. Granularities: `year`, `month`, `day`. M.cal_samaritan = new_calendar("samaritan", {"year","month","day"}, astro_lunar.samaritan_from_fixed, astro_lunar.fixed_from_samaritan) --- Babylonian calendar (astronomical). Granularities: `year`, `month`, `leap`, `day`. M.cal_babylonian = new_calendar("babylonian", {"year","month","leap","day"}, astro_lunar.babylonian_from_fixed, astro_lunar.fixed_from_babylonian) -- === Holidays === --- @section Holidays local holiday_fns = {} -- Christian / Western holiday_fns["easter"] = eccl.easter holiday_fns["astronomical-easter"] = astro_lunar.astronomical_easter holiday_fns["orthodox-easter"] = eccl.orthodox_easter holiday_fns["pentecost"] = eccl.pentecost holiday_fns["christmas"] = gregorian.christmas holiday_fns["advent"] = gregorian.advent holiday_fns["epiphany"] = gregorian.epiphany holiday_fns["eastern-orthodox-christmas"] = julian.eastern_orthodox_christmas holiday_fns["coptic-christmas"] = coptic.coptic_christmas -- US civic holiday_fns["independence-day"] = gregorian.independence_day holiday_fns["labor-day"] = gregorian.labor_day holiday_fns["memorial-day"] = gregorian.memorial_day holiday_fns["election-day"] = gregorian.election_day holiday_fns["daylight-saving-start"] = gregorian.daylight_saving_start holiday_fns["daylight-saving-end"] = gregorian.daylight_saving_end -- Jewish holiday_fns["rosh-hashanah"] = hebrew.rosh_hashanah holiday_fns["sukkot"] = hebrew.sukkot holiday_fns["shavuot"] = hebrew.shavuot holiday_fns["passover"] = hebrew.passover holiday_fns["yom-kippur"] = hebrew.yom_kippur holiday_fns["purim"] = hebrew.purim holiday_fns["hanukkah"] = hebrew.hanukkah holiday_fns["tishah-be-av"] = hebrew.tishah_be_av holiday_fns["ta-anit-esther"] = hebrew.ta_anit_esther holiday_fns["yom-ha-zikkaron"] = hebrew.yom_ha_zikkaron -- Islamic (arithmetic calendar) holiday_fns["islamic-new-year"] = islamic.islamic_new_year holiday_fns["ramadan"] = islamic.ramadan holiday_fns["eid-al-fitr"] = islamic.eid_al_fitr holiday_fns["eid-al-adha"] = islamic.eid_al_adha holiday_fns["mawlid"] = islamic.mawlid -- Persian holiday_fns["nowruz"] = persian.nowruz -- Bahá'í holiday_fns["bahai-new-year"] = bahai.bahai_new_year holiday_fns["naw-ruz"] = bahai.naw_ruz holiday_fns["feast-of-ridvan"] = bahai.feast_of_ridvan holiday_fns["birth-of-the-bab"] = bahai.birth_of_the_bab -- Balinese holiday_fns["kajeng-keliwon"] = balinese.kajeng_keliwon holiday_fns["tumpek"] = balinese.tumpek -- Chinese holiday_fns["chinese-new-year"] = chinese.chinese_new_year holiday_fns["dragon-festival"] = chinese.dragon_festival holiday_fns["qing-ming"] = chinese.qing_ming -- Hindu holiday_fns["hindu-lunar-new-year"] = hindu.hindu_lunar_new_year holiday_fns["mesha-samkranti"] = function(g_year) return math.floor(hindu.mesha_samkranti(g_year)) end holiday_fns["diwali"] = hindu.diwali holiday_fns["maha-shivaratri"] = hindu.shiva holiday_fns["rama-navami"] = hindu.rama holiday_fns["sacred-wednesdays"] = hindu.sacred_wednesdays -- Tibetan holiday_fns["losar"] = tibetan.tibetan_new_year -- Gregorian holiday_fns["unlucky-fridays"] = gregorian.unlucky_fridays --- Return occurrences of a named holiday in a Gregorian year as date objects. -- @tparam string name Holiday name (e.g. `"easter"`, `"hanukkah"`). -- See `holiday_names` for the full list. -- @tparam number g_year Gregorian year. -- @treturn {table,...} List of date objects (may be empty if the holiday does -- not fall in the given year). -- @raise If `name` is not a registered holiday. function M.holiday(name, g_year) local fn = holiday_fns[name] assert(fn, "unknown holiday: " .. tostring(name)) return normalize(fn(g_year)) end --- Return all holidays occurring in a Gregorian year, sorted by date. -- @tparam number g_year Gregorian year. -- @treturn {{name=string,dates={table,...}},...} List of `{name, dates}` records, -- one per holiday that has at least one occurrence in the year. function M.holidays(g_year) local result = {} for name, fn in pairs(holiday_fns) do local dates = normalize(fn(g_year)) if #dates > 0 then result[#result + 1] = {name = name, dates = dates} end end table.sort(result, function(a, b) return a.dates[1]._rd < b.dates[1]._rd end) return result end -- === Enumeration === --- @section Enumeration --- Return a sorted list of all registered holiday names. -- @treturn {string,...} function M.holiday_names() local names = {} for k in pairs(holiday_fns) do names[#names + 1] = k end table.sort(names) return names end --- Return a sorted list of all built-in calendar names. -- @treturn {string,...} function M.calendar_names() local names = {} for k in pairs(cal_registry) do names[#names + 1] = k end table.sort(names) return names end -- === Astronomy === --- @section Astronomy --- Construct a location for use with astronomical functions. -- @function location -- @tparam number latitude Degrees north (negative = south). -- @tparam number longitude Degrees east (negative = west). -- @tparam number elevation Metres above sea level. -- @tparam number zone UTC offset in hours (e.g. 2 for UTC+2). -- @treturn table Location object. M.location = astro.location --- Lunar phase (0–360°) at midnight of `date`. -- 0 = new moon, 90 = first quarter, 180 = full moon, 270 = last quarter. -- @tparam table date A date object. -- @treturn number Degrees. function M.lunar_phase(date) return astro.lunar_phase(date._rd) end --- List of new-moon date objects falling in Gregorian year `g_year`. -- @tparam number g_year Gregorian year. -- @treturn {table,...} Date objects (in cal_gregorian) for each new moon. function M.new_moons(g_year) local start = gregorian.gregorian_new_year(g_year) local finish = gregorian.gregorian_new_year(g_year + 1) local out, tee = {}, astro.new_moon_at_or_after(start) while tee < finish do out[#out + 1] = make_date(math.floor(tee), cal_registry["gregorian"]) tee = astro.new_moon_at_or_after(tee + 1) end return out end --- List of full-moon date objects falling in Gregorian year `g_year`. -- @tparam number g_year Gregorian year. -- @treturn {table,...} Date objects (in cal_gregorian) for each full moon. function M.full_moons(g_year) local start = gregorian.gregorian_new_year(g_year) local finish = gregorian.gregorian_new_year(g_year + 1) local out, tee = {}, astro.lunar_phase_at_or_after(180, start) while tee < finish do out[#out + 1] = make_date(math.floor(tee), cal_registry["gregorian"]) tee = astro.lunar_phase_at_or_after(180, tee + 1) end return out end --- Standard time of sunrise on `date` at `loc`, or nil if none. -- @tparam table date A date object. -- @tparam table loc A location (from `location`). -- @treturn table|nil `{hour, minute, second, zone}` or nil. function M.sunrise(date, loc) return as_time(astro.sunrise(date._rd, loc), astro.zone(loc)) end --- Standard time of sunset on `date` at `loc`, or nil if none. -- @tparam table date A date object. -- @tparam table loc A location (from `location`). -- @treturn table|nil `{hour, minute, second, zone}` or nil. function M.sunset(date, loc) return as_time(astro.sunset(date._rd, loc), astro.zone(loc)) end --- Standard time of moonrise on `date` at `loc`, or nil if none. -- @tparam table date A date object. -- @tparam table loc A location (from `location`). -- @treturn table|nil `{hour, minute, second, zone}` or nil. function M.moonrise(date, loc) return as_time(astro.moonrise(date._rd, loc), astro.zone(loc)) end --- Standard time of moonset on `date` at `loc`, or nil if none. -- @tparam table date A date object. -- @tparam table loc A location (from `location`). -- @treturn table|nil `{hour, minute, second, zone}` or nil. function M.moonset(date, loc) return as_time(astro.moonset(date._rd, loc), astro.zone(loc)) end return M