# `Calendrical.Date`

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.

# `parse`

```elixir
@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 `t: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

* `input` is the raw user input string.

* `options` is a keyword list of options.

### Options

* `:locale` — the locale to interpret the string under.
  Defaults to `Localize.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 the `cldr_calendar_type/0` callback;
  `Calendar.ISO` is an alias for `:gregorian`. Defaults to
  `:gregorian`. Drives both how the input is interpreted
  and what calendar the returned `Date` is in.

* `:reference_date` — the "today" anchor for two-digit-year
  pivoting. Defaults to `Date.utc_today/0`.

* `:return_calendar` — `:native` (default) returns the
  parsed date in whatever `:calendar` named. `:iso` forces
  `Calendar.ISO`. A calendar module (e.g.
  `Calendrical.Persian`) returns the date in that
  specific calendar.

* `:as` — `:struct` (default) returns a `t:Date.t/0`.
  `:map` returns a bare field map containing only what the
  input actually supplied (`%{month: 5, day: 5, calendar:
  Calendar.ISO}` for `"May 5"`) plus a `:calendar` key
  naming the resolved calendar module. Useful when a
  downstream library (e.g.
  [`Tempo`](https://github.com/elixir-localize/tempo))
  needs the unresolved partial rather than a defaulted
  `Date`. In `:map` mode the `:reference_date` fallback for
  missing-year inputs is suppressed, and `:return_calendar`
  has no effect (the map stays in the parsing calendar).

### Returns

* `{:ok, Date.t()}` on success when `as: :struct` (the
  default).

* `{:ok, map()}` on success when `as: :map`. The map
  always has a `:calendar` key; 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]}

# `parse_range`

```elixir
@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 `t: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

* `input` is either a binary or a `{from_binary, to_binary}`
  tuple.

* `options` is a keyword list of options.

### Options

Same as `parse/2`: `:locale`, `:calendar`, `:reference_date`,
`:as`. Plus:

* `:allow_inverted` — when `true`, an end-before-start
  range is returned as-is (Elixir's
  `Date.range/3` builds a descending range). When `false`
  (the default), an inverted range is rejected with a
  `Calendrical.DateRangeParseError`. Only applies when
  `as: :struct` (the default); `as: :map` skips the
  comparison because partial maps may not have enough
  fields to compare.

### Returns

* `{:ok, Date.Range.t()}` on success when `as: :struct`.
  The endpoints' calendar matches the `:calendar` option.

* `{:ok, {from_map, to_map}}` on success when `as: :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]}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
