defmodule Localize.Inputs.Date.Parser do
@moduledoc """
Front-door parser for locale-aware date form input.
Delegates to `Calendrical.Date.parse/2`. The input layer does
no parsing of its own — this module is a thin policy layer
over the underlying CLDR-driven parser.
"""
@doc """
Parses a locale-formatted date string.
Tries, in order: bare ISO-8601 (`YYYY-MM-DD`), then every
CLDR `availableFormats` skeleton for the (locale, calendar)
tuple. Skeletons encode the locale's preferred field order
(and any era / week-numbering / quarter conventions), so the
same input may parse to different dates under different
locales — by design.
Returns a `Date` 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.
### Arguments
* `string` is the raw user input.
* `options` is a keyword list of options.
### Options
* `:locale` — the locale to interpret the string under. Defaults
to `Localize.get_locale/0`.
* `:calendar` — the CLDR calendar key whose patterns to try
AND the calendar of the returned `Date`. Defaults to
`:gregorian`.
* `:reference_date` — the "today" anchor used 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`.
### Returns
* `{:ok, Date.t()}` on success.
* `{:ok, nil}` for blank input.
* `{:error, Exception.t()}` on parse failure.
### Examples
iex> Localize.Inputs.Date.Parser.parse_date("2026-05-16", locale: :en)
{:ok, ~D[2026-05-16]}
iex> Localize.Inputs.Date.Parser.parse_date("5/16/26", locale: :en)
{:ok, ~D[2026-05-16]}
iex> Localize.Inputs.Date.Parser.parse_date("16/05/2026", locale: :"en-GB")
{:ok, ~D[2026-05-16]}
iex> Localize.Inputs.Date.Parser.parse_date("16.05.2026", locale: :de)
{:ok, ~D[2026-05-16]}
iex> Localize.Inputs.Date.Parser.parse_date("Q2 2026", locale: :en)
{:ok, ~D[2026-04-01]}
iex> Localize.Inputs.Date.Parser.parse_date("week 20 of 2026", locale: :en)
{:ok, ~D[2026-05-11]}
iex> Localize.Inputs.Date.Parser.parse_date("Saturday, May 16, 2026", locale: :en)
{:ok, ~D[2026-05-16]}
iex> Localize.Inputs.Date.Parser.parse_date("2026-05-16", locale: :en, calendar: :hebrew)
{:ok, ~D[5786-09-29 Calendrical.Hebrew]}
iex> Localize.Inputs.Date.Parser.parse_date("", locale: :en)
{:ok, nil}
"""
@spec parse_date(String.t() | nil, Keyword.t()) ::
{:ok, Date.t() | nil} | {:error, Exception.t()}
def parse_date(string, options \\ [])
def parse_date(nil, _options), do: {:ok, nil}
def parse_date("", _options), do: {:ok, nil}
def parse_date(string, options) when is_binary(string) do
Calendrical.Date.parse(String.trim(string), options)
end
end