Manual
A guide to the high-level calendrica API: creating dates, converting between calendars, looking up holidays, and computing astronomical events. For the list of every built-in calendar and holiday, see the Calendars and Holidays pages. For the per-calendar low-level modules, see the module index.
All examples assume:
local calendrica = require("calendrica")
Core concepts
Three ideas underpin the whole API:
- Rata Die (RD). Every date is stored internally as a single integer, the fixed or Rata Die day number, counting days from the proleptic Gregorian date 1 January of year 1 (RD 1). All conversions go through this common representation, so any calendar can be expressed in any other.
- Calendars are built-in names or calendar objects. A built-in calendar is named with a
string, e.g.
"gregorian","hebrew","islamic-umalqura";calendrica.calendars()lists them all. The names are fixed — nothing can be registered under a name, so they always mean the same. A calendar of your own is an object made withcalendar{…}(see below), accepted wherever a name is. - Dates are immutable. A date never changes in place; to obtain a different date — for example by converting it to another calendar — you create a new one. Assigning to a field of a date raises an error.
Dates
Creating a date
date(source, calendar) builds a date in a calendar, given by name or as a calendar
object. The first argument may be:
-- from named fields, interpreted in that calendar local d = calendrica.date({year = 2024, month = 1, day = 1}, "gregorian") -- from a fixed RD integer (the inverse of the .rd field, below) local d2 = calendrica.date(738886, "gregorian") -- from an existing date, re-expressed in another calendar local h = calendrica.date(d, "hebrew")
Field names depend on the calendar’s granularities — most use year, month, day,
but others differ (the Chinese calendar has cycle, year, month, leap, day; the
ISO week calendar has year, week, day). See the Calendars page.
Reading fields
Fields are read as properties. They are computed on first access and cached:
print(d.year, d.month, d.day) -- 2024 1 1
Every date also exposes two universal properties:
print(d.rd) -- 738886 its fixed Rata Die integer print(d.calendar.name) -- gregorian its calendar (an object, see below)
Converting between calendars
Because a date is anchored to an RD, converting is just re-expressing the same day in a different calendar. Four equivalent spellings:
local h = calendrica.date(d, "hebrew") local h = d:date("hebrew") local h = d:as("hebrew") local h = d:to("hebrew") print(h) -- hebrew(year=5784, month=10, day=20, rd=738886) print(h.year, h.month, h.day) -- 5784 10 20
Note the .rd is identical (738886) — it is the same day, seen through a different
calendar.
Printing a date
Converting a date to a string lists its fields and RD:
print(d) -- gregorian(year=2024, month=1, day=1, rd=738886)
Immutability
Dates are read-only. Assigning to a field raises an error — create a new date instead:
d.year = 2025 -- error: date objects are read-only; create a new one with date(...)
Field overflow is normalized
You do not need to keep fields in range: out-of-range values are carried into the neighbouring unit, which makes simple date arithmetic easy.
print(calendrica.date({year = 2024, month = 13, day = 1}, "gregorian")) -- gregorian(year=2025, month=1, day=1, rd=739252) print(calendrica.date({year = 2024, month = 1, day = 0}, "gregorian")) -- gregorian(year=2023, month=12, day=31, rd=738885)
Today
today(calendar) returns the current date, defaulting to Gregorian:
local now = calendrica.today() -- Gregorian local heb = calendrica.today("hebrew")
Holidays
holiday(name, year) returns the occurrences of a named holiday in a given Gregorian
year, as a list of date objects (empty if it does not fall in that year):
local dates = calendrica.holiday("shavuot", 2026) print(dates[1]) -- gregorian(year=2026, month=5, day=22, rd=739758) print(dates[1].rd) -- 739758
Some holidays occur more than once a year (e.g. unlucky-fridays, tumpek), so the
result is always a list.
Choosing the calendar for the result
holiday (and holidays) take an optional trailing calendar (a name or an object); the
returned dates
are expressed in that calendar (the underlying day is unchanged):
print(calendrica.holiday("shavuot", 2026, "hebrew")[1]) -- hebrew(year=5786, month=3, day=6, rd=739758)
All holidays in a year
holidays(year) returns every holiday with at least one occurrence in the year, sorted
by date, as {name = …, dates = {…}} records:
for _, h in ipairs(calendrica.holidays(2026)) do print(h.name, h.dates[1]) end
It also accepts an optional calendar.
Listing holiday names
for _, name in ipairs(calendrica.holiday_names()) do print(name) end
The full list with descriptions is on the Holidays page.
Astronomy
Locations
Astronomical functions need a location, built with location(latitude, longitude,
elevation, zone):
local jerusalem = calendrica.location(31.78, 35.24, 754, 2) -- lat, lon, elevation (m), UTC+2 print(jerusalem) -- location(latitude=31.78, longitude=35.24, elevation=754, zone=UTC+2)
latitude— degrees north (negative south). Any value is accepted; values beyond ±90 are folded over the poles.longitude— degrees east (negative west). Any value is accepted and wrapped into [-180, 180).elevation— metres above sea level (may be negative). Unrestricted.zone— the UTC offset in hours (e.g.2for UTC+2,5.5for UTC+5:30).
A location is an immutable value object with .latitude, .longitude, .elevation,
and .zone (reported back in hours).
Sunrise, sunset, moonrise, moonset
These return a moment: a date object that also carries a time-of-day. They return
nil when the event does not occur (e.g. polar day/night):
local d = calendrica.date({year = 2024, month = 1, day = 1}, "gregorian") local sr = calendrica.sunrise(d, jerusalem) print(sr) -- gregorian(year=2024, month=1, day=1, rd=738886, 06:33:32 UTC+2)
A moment adds these properties on top of a normal date:
.hour,.minute,.second— the standard-time clock (.secondis fractional);.zone— the UTC offset in hours;.moment— the instant as a fractional RD (the day plus the time-of-day).
print(sr.hour, sr.minute) -- 6 33 print(sr.zone) -- 2.0 print(sr.moment) -- 738886.27...
A moment is still a date, so it converts like one — and the time-of-day is preserved:
print(sr:to("hebrew")) -- hebrew(...same day..., 06:33:32 UTC+2)
A plain date has no time, so .hour, .minute, .second, and .zone are nil on it,
and .moment equals .rd.
Passing a Rata Die directly
sunrise, sunset, moonrise, moonset, and lunar_phase also accept a raw RD
integer instead of a date object (the resulting moment is then Gregorian):
print(calendrica.sunrise(738886, jerusalem))
Lunar phase
lunar_phase(date) returns the phase angle in degrees at midnight: 0 is new moon, 90
first quarter, 180 full moon, 270 last quarter.
print(calendrica.lunar_phase(d)) -- e.g. 235.95
lunar_phases(phase, year) returns the dates in a Gregorian year on which the moon
reaches a given phase; an optional calendar names the result calendar:
local new_moons = calendrica.lunar_phases(0, 2026) -- Gregorian local full_moons = calendrica.lunar_phases(180, 2026, "islamic") -- Hijri dates
Enumerating what is available
-- sorted built-in calendar names, and each one's ordered granularity (field) names for _, name in ipairs(calendrica.calendars()) do print(name, table.concat(calendrica.calendar(name).granularities, ", ")) end -- sorted holiday names for _, name in ipairs(calendrica.holiday_names()) do print(name) end
The rendered lists with descriptions are the Calendars and Holidays pages.
Calendar objects
calendar(name) returns the object of a built-in calendar (or nil for an unknown
name). A calendar object is read-only, with the fields name and granularities, and
can be passed wherever a calendar name can; a date’s .calendar is its calendar object:
local hebrew = calendrica.calendar("hebrew") print(hebrew.name, table.concat(hebrew.granularities, ", ")) -- hebrew year, month, day local h = calendrica.date(d, hebrew) print(h.calendar == hebrew) -- true
Calendars of your own
calendar{…} makes a new calendar from two conversion functions — from_rd maps an RD
to a positional table of fields, and to_rd maps a positional table back to an RD (the
field names you list in granularities are matched to those positions):
local greg = require("calendrica-gregorian") local mine = calendrica.calendar{ name = "my-calendar", -- for display only granularities = {"year", "month", "day"}, from_rd = greg.gregorian_from_fixed, -- RD -> {year, month, day} to_rd = greg.fixed_from_gregorian, -- {year, month, day} -> RD } local d = calendrica.date({year = 2024, month = 1, day = 1}, mine)
Omit to_rd for a read-only calendar (one that can be converted to but not
constructed from, like the Mayan Haab).
The calendar is not registered under its name: keep the object (in a table of your own, if you want to look calendars up by name). So a calendar of your own never clashes with a built-in one, or with another package’s.
Holidays of your own
The holidays of holiday and holidays are the built-in ones. A holiday of your own is
just a function of the Gregorian year; date turns its result into dates:
-- every 1 May local function may_day(year) return calendrica.date({year = year, month = 5, day = 1}, "gregorian") end print(may_day(2026)) -- gregorian(year=2026, month=5, day=1, ...) print(calendrica.date(may_day(2026), "hebrew"))
The low-level API
Each calendar is also a standalone module that works directly with RD integers and positional tables, and sometimes exposes extra functions (leap-year predicates, holiday helpers, and so on):
local gregorian = require("calendrica-gregorian") local hebrew = require("calendrica-hebrew") local rd = gregorian.fixed_from_gregorian({2024, 1, 1}) local h = hebrew.hebrew_from_fixed(rd) print(h[1], h[2], h[3]) -- year, month, day
See the module index for every module’s reference.