Calendrical

Localized month- and week-based calendars, fiscal-year support, calendar arithmetic, and 17+ CLDR-based calendar systems for Elixir, built on the Unicode CLDR repository via Localize.

Calendrical extends Elixir's standard Calendar and Date modules with comprehensive support for the calendar systems used around the world, including arithmetic and astronomical lunar calendars, year-shifted variants such as Buddhist and ROC, and the official tabular and observational Islamic calendars.

Features

Supported Elixir and OTP versions

Calendrical requires Elixir 1.17+ and Erlang/OTP 26+.

Installation

Add calendrical to your dependencies in mix.exs:

def deps do
  [
    {:calendrical, "~> 0.1.0"}
  ]
end

Quick start

iex> # Convert a Gregorian date to the Hebrew calendar
iex> {:ok, gregorian} = Date.new(2024, 10, 3, Calendrical.Gregorian)
iex> {:ok, hebrew} = Date.convert(gregorian, Calendrical.Hebrew)
iex> hebrew
~D[5785-01-01 Calendrical.Hebrew]

iex> # Localize the month name
iex> Calendrical.localize(hebrew, :month, locale: "en")
"Tishri"

iex> Calendrical.localize(hebrew, :month, locale: "he", format: :wide)
"תשרי"

iex> # Convert to the Islamic Umm al-Qura calendar
iex> {:ok, hijri} = Date.convert(gregorian, Calendrical.Islamic.UmmAlQura)
iex> Calendrical.localize(hijri, :month, locale: "en")
"Rab. I"

iex> # Buddhist Era (Thailand)
iex> {:ok, buddhist} = Date.convert(gregorian, Calendrical.Buddhist)
iex> buddhist.year
2567

iex> # Get a date range for a fiscal year quarter
iex> {:ok, calendar} = Calendrical.FiscalYear.calendar_for(:US)
iex> Calendrical.Interval.quarter(2024, 1, calendar)

iex> # Find the second Tuesday in November 2024 (Tuesday = day 2)
iex> Calendrical.Kday.nth_kday(~D[2024-11-01], 2, 2)
~D[2024-11-12]

iex> # Western Easter Sunday for 2024 (Gregorian computus)
iex> Calendrical.Ecclesiastical.easter_sunday(2024)
~D[2024-03-31 Calendrical.Gregorian]

iex> # Eastern Orthodox Easter Sunday for 2024 (Julian computus)
iex> Calendrical.Ecclesiastical.orthodox_easter_sunday(2024)
~D[2024-04-22 Calendrical.Julian]

iex> # Astronomical Paschal Full Moon for 2025
iex> {:ok, pfm} = Calendrical.Ecclesiastical.paschal_full_moon(2025)
iex> pfm
~D[2025-04-13]

Available calendars

Calendrical implements all 17 calendar systems exposed by CLDR. They are grouped below by their underlying mechanism. See guides/calendar_summary.md for the full descriptions, eras, month structures, and reference dates.

Family Calendars
Gregorian-based (year-offset over Calendrical.Gregorian) Calendrical.Gregorian, Calendrical.ISOWeek, Calendrical.NRF, Calendrical.Buddhist, Calendrical.Roc, Calendrical.Japanese, Calendrical.Indian
Julian-based (proleptic Julian + variants) Calendrical.Julian, Calendrical.Julian.Jan1, Calendrical.Julian.March1, Calendrical.Julian.March25, Calendrical.Julian.Sept1, Calendrical.Julian.Dec25
Solar (non-Gregorian)Calendrical.Persian (astronomical)
Lunar (tabular)Calendrical.Coptic, Calendrical.Ethiopic, Calendrical.Ethiopic.AmeteAlem, Calendrical.Islamic.Civil, Calendrical.Islamic.Tbla, Calendrical.Islamic.UmmAlQura
Lunar (observational/astronomical)Calendrical.Islamic.Observational (Cairo), Calendrical.Islamic.Rgsa (Mecca), Calendrical.Islamic.UmmAlQura.Astronomical
LunisolarCalendrical.Hebrew (arithmetic), Calendrical.Chinese, Calendrical.Korean (Dangi), Calendrical.LunarJapanese
CompositeCalendrical.Composite (user-defined; e.g. England with Julian-to-Gregorian transition)
Fiscal-yearCalendrical.FiscalYear.US, .AU, .UK, … (50+ territories)

