Calendrical. Date
(Calendrical v0.13.0)
Copy Markdown
Date parsing and helpers built on Calendrical's calendar implementations and Localize's CLDR data.
When the caller doesn't know in advance whether the input is a
date, time, datetime, or range, use Calendrical.parse/2 — it
dispatches to the appropriate sub-parser.
Summary
Functions
@spec parse(String.t(), Keyword.t()) :: {:ok, Date.t() | map()} | {:error, Exception.t()}
Parses a locale-formatted date string.
Tries, in order: bare ISO-8601 (YYYY-MM-DD), then the
locale's CLDR short/medium/long/full patterns for the
requested calendar. The patterns encode the locale's
preferred field order and any era markers — so the same
input may parse to different dates under different locales
by design.
Returns a Date.t/0 in the calendar identified by the
:calendar option — Calendar.ISO for :gregorian (the
default), Calendrical.Hebrew for :hebrew,
Calendrical.Japanese for :japanese, and so on. Pass
return_calendar: :iso to force the result into Gregorian
regardless (useful for Date.Range, Ecto :date casts, or
any consumer that requires ISO).
Arguments
inputis the raw user input string.optionsis a keyword list of options.
Options
:locale— the locale to interpret the string under. Defaults toLocalize.get_locale/0.:calendar— either a CLDR calendar key (e.g.:gregorian,:buddhist,:islamic_civil,:japanese,:persian,:hebrew) or a calendar module (Calendar.ISO,Calendrical.Hebrew,Calendrical.Buddhist, …). A module is coerced to its CLDR atom via thecldr_calendar_type/0callback;Calendar.ISOis an alias for:gregorian. Defaults to:gregorian. Drives both how the input is interpreted and what calendar the returnedDateis in.:reference_date— the "today" anchor for two-digit-year pivoting. Defaults toDate.utc_today/0.:return_calendar—:native(default) returns the parsed date in whatever:calendarnamed.:isoforcesCalendar.ISO. A calendar module (e.g.Calendrical.Persian) returns the date in that specific calendar.:as—:struct(default) returns aDate.t/0.:mapreturns a bare field map containing only what the input actually supplied (%{month: 5, day: 5, calendar: Calendar.ISO}for"May 5") plus a:calendarkey naming the resolved calendar module. Useful when a downstream library (e.g.Tempo) needs the unresolved partial rather than a defaultedDate. In:mapmode the:reference_datefallback for missing-year inputs is suppressed, and:return_calendarhas no effect (the map stays in the parsing calendar).
Returns
{:ok, Date.t()}on success whenas: :struct(the default).{:ok, map()}on success whenas: :map. The map always has a:calendarkey; the other keys (:year,:month,:day,:quarter,:week_of_year,:week_based_year,:day_of_year,:day_of_week,:day_of_week_in_month) are present only when the input supplied them.{:error, Calendrical.DateParseError.t()}when no pattern matched.
Examples
iex> Calendrical.Date.parse("2026-05-16", locale: :en)
{:ok, ~D[2026-05-16]}
iex> Calendrical.Date.parse("May 5", locale: :en, as: :map)
{:ok, %{calendar: Calendar.ISO, month: 5, day: 5}}
iex> Calendrical.Date.parse("2026", locale: :en, as: :map)
{:ok, %{calendar: Calendar.ISO, year: 2026}}
iex> Calendrical.Date.parse("5/16/26", locale: :en)
{:ok, ~D[2026-05-16]}
iex> Calendrical.Date.parse("16.05.2026", locale: :de)
{:ok, ~D[2026-05-16]}
iex> Calendrical.Date.parse("2026-05-16", locale: :en, calendar: :hebrew)
{:ok, ~D[5786-09-29 Calendrical.Hebrew]}
iex> Calendrical.Date.parse("2026-05-16", locale: :en, calendar: Calendrical.Hebrew)
{:ok, ~D[5786-09-29 Calendrical.Hebrew]}
iex> Calendrical.Date.parse("2026-05-16", locale: :en, calendar: :hebrew, return_calendar: :iso)
{:ok, ~D[2026-05-16]}
iex> Calendrical.Date.parse("Q2 2026", locale: :en)
{:ok, ~D[2026-04-01]}
iex> Calendrical.Date.parse("2nd quarter 2026", locale: :en)
{:ok, ~D[2026-04-01]}
iex> Calendrical.Date.parse("week 20 of 2026", locale: :en)
{:ok, ~D[2026-05-11]}
iex> Calendrical.Date.parse("Saturday, May 16, 2026", locale: :en)
{:ok, ~D[2026-05-16]}
@spec parse_range(String.t() | {String.t(), String.t()}, Keyword.t()) :: {:ok, Date.Range.t() | {map(), map()}} | {:error, Exception.t()}
Parses a locale-formatted date range.
Accepts either a single string (e.g. "May 5 – May 10, 2026")
in which case the parser splits on the locale's CLDR
intervalFormatFallback separator, or a 2-tuple
{from_string, to_string} for two-input UIs that already
have the endpoints split.
Each endpoint is parsed independently via parse/2. The
result is always a Date.Range.t/0; the calendar of the
range's endpoints follows the :calendar option (defaults
to :gregorian, i.e. Calendar.ISO). Date.Range supports
any calendar provided both endpoints share it — both
endpoints are parsed under the same option, so the range is
well-formed for Buddhist, Hebrew, Japanese, Persian, and
every other Calendrical-supported calendar.
Arguments
inputis either a binary or a{from_binary, to_binary}tuple.optionsis a keyword list of options.
Options
Same as parse/2: :locale, :calendar, :reference_date,
:as. Plus:
:allow_inverted— whentrue, an end-before-start range is returned as-is (Elixir'sDate.range/3builds a descending range). Whenfalse(the default), an inverted range is rejected with aCalendrical.DateRangeParseError. Only applies whenas: :struct(the default);as: :mapskips the comparison because partial maps may not have enough fields to compare.
Returns
{:ok, Date.Range.t()}on success whenas: :struct. The endpoints' calendar matches the:calendaroption.{:ok, {from_map, to_map}}on success whenas: :map. Each endpoint is a field map; missing fields are inherited from the other endpoint per the CLDR interval convention (so"May 5 – May 10, 2026"yields two maps both carrying:year).{:error, Calendrical.DateParseError.t() | Calendrical.DateRangeParseError.t()}on failure.
Examples
iex> {:ok, range} = Calendrical.Date.parse_range({"2026-05-05", "2026-05-10"})
iex> {range.first, range.last}
{~D[2026-05-05], ~D[2026-05-10]}
iex> Calendrical.Date.parse_range("May 5 – May 10, 2026", locale: :en, as: :map)
{:ok,
{%{calendar: Calendar.ISO, year: 2026, month: 5, day: 5},
%{calendar: Calendar.ISO, year: 2026, month: 5, day: 10}}}
iex> {:ok, range} = Calendrical.Date.parse_range("May 5, 2026 – May 10, 2026", locale: :en)
iex> {range.first, range.last}
{~D[2026-05-05], ~D[2026-05-10]}
iex> {:ok, range} = Calendrical.Date.parse_range({"2026-05-05", "2026-05-10"}, calendar: :buddhist)
iex> {range.first, range.last}
{~D[2569-05-05 Calendrical.Buddhist], ~D[2569-05-10 Calendrical.Buddhist]}
iex> {:ok, range} = Calendrical.Date.parse_range({"2026-05-05", "2026-05-10"}, calendar: Calendrical.Buddhist)
iex> {range.first, range.last}
{~D[2569-05-05 Calendrical.Buddhist], ~D[2569-05-10 Calendrical.Buddhist]}