Localize.Interval (Localize v1.1.1)

Copy Markdown View Source

Formats date and time intervals as localized strings.

Interval formats produce strings like "Jan 10 – 12, 2008" from two dates, rather than repeating "Jan 10, 2008 – Jan 12, 2008". The format is selected based on the greatest calendar field difference between the start and end values.

Summary

Functions

Returns the greatest calendar field difference between two dates or datetimes.

Returns the locale-independent skeletons for the :fields option of to_string/3.

Splits an interval format string into [left, right] halves at the point where a format character repeats.

Formats a date, time, or datetime interval into typed parts, mirroring ECMA-402's formatRangeToParts.

Same as to_parts/3 but raises on error.

Formats a date interval as a localized string.

Same as to_string/3 but raises on error.

Functions

greatest_difference(from, to)

@spec greatest_difference(map(), map()) ::
  {:ok, :y | :M | :d | :H | :m} | {:error, Exception.t()}

Returns the greatest calendar field difference between two dates or datetimes.

Arguments

  • from is a date or datetime map.

  • to is a date or datetime map.

Returns

  • {:ok, field} where field is :y, :M, :d, :H, or :m.

  • {:error, %Localize.NoPracticalDifferenceError{}} if the values are equal at every field considered.

Examples

iex> Localize.Interval.greatest_difference(~D[2022-04-22], ~D[2022-04-27])
{:ok, :d}

iex> Localize.Interval.greatest_difference(~D[2021-12-31], ~D[2022-01-01])
{:ok, :y}

known_fields()

@spec known_fields() :: %{
  month: %{short: :M, medium: :MMM, long: :MMM, full: :MMM},
  month_and_day: %{short: :Md, medium: :MMMd, long: :MMMEd, full: :MMMEd},
  year_and_month: %{short: :yM, medium: :yMMM, long: :yMMMM, full: :yMMMM}
}

Returns the locale-independent skeletons for the :fields option of to_string/3.

Only the non-default :fields selections (:month, :month_and_day, :year_and_month) appear here, because only those are locale-independent. The default :date selection is resolved per-locale, mirroring Localize.Date.to_string/2's :format → skeleton mapping for that locale, so it has no fixed entry to list.

Returns

  • A map keyed by field selection (:month, :month_and_day, :year_and_month), each value a map of :format (:short, :medium, :long, :full) to the CLDR skeleton atom used for that combination.

Examples

iex> Localize.Interval.known_fields()
%{
  month: %{short: :M, full: :MMM, long: :MMM, medium: :MMM},
  month_and_day: %{short: :Md, full: :MMMEd, long: :MMMEd, medium: :MMMd},
  year_and_month: %{short: :yM, full: :yMMMM, long: :yMMMM, medium: :yMMM}
}

split_interval(interval)

@spec split_interval(String.t()) :: {:ok, [String.t()]} | {:error, Exception.t()}

Splits an interval format string into [left, right] halves at the point where a format character repeats.

Arguments

  • interval is an interval format pattern string.

Returns

  • {:ok, [left, right]} where left and right are the two halves of the interval pattern.

  • {:error, exception} if the pattern is malformed or has no repeating field.

Examples

iex> Localize.Interval.split_interval("MMM d – d")
{:ok, ["MMM d – ", "d"]}

to_parts(from, to, options \\ [])

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

Formats a date, time, or datetime interval into typed parts, mirroring ECMA-402's formatRangeToParts.

The parts concatenate to exactly the string to_string/3 produces with the same options. Every part carries a :source key: :start_range for parts of the interval start, :end_range for parts of the interval end, and :shared for the separators between them. When the endpoints have no practical difference the single formatted value carries source :shared throughout.

Unlike to_string/3, open intervals (a nil endpoint) are not supported — both endpoints are required, matching the JS API.

Arguments

Returns

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

  • {:error, exception} on failure or when an endpoint is nil.

Examples

iex> Localize.Interval.to_parts(~D[2022-04-22], ~D[2022-04-25], locale: :en)
{:ok,
 [
   %{type: :month, value: "Apr", source: :start_range},
   %{type: :literal, value: " ", source: :start_range},
   %{type: :day, value: "22", source: :start_range},
   %{type: :literal, value: " – ", source: :shared},
   %{type: :day, value: "25", source: :end_range},
   %{type: :literal, value: ", ", source: :end_range},
   %{type: :year, value: "2022", source: :end_range}
 ]}

to_parts!(from, to, options \\ [])

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

Same as to_parts/3 but raises on error.

Arguments

  • from is the interval start.

  • to is the interval end.

  • options is a keyword list of options. See to_parts/3.

Returns

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

Raises

  • Raises an exception if the interval cannot be decomposed into parts.

Examples

iex> Localize.Interval.to_parts!(~D[2022-04-22], ~D[2022-04-25], locale: :en) |> length()
7

to_string(from, to, options \\ [])

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

Formats a date interval as a localized string.

Arguments

Options

  • :locale is a locale identifier. The default is :en.

  • :fields selects which date fields appear: :date (the whole date, the default), :month, :month_and_day, or :year_and_month. See known_fields/0.

  • :format selects how wide those fields are rendered: :short, :medium, :long, or :full. The default is :medium.

The two are independent axes: :fields chooses which fields appear, :format chooses how wide they are rendered. So fields: :year_and_month renders the two months against a single year either way — numerically for format: :short ("1/2022" … "3/2022") and spelled out for format: :long ("January" … "March 2022").

Returns

  • {:ok, formatted_string} on success.

  • {:error, exception} on failure.

Examples

iex> {:ok, result} = Localize.Interval.to_string(~D[2022-04-22], ~D[2022-04-25], locale: :en)
iex> String.contains?(result, "Apr")
true

iex> {:ok, result} = Localize.Interval.to_string(~D[2022-01-15], ~D[2022-03-20], locale: :en)
iex> String.contains?(result, "Jan") and String.contains?(result, "Mar")
true

to_string!(from, to, options \\ [])

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

Same as to_string/3 but raises on error.

Arguments

Options

See to_string/3 for the supported options.

Returns

  • The formatted interval as a string.

  • Raises an exception if the interval cannot be formatted.

Examples

iex> Localize.Interval.to_string!(~D[2022-04-22], ~D[2022-04-25], locale: :en)
"Apr 22 – 25, 2022"

iex> Localize.Interval.to_string!(~D[2022-01-15], ~D[2022-03-20], locale: :en)
"Jan 15 – Mar 20, 2022"