# `Localize.Interval`
[🔗](https://github.com/elixir-localize/localize/blob/v1.2.0/lib/localize/interval.ex#L1)

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.

# `greatest_difference`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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

* `from` is a `Date`, `Time`, `DateTime`, `NaiveDateTime`, or compatible map for the interval start.

* `to` is a value of the same kind for the interval end.

* `options` is a keyword list of options. See `to_string/3`.

### 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!`

```elixir
@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`

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

Formats a date interval as a localized string.

### Arguments

* `from` is a `t:Date.t/0`.

* `to` is a `t:Date.t/0`.

* `options` is a keyword list of options.

### 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!`

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

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

### Arguments

* `from` is a `t:Date.t/0`.

* `to` is a `t:Date.t/0`.

* `options` is a keyword list of options.

### 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"

---

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