Calendrical - over 17 localised calendar implementations and date/time parsing

Somehow I missed introducing Calendrical, which implements a significant number of the calendars used around the world. Its the spiritual successor to ex_cldr_calendars and friends, but it also several additional calendars like:

New Date, Time, DateTime and Interval string parsers

New in Calendarical version 0.6.0 published today is flexible date/time parsing.

If you’ve got user-typed date/time strings coming in from a form, an import, or a chat command, this should save you some pain.

There’s a single entry point — Calendrical.parse/2 — that figures out what kind of value you typed and dispatches to the right sub-parser:

iex> Calendrical.parse("2026-05-16", locale: :en)
{:ok, ~D[2026-05-16]}
iex> Calendrical.parse "23 May"
{:ok, ~D[2026-05-23]}
iex> Calendrical.parse("14:30", locale: :en)
{:ok, ~T[14:30:00]}
iex> Calendrical.parse("May 16, 2026, 2:30 PM", locale: :en)
{:ok, ~N[2026-05-16 14:30:00]}
iex> Calendrical.parse("May 5, 2026 – May 10, 2026", locale: :en)
{:ok, Date.range(~D[2026-05-05], ~D[2026-05-10])}

Parsing is locale aware

Every parser reads CLDR data, so the same string may parse differently depending on the locale you pass:

iex> Calendrical.Date.parse("3/4/26", locale: :en)
{:ok, ~D[2026-03-04]}
iex> Calendrical.Date.parse("3/4/26", locale: :"en-GB")
{:ok, ~D[2026-04-03]}
iex> Calendrical.Date.parse("16.05.2026", locale: :de)
{:ok, ~D[2026-05-16]}
iex> Calendrical.parse "23 mai", locale: :fr
{:ok, ~D[2026-05-23]}
iex> Calendrical.Time.parse("2:30 PM", locale: :en)
{:ok, ~T[14:30:00]}
iex> Calendrical.Time.parse("14:30", locale: :de)
{:ok, ~T[14:30:00]}

Parsing supports any known calendar

Pass :calendar to interpret input pretty much all of the worlds major calendars — Gregorian, Buddhist, Japanese imperial, Islamic, Hebrew, Persian, ROC, Coptic, Ethiopic, Indian, and more. The returned Date is in that calendar:

iex> Calendrical.Date.parse("2026-05-16", calendar: Calendrical.Hebrew)
{:ok, ~D[5786-09-29 Calendrical.Hebrew]}
iex> Calendrical.Date.parse("民國115年5月16日", locale: :"zh-Hant-TW", calendar: Calendrical.Roc)
{:ok, ~D[0115-05-16 Calendrical.Roc]}
iex> Calendrical.Date.parse("令和6年7月1日", locale: :"ja-JP", calendar: Calendrical.Japanese)
{:ok, ~D[2024-07-01 Calendrical.Japanese]}
iex> Calendrical.Date.parse("١٧ رمضان ١٤٣٥ هـ", locale: :"ar-SA", calendar: Calendrical.Islamic.Civil)
{:ok, ~D[1435-09-17 Calendrical.Islamic.Civil]}
iex> Calendrical.Date.parse("1 ก.ค. 2567", locale: :"th-TH", calendar: Calendrical.Buddhist)
{:ok, ~D[2567-07-01 Calendrical.Buddhist]}

Parsing date ranges

Date ranges follow the same calendar rule — Date.Range works in any calendar, not just Calendar.ISO:

iex> Calendrical.Date.parse_range({"2026-05-05", "2026-05-10"}, calendar: Calendrical.Buddhist])
{:ok, Date.range(~D[2569-05-05 Calendrical.Buddhist], ~D[2569-05-10 Calendrical.Buddhist])}
iex> Calendrical.Date.parse_range("May 5 – May 10, 2026", locale: :en)
{:ok, Date.range(~D[2026-05-05], ~D[2026-05-10])}

That last example also shows CLDR interval-skeleton inheritance — the left endpoint has no year, so it inherits 2026 from the right.

Going directly to the individual parsers

If you already know the shape of your input, skip Calendrical.parse/2 and call the relevant parser directly:

  • Calendrical.Date.parse/2
  • Calendrical.Time.parse/2
  • Calendrical.DateTime.parse/2
  • Calendrical.Date.parse_range/2

Each accepts :locale, :calendar (where applicable), and a few specialised options like :allow_inverted for ranges and :reference_date for two-digit-year pivoting.

Links

21 Likes

Thank you for providing all the Localize libraries. Amazing job, they work great and make the dev life much easier.
Just noticed the GitHub link in the post is not working however the GitHub link in the hex package is working.

2 Likes

Looks like @AstonJ (thanks!) already beat me to it - the link is correct now.

1 Like

Wonderful! Maybe I can deprecate date_time_parser! It doesn’t support locales very well and I’m glad Calendrical does.

TL;DR: your lib remains incredibly useful, Calendrical covers a distinct set of requirements - but definitely not all of them.

There are a number of use cases that date_time_parser supports that Calendrical does not and I consider out of scope.

Date/Time Parser Support Why Calendrical doesn’t
Unix epoch / Excel serial numerics (-9999999999, 30134, 62.0, etc.) Out of scope — no locale, calendar, or format pattern involved
Quote/equals CSV wrappers (""=""9/5/2018""") Out of scope — CSV preprocessing, not parsing
Unix date(1) output, year at end (Fri Mar 2 09:01:57 2018) Non-CLDR pattern; would need synthesised fallback
JS Date.toString() (Fri Mar 31 2017 21:41:40 GMT+0000 (UTC)) Non-CLDR pattern; JS-specific
mm:ss.frac time (07:09.3) Non-CLDR; DTP-specific convention
ISO date-only with offset (2017-09-29+00:00) Non-standard ISO 8601 shape

Fair enough! And yes it’s all those non-standard formats that I remember being problematic in CSV imports that had a variety of these formats that I made date_time_parser for.

in any case, wonderful job with Calendrical!