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

TR35 semantic skeletons: asking for a date or time by *meaning* rather
than by which fields to render.

A classical skeleton such as `:yMMMd` is an instruction — year, abbreviated
month, day. A semantic skeleton is a request: "year, month, day and
weekday, at medium length", written `YMDE`. The library then chooses the
fields, and a locale is free to choose differently.

CLDR ships no semantic skeleton data. TR35 §Mapping to Standard Skeletons
resolves one from data Localize already has: the year, month and day take
their widths from the locale's own date format at the requested length,
and the weekday, time and zone come from a fixed table. So a medium date is
`yMMMd` in `en` but `yMMdd` in `de`, whose medium date is numeric. The
datetime conformance data, the one file in CLDR that mentions semantic
skeletons, pairs each of its cases with the classical skeleton it resolves
to, and every case resolves as it says.

> #### Semantic skeletons are a technical preview {: .warning}
>
> TR35 designates semantic skeletons, and the MessageFormat 2 date and
> time functions built on them, a technical preview. The fields a
> request resolves to may change with a later CLDR release.

## Field codes

The skeleton code names the fields wanted:

* `Y` — year

* `M` — month

* `D` — day of the month

* `E` — day of the week

* `T` — time

* `Z` — time zone

So `YMD` is a plain date, `YMDE` adds the weekday, `T` is a time on its
own, and `MDTZ` is a month, day, time and zone together.

The codes name a set, so their order does not matter, and TR35 defines
which sets there are: a date — `D`, `E`, `DE`, `MD`, `MDE`, `YMD` or
`YMDE`; a calendar period — `Y`, `M` or `YM`; a time, `T`; and a zone,
`Z`. A date may be joined to a time, a zone or both (`YMDT`, `MDZ`,
`ETZ`) and a time to a zone (`TZ`), but a calendar period stands alone.
Any other set, such as `YD` or `YMT`, is an error, as TR35 requires.

## Usage

    import Localize.DateTime.SemanticSkeleton, only: [semantic: 1, semantic: 2]

    Localize.DateTime.to_string(datetime, format: semantic("YMDE"))

    Localize.DateTime.to_string(datetime,
      format: semantic("MDTZ", length: :long, zone_style: :generic))

The struct is accepted anywhere `:format` is, alongside the standard
styles, classical skeletons and literal patterns it already took. Building
it once and reusing it across calls is cheaper than re-validating a
keyword list each time.

# `field`

```elixir
@type field() :: :year | :month | :day | :weekday | :time | :zone
```

# `t`

```elixir
@type t() :: %Localize.DateTime.SemanticSkeleton{
  alignment: :inline | :column,
  fields: [field()],
  hour_cycle: :auto | :clock12 | :clock24 | :h11 | :h12 | :h23 | :h24,
  length: :short | :medium | :long,
  time_precision: time_precision(),
  year_style: :auto | :full | :with_era,
  zone_length: :auto | :short | :long,
  zone_style: :specific | :generic | :location | :offset
}
```

# `time_precision`

```elixir
@type time_precision() ::
  :hour | :minute | :minute_optional | :second | {:fractional_second, 1..9}
```

# `new`

```elixir
@spec new(String.t() | [field()], Keyword.t()) :: {:ok, t()} | {:error, Exception.t()}
```

Builds a semantic skeleton.

### Arguments

* `code` is a string of field codes such as `"YMDE"` or `"MDTZ"`, or a
  list of field atoms such as `[:year, :month, :day]`.

* `options` is a keyword list.

### Options

* `:length` is `:short`, `:medium` (the default) or `:long`. It says how
  much space the result has, and the locale's own date format at that
  length sets the widths of the year, month and day — `:short` is usually
  numeric, `:long` spells the month out.

* `:year_style` is `:auto` (the default), `:full` or `:with_era`. `:auto`
  takes the year as the locale's date format writes it, era and all;
  `:full` never truncates it to two digits; `:with_era` also shows the era.

* `:zone_style` is `:specific` (the default), `:generic`, `:location` or
  `:offset`.

* `:zone_length` is `:auto` (the default), `:short` or `:long`. `:auto` is
  TR35's rule: a zone on its own takes the long form except at short
  length, and a zone beside other fields the short one. `:short` and
  `:long` choose the form outright — `z` or `zzzz` for a specific zone, `v`
  or `vvvv` for a generic one, `O` or `OOOO` for an offset — which TR35
  does not provide for but MessageFormat 2's `timeZoneStyle` needs. A
  location zone has one form.

