Packages
ex_cldr_units
3.2.0
3.20.5
3.20.4
3.20.3
3.20.2
3.20.1
3.20.0
3.19.2
3.19.1
3.19.0
3.18.1
3.18.0
3.17.2
3.17.1
3.17.0
3.16.5
3.16.4
3.16.3
3.16.2
3.16.1
3.16.0
3.15.0
3.14.0
3.13.3
3.13.2
3.13.1
3.13.0
3.12.2
3.12.1
3.12.0
3.11.0
3.10.0
3.9.2
3.9.1
3.9.0
3.8.0
3.8.0-rc.2
3.8.0-rc.1
3.8.0-rc.0
3.7.1
3.7.0
3.6.0
3.5.3
3.5.2
3.5.1
3.5.0
3.5.0-rc.1
3.5.0-rc.0
3.4.0
3.4.0-rc.0
3.3.1
3.3.0
3.3.0-rc.0
3.2.1
3.2.0
3.1.2
3.1.1
3.1.0
3.0.1
3.0.0
3.0.0-rc.0
2.8.1
2.8.0
2.7.0
2.6.1
2.6.0
2.5.3
2.5.2
2.5.1
2.5.0
2.4.0
2.3.3
2.3.2
retired
2.3.1
2.3.0
2.2.0
2.1.0
2.0.0
1.3.0
1.2.2
1.2.1
1.2.0
1.1.1
1.1.0
retired
1.0.1
retired
1.0.0
retired
1.0.0-rc.0
retired
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.1
0.3.0
0.2.1
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
Unit formatting (volume, area, length, ...), conversion and arithmetic functions based upon the Common Locale Data Repository (CLDR).
Current section
Files
Jump to
Current section
Files
lib/cldr/unit/conversion.ex
defmodule Cldr.Unit.Conversion do
@moduledoc """
Unit conversion functions for the units defined
in `Cldr`.
"""
@enforce_keys [:factor, :offset, :base_unit]
defstruct factor: 1,
offset: 0,
base_unit: nil
@type t :: %{
factor: integer | float | Ratio.t(),
base_unit: [atom(), ...],
offset: integer | float
}
alias Cldr.Unit
import Unit, only: [incompatible_units_error: 2]
defmodule Options do
defstruct usage: nil, locale: nil, backend: nil, territory: nil
end
@doc """
Convert one unit into another unit of the same
unit type (length, volume, mass, ...)
## Arguments
* `unit` is any unit returned by `Cldr.Unit.new/2`
* `to_unit` is any unit name returned by `Cldr.Unit.units/0`
## Returns
* a `Unit.t` of the unit type `to_unit` or
* `{:error, {exception, message}}`
## Examples
iex> Cldr.Unit.convert Cldr.Unit.new!(:mile, 1), :foot
{:ok, Cldr.Unit.new!(:foot, 5280)}
iex> Cldr.Unit.convert Cldr.Unit.new!(:mile, 1), :gallon
{:error, {Cldr.Unit.IncompatibleUnitsError,
"Operations can only be performed between units of the same category. Received :mile and :gallon"}}
"""
@spec convert(Unit.t(), Unit.unit()) :: {:ok, Unit.t()} | {:error, {module(), String.t()}}
def convert(%Unit{} = unit, to_unit) do
%{unit: from_unit, value: value, base_conversion: from_conversion} = unit
with {:ok, to_unit, to_conversion} <- Unit.validate_unit(to_unit),
{:ok, converted} <- convert(value, from_conversion, to_conversion) do
Unit.new(to_unit, converted, usage: unit.usage, format_options: unit.format_options)
else
{:error, {Cldr.Unit.IncompatibleUnitsError, _}} ->
{:error, incompatible_units_error(from_unit, to_unit)}
end
end
defp convert(value, from, to) when is_number(value) or is_map(value) do
use Ratio
with {:ok, from, to} <- compatible(from, to) do
value
|> Ratio.new()
|> convert_to_base(from)
|> convert_from_base(to)
|> wrap_ok
end
end
def convert_to_base(value, %__MODULE__{} = from) do
use Ratio
%{factor: from_factor, offset: from_offset} = from
value * from_factor + from_offset
end
def convert_to_base(value, [{_, [{_, from}]}]) do
convert_to_base(value, from)
end
# A known translation with a "per" conversion
def convert_to_base(value, [{_, {_, _} = from}]) do
convert_to_base(value, from)
end
def convert_to_base(value, {_, %__MODULE__{} = from}) do
convert_to_base(value, from)
end
def convert_to_base(value, {numerator, denominator}) do
use Ratio
convert_to_base(1.0, numerator) / convert_to_base(1.0, denominator) * value
end
def convert_to_base(value, []) do
value
end
def convert_to_base(value, [numerator | rest]) do
convert_to_base(value, numerator) |> convert_to_base(rest)
end
def convert_to_base(_value, conversion) do
raise ArgumentError, "Conversion not recognised: #{inspect conversion}"
end
def convert_from_base(value, %__MODULE__{} = to) do
use Ratio
%{factor: to_factor, offset: to_offset} = to
(value - to_offset) / to_factor
end
def convert_from_base(value, [{_, [{_, to}]}]) do
convert_from_base(value, to)
end
# A known translation with a "per" conversion
def convert_from_base(value, [{_, {_, _} = to}]) do
convert_from_base(value, to)
end
def convert_from_base(value, {_, %__MODULE__{} = to}) do
convert_from_base(value, to)
end
def convert_from_base(value, {numerator, denominator}) do
use Ratio
convert_from_base(1.0, numerator) / convert_from_base(1.0, denominator) * value
end
def convert_from_base(value, []) do
value
end
def convert_from_base(value, [numerator | rest]) do
convert_from_base(value, numerator) |> convert_from_base(rest)
end
defp compatible(from, to) do
with {:ok, base_unit_from} <- Unit.base_unit(from),
{:ok, base_unit_to} <- Unit.base_unit(to),
true <- to_string(base_unit_from) == to_string(base_unit_to) do
{:ok, from, to}
else
_ -> {:error, incompatible_units_error(from, to)}
end
end
defp wrap_ok(unit) do
{:ok, unit}
end
@doc """
Convert one unit into another unit of the same
unit type (length, volume, mass, ...) and raises
on a unit type mismatch
## Arguments
* `unit` is any unit returned by `Cldr.Unit.new/2`
* `to_unit` is any unit name returned by `Cldr.Unit.units/0`
## Returns
* a `Unit.t` of the unit type `to_unit` or
* raises an exception
## Examples
iex> Cldr.Unit.Conversion.convert!(Cldr.Unit.new!(:celsius, 0), :fahrenheit)
...> |> Cldr.Unit.round
#Cldr.Unit<:fahrenheit, 32.0>
iex> Cldr.Unit.Conversion.convert!(Cldr.Unit.new!(:fahrenheit, 32), :celsius)
...> |> Cldr.Unit.round
#Cldr.Unit<:celsius, 0.0>
Cldr.Unit.Conversion.convert Cldr.Unit.new!(:mile, 1), :gallon
** (Cldr.Unit.IncompatibleUnitsError) Operations can only be performed between units of the same type. Received :mile and :gallon
"""
@spec convert!(Unit.t(), Unit.unit()) :: Unit.t() | no_return()
def convert!(%Unit{} = unit, to_unit) do
case convert(unit, to_unit) do
{:error, {exception, reason}} -> raise exception, reason
{:ok, unit} -> unit
end
end
@doc """
Convert a unit into its base unit.
For example, the base unit for `length`
is `meter`. The base unit is an
intermediary unit used in all
conversions.
## Arguments
* `unit` is any unit returned by `Cldr.Unit.new/2`
## Returns
* `unit` converted to its base unit as a `t:Unit.t()` or
* `{;error, {exception, reason}}` as an error
## Example
iex> unit = Cldr.Unit.new!(:kilometer, 10)
iex> Cldr.Unit.Conversion.convert_to_base_unit unit
{:ok, Cldr.Unit.new!(:meter, 10000)}
"""
def convert_to_base_unit(%Unit{} = unit) do
with {:ok, base_unit} <- Unit.base_unit(unit) do
convert(unit, base_unit)
end
end
def convert_to_base_unit(unit) when is_atom(unit) do
unit
|> Unit.new!(1.0)
|> convert_to_base_unit()
end
def convert_to_base_unit([unit | _rest]) when is_atom(unit) do
convert_to_base_unit(unit)
end
@doc """
Convert a unit into its base unit and
raises on error
For example, the base unit for `length`
is `meter`. The base unit is an
intermediary unit used in all
conversions.
## Arguments
* `unit` is any unit returned by `Cldr.Unit.new/2`
## Returns
* `unit` converted to its base unit as a `t:Unit.t()` or
* raises an exception
## Example
iex> unit = Cldr.Unit.new!(:kilometer, 10)
iex> Cldr.Unit.Conversion.convert_to_base_unit! unit
#Cldr.Unit<:meter, 10000>
"""
def convert_to_base_unit!(%Unit{} = unit) do
case convert_to_base_unit(unit) do
{:error, {exception, reason}} -> raise exception, reason
{:ok, unit} -> unit
end
end
end