Localize.DateTime.SemanticSkeleton (Localize v1.4.0)

Copy Markdown View Source

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

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.

Summary

Functions

Builds a semantic skeleton.

Builds a semantic skeleton, raising on invalid input.

Returns the classical skeleton a semantic skeleton resolves to.

Types

field()

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

t()

@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()

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

Functions

new(code, options \\ [])

@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

    1. :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(code, options \\ [])

@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

Returns

Raises

Examples

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

to_classical_skeleton(skeleton, options \\ [])

@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/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}