* `:hour_cycle` is `:auto` (the default), `:clock12`, `:clock24`, `:h11`,
  `:h12`, `:h23` or `:h24`, in TR35's vocabulary. `:auto` uses the locale's
  own cycle, which is what a semantic request usually means. `:clock12` and
  `:clock24` ask for a 12- or 24-hour clock and then take the locale's
  pattern as it stands, so `ja` renders `K` rather than `h`. The four exact
  cycles substitute their hour symbol into the matched pattern — `:h11` `K`,
  `:h12` `h`, `:h23` `H`, `:h24` `k` — which is the only way to be sure
  which symbol you get.

* `:time_precision` is `:hour`, `:minute`, `:minute_optional`, `:second`
  (the default) or `{:fractional_second, digits}` with `digits` from 1 to
  9. `:minute_optional` drops the minutes when they are zero.

* `:alignment` is `:inline` (the default) or `:column`, TR35's two
  alignments. `:column` is for dates and times set in a column, where
  equal widths line up: it pads a numeric month, day and hour to two
  digits, so `en`'s short date "1/5/25" becomes "01/05/25". It never
  turns a number into text or back, and it acts on the pattern the
  skeleton resolves to, leaving the classical skeleton as it is.

Each option shapes certain fields, and TR35 makes giving one for a field
set that has none of them an error: `:year_style` needs `Y`, `:hour_cycle`
and `:time_precision` need `T`, `:zone_style` and `:zone_length` need `Z`,
and `:alignment` needs `Y`, `M`, `D` or `T`. `:length` suits every set.

### Returns

* `{:ok, skeleton}`.

* `{:error, exception}` if a field code or option value is unknown, the
  fields are not a set TR35 defines, or an option is given for a set
  without the fields it applies to.

### Examples

    iex> {:ok, skeleton} = Localize.DateTime.SemanticSkeleton.new("YMDE")
    iex> skeleton.fields
    [:year, :month, :day, :weekday]

    iex> Localize.DateTime.SemanticSkeleton.new("YMDQ")
    {:error, %Localize.InvalidValueError{value: "Q", expected: "one of Y, M, D, E, T, Z", context: "Localize.DateTime.SemanticSkeleton"}}

# `semantic`

```elixir
@spec semantic(String.t() | [field()], Keyword.t()) :: t()
```

Builds a semantic skeleton, raising on invalid input.

Intended for the `:format` option, where the skeleton is usually a literal
and a mistake in it is a programming error rather than bad data.

### Arguments

* See `new/2`.

### Returns

* A `t:t/0`.

### Raises

* `Localize.InvalidValueError` wherever `new/2` returns an error.

### Examples

    iex> skeleton = Localize.DateTime.SemanticSkeleton.semantic("YMD", length: :long)
    iex> {skeleton.fields, skeleton.length}
    {[:year, :month, :day], :long}

# `to_classical_skeleton`

```elixir
@spec to_classical_skeleton(t(), Keyword.t()) ::
  {:ok, atom()} | {:error, Exception.t()}
```

Returns the classical skeleton a semantic skeleton resolves to.

This is TR35 §Mapping to Standard Skeletons: the year, month and day take
their widths from the locale's date format at the skeleton's length, the
weekday, time and zone come from a fixed table, and the time precision and
year style then adjust the result. The result is an ordinary skeleton
atom, resolved from there by the machinery that already handles
`format: :yMMMd`.

### Arguments

* `skeleton` is a `t:t/0`.

* `options` is a keyword list.

### Options

* `:locale` is the locale whose date formats set the widths of the year,
  month and day. The default is `Localize.get_locale/0`.

* `:calendar` is a calendar module, `Calendar.ISO` by default. Its CLDR
  calendar type selects the date formats, so a year in a calendar that
  counts years by era carries one.

* `:value` is the date or time being formatted. Only `:minute_optional`
  precision consults it, dropping the minutes when they are zero; without
  a value the minutes are shown.

### Returns

* `{:ok, atom}` — the classical skeleton.

* `{:error, exception}` if the skeleton is not one `new/2` builds, as a
  struct made by hand may not be, or the locale or calendar is not known.

### Examples

    iex> skeleton = Localize.DateTime.SemanticSkeleton.semantic("YMDE", length: :short)
    iex> Localize.DateTime.SemanticSkeleton.to_classical_skeleton(skeleton, locale: :en)
    {:ok, :yyMdEEE}

    iex> skeleton = Localize.DateTime.SemanticSkeleton.semantic("YMD")
    iex> Localize.DateTime.SemanticSkeleton.to_classical_skeleton(skeleton, locale: :de)
    {:ok, :yMMdd}

    iex> skeleton = Localize.DateTime.SemanticSkeleton.semantic("T", time_precision: :minute)
    iex> Localize.DateTime.SemanticSkeleton.to_classical_skeleton(skeleton, locale: :en)
    {:ok, :jm}

---

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