# `Localize.DateTime`
[🔗](https://github.com/elixir-localize/localize/blob/v1.4.0/lib/localize/datetime.ex#L1)

Provides localized formatting of `DateTime`, `NaiveDateTime`,
and datetime-like maps.

The primary function is `to_string/2` which accepts a datetime
value and an options keyword list. Format patterns are defined
in CLDR and described in
[TR35](http://unicode.org/reports/tr35/tr35-dates.html).

> #### Ordinal days are a technical preview {: .warning}
>
> The `ddd` field formats the day as a date ordinal ("Jul 6th"). TR35
> designates the section a technical preview, so both the output and the
> surface may change. `Localize.Date` documents the detail.

## Predefined formats

* `:short` — abbreviated date and time (e.g., "1/2/25, 3:04 PM").

* `:medium` — standard date and time (default).

* `:long` — includes time zone name.

* `:full` — verbose day-of-week, date, and time zone.

Custom CLDR skeleton strings and raw format patterns are also
supported via the `:format` option.

# `numeric_separators`

```elixir
@spec numeric_separators(Localize.locale(), atom()) ::
  {:ok, map()} | {:error, Exception.t()}
```

Returns the numeric date and time separators a locale uses.

These are CLDR 49's `numericDateSeparator` and `numericTimeSeparator`, which
TR35 designates a technical preview — the data and the behaviour may change.
TR35 offers them so an implementation can let a caller choose a
separator — dates as "05/06/2006" or "05-06-2006", times as "23:59" or
"23.59" — which `to_string/2` does through its `:numeric_date_separator`
and `:numeric_time_separator` options. Read them to show a caller what
the locale uses before they override it.

### Arguments

* `locale` is any locale returned by `Localize.all_locale_ids/0` or a
  `t:Localize.LanguageTag.t/0`. The default is `Localize.get_locale/0`.

* `calendar_type` is a CLDR calendar name. The default is `:gregorian`.

### Returns

* `{:ok, separators}` where `separators` is a map with the keys
  `:numeric_date_separator` and `:numeric_time_separator`.

* `{:error, exception}` if the locale or calendar is unknown, or the
  locale carries no separator data.

### Examples

    iex> Localize.DateTime.numeric_separators(:en)
    {:ok, %{numeric_date_separator: "/", numeric_time_separator: ":"}}

    iex> Localize.DateTime.numeric_separators(:fi)
    {:ok, %{numeric_date_separator: ".", numeric_time_separator: "."}}

# `parse`

```elixir
@spec parse(String.t(), Keyword.t()) ::
  {:ok, NaiveDateTime.t() | DateTime.t()} | {:error, Exception.t()}
```

Parses a localized date and time string.

Accepts any shape the locale accepts, including the locale's CLDR short,
medium, long and full patterns and ISO 8601.

ISO 8601 is read in every form it joins a date to a time with a `T`:
the date as a calendar date, a day of the year (`"2026-167"`) or a week
date (`"2026-W25-2"`), the time with its seconds, or its minutes and
seconds, left out (`"2026-06-16T10:30"`), and either without its
separators (`"20260616T103000"`). A fraction is the second's alone: a
fraction of a minute or of an hour is not read.

An input carrying a fixed UTC offset resolves to a `t:DateTime.t/0`,
whether written as an ISO 8601 offset (`+05:30`, `Z`) or in the
locale's GMT format (`GMT+10:30`, `UTC-5`). Its time zone is the offset
itself, `"+05:30"`, as ECMA-262 Temporal and RFC 9557 name the zone of
a fixed offset, and `"Etc/UTC"` for no offset at all; see
`Localize.TimeZoneDatabase` for the functions of `DateTime` that look
such a zone up. A named zone (`PST`,
`Asia/Tokyo`) carries no offset of its own and needs a time-zone
database, which the companion
[calendrical](https://hex.pm/packages/calendrical) package supplies;
without it the zone is dropped and the result is a
`t:NaiveDateTime.t/0`.

### Arguments

* `string` is a string in any shape the locale accepts, including the
  locale's CLDR short, medium, long and full patterns and ISO 8601.

* `options` is a keyword list of options.

### Options

* `:locale` is a locale identifier. The default is the locale returned by
  `Localize.get_locale/0`.

* `:calendar` is a calendar module, such as `Calendar.ISO` (the
  default), `Calendrical.Gregorian` or `Calendrical.Hebrew`. The input is
  read with the locale's patterns for the calendar's CLDR type, and the
  date is built and returned in this module. A week date, in any
  form ISO 8601 writes one in (`"2024-W05-4"`, `"2024W054"`), is the
  calendar's own, as its `date_from_week_date/3` answers: ISO 8601's
  week for `Calendar.ISO`, and the fifth week of its own year for a
  calendar built of weeks. Text that none of the calendar's own
  formats reads is read in the calendar its `info/0` names as
  `:parsing_calendar`, and converted, as `Localize.Date.parse/2` reads a date. A
  calendar's own formats come before ISO 8601: a year, a
  month and a day between hyphens that any of them reads, as CLDR's
  root short date of the Chinese calendar does (`r-MM-dd`), and a time
  after a space are the calendar's own, and where none does, or with
  ISO 8601's `T`, the text is an ISO 8601 date and time, converted into
  the calendar. Anything that is not a calendar module,
  including a CLDR calendar name such as `:hebrew` or `"gregorian"`, and
  a module that implements only the `Calendar` behaviour, returns a
  `t:Localize.UnknownCalendarError.t/0`.

* `:format` is the format the date and time were written with, as `to_string/2` takes it: a standard format (`:short`, `:medium`, `:long` or `:full`), a skeleton atom such as `:yMdHm`, a `Localize.DateTime.SemanticSkeleton`, or a pattern string such as `"d/M/y HH:mm"`. Each half is read with its part of it and with nothing else, ISO 8601 included. A standard format is the date's and the time's alike. A skeleton is split into its date fields and its time fields, and so is a semantic skeleton, its zone going with its time. A pattern, which writes its date fields in one run and its time fields in another, is split at the text between them, where the input is split too. The default is `nil`: the text is read as ISO 8601 or in whichever of the locale's formats reads it first.

* `:date_format` and `:time_format` are the formats the date and the time were written with, each as `Localize.Date.parse/2` and `Localize.Time.parse/2` take `:format`, and take the place of the halves of `:format`. A half with no format is read in any of the locale's formats.

* `:reference_date` is the `t:Date.t/0` that partial input is completed
  against, taken in the calendar the input is read in. The default is
  today.

* `:as` is `:struct` or `:map`. `:map` returns only the fields the input
  actually carried, rather than completing them; a field no date or
  time has is an error in both forms. A zone adds the
  `t:DateTime.t/0` zone fields (`:time_zone`, `:utc_offset`, `:std_offset`
  and `:zone_abbr`) as the struct form resolves them: a fixed offset
  always, and a named zone when the input gives the full date its offset
  depends on; otherwise `:time_zone` holds the zone as written. A date
  and time read in another calendar and converted, as a calendar of
  weeks reads one, comes back whole, but for a week written without its
  day, which is the calendar's year and week. The default is `:struct`.

### Returns

* `{:ok, value}` where `value` is a `t:NaiveDateTime.t/0`, or a
  `t:DateTime.t/0` when the input carried a resolvable zone, or

* `{:error, exception}` if the string does not parse, or a
  `t:Localize.InvalidValueError.t/0` if `string` is not a string or an
  option is malformed.

### Examples

    iex> Localize.DateTime.parse("22.03.2026, 14:30", locale: :de)
    {:ok, ~N[2026-03-22 14:30:00]}

    iex> Localize.DateTime.parse("March 22, 2026, 2:30 PM", locale: :en)
    {:ok, ~N[2026-03-22 14:30:00]}

    iex> Localize.DateTime.parse("2026-W12-7T14:30", locale: :en)
    {:ok, ~N[2026-03-22 14:30:00]}

    iex> {:ok, map} = Localize.DateTime.parse("March 22, 2026, 2:30 PM GMT+5", locale: :en, as: :map)
    iex> Map.take(map, [:hour, :time_zone, :utc_offset])
    %{hour: 14, time_zone: "+05:00", utc_offset: 18000}

# `to_parts`

```elixir
@spec to_parts(map(), Keyword.t()) ::
  {:ok, [%{type: atom(), value: String.t()}]} | {:error, Exception.t()}
```

Formats a datetime into typed parts, mirroring ECMA-402's `formatToParts`.

The parts concatenate to exactly the string `to_string/2` produces with the same options. Each pattern field is tagged with its type (`:year`, `:month`, `:day`, `:weekday`, `:hour`, `:minute`, `:second`, `:day_period`, `:time_zone_name`, `:era`, `:fractional_second`, `:literal`, …). Standard formats, skeleton atoms, explicit pattern strings, and combined date+time wrappers all decompose.

### Arguments

* `datetime` is a `t:DateTime.t/0`, `t:NaiveDateTime.t/0`, or any map with date and time keys.

* `options` is a keyword list of options.

### Options

See `to_string/2` for the supported options.

### Returns

* `{:ok, parts}` where `parts` is a list of `%{type: atom(), value: String.t()}` maps.

* `{:error, exception}` if the datetime cannot be formatted.

### Examples

    iex> Localize.DateTime.to_parts(~N[2017-07-10 14:30:00], format: :hm, locale: :en, prefer: :ascii)
    {:ok,
     [
       %{type: :hour, value: "2"},
       %{type: :literal, value: ":"},
       %{type: :minute, value: "30"},
       %{type: :literal, value: " "},
       %{type: :day_period, value: "PM"}
     ]}

# `to_parts!`

```elixir
@spec to_parts!(map(), Keyword.t()) :: [%{type: atom(), value: String.t()}]
```

Same as `to_parts/2` but raises on error.

### Arguments

* `datetime` is a `t:DateTime.t/0`, `t:NaiveDateTime.t/0`, or any map with date and time keys.

* `options` is a keyword list of options. See `to_parts/2`.

### Returns

* A list of `%{type: atom(), value: String.t()}` maps.

### Raises

* Raises an exception if the datetime cannot be formatted.

### Examples

    iex> Localize.DateTime.to_parts!(~N[2017-07-10 14:30:00], format: :hm, locale: :en, prefer: :ascii) |> length()
    5

# `to_string`

```elixir
@spec to_string(map(), Keyword.t()) :: {:ok, String.t()} | {:error, Exception.t()}
```

Formats a datetime according to a CLDR format pattern.

### Arguments

* `datetime` is a `t:DateTime.t/0`, `t:NaiveDateTime.t/0`,
  or any map with date or time keys. A map holding only some of
  the date and time fields formats its date half and its time
  half separately and joins them; one holding only date keys or
  only time keys is formatted as `Localize.Date` or
  `Localize.Time` would format it.

* `options` is a keyword list of options.

### Options

* `:format` is a standard format name (`:short`, `:medium`,
  `:long`, `:full`), a format skeleton atom, a
  `Localize.DateTime.SemanticSkeleton` or a format pattern
  string. The default is `:medium`. A standard format sets the
  width of the date and the time together; `:date_format` and
  `:time_format` override each axis separately.
  A value with no time zone, a `t:NaiveDateTime.t/0` or a map
  holding neither `:time_zone` nor `:utc_offset`, is written at
  `:long` and `:full` with the time `Localize.Time.to_string/2`
  writes for it, the format's fields without its zone.

* `:date_format` and `:time_format` are standard format names,
  skeleton atoms or semantic skeletons that set the date half
  and the time half independently, each defaulting to `:format`. Use them for
  the common "full date, short time" pairing:
  `date_format: :full, time_format: :short` renders
  "Wednesday, April 8, 2026, 12:00 PM". When `:date_format`
  is given it also selects the wrapper width.

* `:style` selects the CLDR pattern that joins the date and
  the time. `:at` (the default) uses the locale's "at time"
  wrapper ("April 8, 2026 at 12:00:00 PM", de "8. April 2026 um
  12:00:00"); `:default` uses the standard wrapper ("April 8,
  2026, 12:00:00 PM").

  TR35 makes the "at time" wrapper the default for a single date
  combined with a single time — an *event* time — and asks that an
  API also offer the standard wrapper for a *current* time, which
  is what `:default` is. CLDR defines the "at time" wrapper only
  for `:full` and `:long`, so both styles agree at `:medium` and
  `:short`.

* `:locale` is a locale identifier. The default is `:en`.

* `:number_system` is a CLDR numbering system name (for example, `:thai`). All numeric fields render in that system; a `-u-nu-` locale extension may be used instead. The default is the locale's number system.

* `:numeric_date_separator` is a string replacing the locale's `numericDateSeparator` wherever it separates the fields of a numeric-month date, as TR35 allows an implementation to offer. `Localize.DateTime.numeric_separators/2` returns the locale's own. The default is the locale's separator.

* `:numeric_time_separator` is a string replacing the locale's `numericTimeSeparator` wherever it separates time fields. The default is the locale's separator.

* `:prefer` selects between CLDR `alt` variants. Accepts an
  atom or a list of atoms in priority order. Recognised values:
  `:standard` / `:variant` (locales like en-CA publish both an
  ISO pattern `"y-MM-dd"` and a locale-variant `"d/M/yy"`),
  and `:unicode` / `:ascii` (mostly time formats — NBSP and
  curly quotes vs ASCII-only). Examples: `prefer: :variant`,
  `prefer: [:variant, :ascii]`. The default is
  `[:standard, :unicode]`.

### Returns

* `{:ok, formatted_string}` on success.

* `{:error, exception}` if the datetime cannot be formatted.

### Examples

    iex> Localize.DateTime.to_string(~N[2017-07-10 14:30:00], locale: :en, prefer: :ascii)
    {:ok, "Jul 10, 2017, 2:30:00 PM"}

    iex> Localize.DateTime.to_string(~N[2017-07-10 14:30:00], format: :short, locale: :en, prefer: :ascii)
    {:ok, "7/10/17, 2:30 PM"}

    iex> Localize.DateTime.to_string(%{year: 2026, month: 6, day: 15, hour: 14}, locale: :en, prefer: :ascii)
    {:ok, "Jun 15, 2026, 2 PM"}

# `to_string!`

```elixir
@spec to_string!(map(), Keyword.t()) :: String.t()
```

Same as `to_string/2` but raises on error.

### Arguments

* `datetime` is a `t:DateTime.t/0`, `t:NaiveDateTime.t/0`,
  or any map with date and time keys.

* `options` is a keyword list of options.

### Options

See `to_string/2` for the supported options.

### Returns

* A formatted string.

* Raises an exception if the datetime cannot be formatted.

### Examples

    iex> Localize.DateTime.to_string!(~N[2017-07-10 14:30:00], locale: :en, prefer: :ascii)
    "Jul 10, 2017, 2:30:00 PM"

    iex> Localize.DateTime.to_string!(~N[2017-07-10 14:30:00], format: :short, locale: :en, prefer: :ascii)
    "7/10/17, 2:30 PM"

---

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