defmodule Cldr.Unit.Range do
@moduledoc """
Implements a unit range type, similar to `Date.Range`.
A unit range but be comprise `first` and `last` units that
have the same base unit. The `last` unit will be converted
to the unit type of the `first` unit.
Unit ranges are useful in at least the following cases:
1. To be able to enumerate a unit range.
2. To be able to format a unit range in a localised manner using
`Cldr.Number.to_range_string/2`.
"""
defstruct [:first, :last]
@typedoc """
A unit range consists of two units where the
last unit can be converted to the same unit type
as the first unit.
"""
@type t :: %{first: Cldr.Unit.t(), last: Cldr.Unit.t()}
@doc """
Returns a new Cldr.Unit.Range.
### Arguments
* `first` is any `t:Cldr.Unit.t/0` returned by `Cldr.Unit.new/2`.
* `last` is any `t:Cldr.Unit.t/0` returned by `Cldr.Unit.new/2`
that is convertible to the same unit as `first` and where its
converted value is greater than or equal to the value of `first`.
### Returns
* `{:ok, unit_range}` or
* `{:error, {exception, reason}}`.
### Examples
iex> Cldr.Unit.Range.new Cldr.Unit.new!(:gram, 1), Cldr.Unit.new!(:gram, 4)
{:ok, Cldr.Unit.Range.new!(Cldr.Unit.new!(:gram, 1), Cldr.Unit.new!(:gram, 4))}
iex> Cldr.Unit.Range.new Cldr.Unit.new!(:gram, 1), Cldr.Unit.new!(:liter, 4)
{:error,
{Cldr.Unit.InvalidRangeError,
"Unit ranges require that the last unit can be converted to the first unit. " <>
"Found Cldr.Unit.new!(:gram, 1) and Cldr.Unit.new!(:liter, 4)"}}
iex> Cldr.Unit.Range.new Cldr.Unit.new!(:gram, 5), Cldr.Unit.new!(:gram, 4)
{:error,
{Cldr.Unit.InvalidRangeError,
"Unit ranges require that the first unit be less than or equal to the last. " <>
"Found Cldr.Unit.new!(:gram, 5) and Cldr.Unit.new!(:gram, 4)"}}
"""
@doc since: "3.16.0"
@spec new(Cldr.Unit.t(), Cldr.Unit.t()) ::
{:ok, t()} | {:error, {module(), String.t()}}
def new(%Cldr.Unit{} = first, %Cldr.Unit{} = last) do
case Cldr.Unit.convert(last, first.unit) do
{:ok, last} ->
if first.value <= last.value do
{:ok, %__MODULE__{first: first, last: last}}
else
{:error,
{Cldr.Unit.InvalidRangeError,
"Unit ranges require that the first unit be less than or equal to the last. Found #{inspect(first)} and #{inspect(last)}"}}
end
{:error, _} ->
{:error,
{Cldr.Unit.InvalidRangeError,
"Unit ranges require that the last unit can be converted to the first unit. Found #{inspect(first)} and #{inspect(last)}"}}
end
end
@doc """
Returns a new Cldr.Unit.Range or raises an exception.
### Arguments
* `first` is any `t:Cldr.Unit.t/0` returned by `Cldr.Unit.new/2`.
* `last` is any `t:Cldr.Unit.t/0` returned by `Cldr.Unit.new/2`
that is convertible to the same unit as `first` and where its
converted value is greater than or equal to the value of `first`.
### Returns
* `unit_range` or
* raises an exception.
### Example
iex> Cldr.Unit.Range.new! Cldr.Unit.new!(:gram, 1), Cldr.Unit.new!(:gram, 4)
Cldr.Unit.Range.new!(Cldr.Unit.new!(:gram, 1), Cldr.Unit.new!(:gram, 4))
"""
@doc since: "3.16.0"
@dialyzer {:nowarn_function, {:new!, 2}}
@spec new!(Cldr.Unit.t(), Cldr.Unit.t()) :: t() | no_return()
def new!(%Cldr.Unit{} = first, %Cldr.Unit{} = last) do
case new(first, last) do
{:ok, range} -> range
{:error, {exception, reason}} -> raise exception, reason
end
end
defdelegate to_iolist(range), to: Cldr.Unit.Format
defdelegate to_iolist(range, options), to: Cldr.Unit.Format
defdelegate to_iolist(range, backend, options), to: Cldr.Unit.Format
defdelegate to_string(range), to: Cldr.Unit.Format
defdelegate to_string(range, options), to: Cldr.Unit.Format
defdelegate to_string(range, backend, options), to: Cldr.Unit.Format
end