Defining your own calendar

Calendrical exposes the same Calendrical.Behaviour macro that the built-in calendars use. A custom calendar typically needs to define its own date_to_iso_days/3 and date_from_iso_days/1, plus override one or two callbacks (leap_year?/1, days_in_month/2, etc.) when its rules differ from the defaults.

defmodule MyApp.MyCalendar do
  use Calendrical.Behaviour,
    epoch: ~D[0001-01-01 Calendar.ISO],
    cldr_calendar_type: :gregorian

  @impl true
  def leap_year?(year), do: rem(year, 4) == 0

  def date_to_iso_days(year, month, day) do
    # ... calendar-specific calculation
  end

  def date_from_iso_days(iso_days) do
    # ... calendar-specific calculation
  end
end

See guides/calendar_behaviour.md for the full list of options, generated functions, and overridable callbacks.

Localization

All calendars participate in CLDR localization automatically. Calling Calendrical.localize/3 with a date and a part (:era, :month, :quarter, :day_of_week, :days_of_week, :am_pm, :day_periods) returns the locale-specific name through the Localize.Calendar data layer.

iex> {:ok, date} = Date.new(1446, 9, 1, Calendrical.Islamic.UmmAlQura)
iex> Calendrical.localize(date, :month, locale: "en", format: :wide)
"Ramadan"

iex> Calendrical.localize(date, :month, locale: "ar")
"رمضان"

iex> Calendrical.localize(date, :day_of_week, locale: "en", format: :wide)
"Saturday"

Calendrical.strftime_options!/1 returns a keyword list compatible with Calendar.strftime/3 so the standard library's formatter can produce locale-aware output for any Calendrical calendar.

Composite calendars

A composite calendar uses one base calendar before a specified transition date and another afterwards. The canonical example is the European transition from the Julian to the Gregorian calendar in the 16th–20th centuries.

defmodule MyApp.England do
  use Calendrical.Composite,
    calendars: [
      ~D[1155-03-25 Calendrical.Julian.March25],
      ~D[1751-03-25 Calendrical.Julian.Jan1],
      ~D[1752-09-14 Calendrical.Gregorian]
    ],
    base_calendar: Calendrical.Julian
end

# 11 days are "missing" at the September 1752 transition
iex> Date.shift(~D[1752-09-02 MyApp.England], day: 1)
~D[1752-09-14 MyApp.England]

A composite calendar can chain any number of transitions and combine any pair of calendars. See Calendrical.Composite for details.

Configuration

Calendrical inherits the locale and provider configuration from Localize. The only Calendrical-specific configuration is for the few lunisolar calendars that accept a custom epoch:

config :calendrical,
  chinese_epoch: ~D[-2636-02-15],
  korean_epoch: ~D[-2332-02-15],
  lunar_japanese_epoch: ~D[0645-07-20]
Option Default Description
:chinese_epoch~D[-2636-02-15] The first sexagesimal cycle origin used by the Chinese calendar.
:korean_epoch~D[-2332-02-15] The founding-of-Korea origin used by the Korean (Dangi) calendar.
:lunar_japanese_epoch~D[0645-07-20] The Taika-era origin used by the Lunar Japanese calendar.

For Calendrical's underlying locale, default-locale, and locale-cache configuration, see the Localize configuration documentation.

Documentation

Full API documentation is available on HexDocs.

License

Apache License 2.0. See the LICENSE file for details.