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 with calendar{…} (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. 2 for UTC+2, 5.5 for 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 (.second is 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.

generated by LDoc 1.5.0 Last updated 2026-10-01 02:01:33