Localize.Unit (Localize v0.5.0)

Copy Markdown View Source

Represents and formats CLDR units of measure.

A Localize.Unit struct holds the original unit name string, its parsed AST representation, and an optional numeric value. Units can be created with new/3 and formatted with to_string/2.

Unit names

Unit names follow the CLDR identifier syntax defined in TR35. Examples: "meter", "kilogram", "meter-per-second", "square-kilometer", "liter-per-100-kilometer".

Formatting

to_string/2 produces locale-aware output with plural-sensitive patterns (e.g., "1 kilometer" vs "3 kilometers") and supports :long, :short, and :narrow format styles.

Usage preferences

CLDR defines measurement usage preferences by territory and category (e.g., road distances in the US use miles). The :usage field on the struct and the :usage option on to_string/2 support automatic unit selection based on locale.

Summary

Functions

Adds two convertible units.

Compares two convertible units.

Returns whether two units are convertible (same category).

Converts a unit to a different target unit.

Converts a unit to a different target unit, raising on error.

Converts a unit to the preferred unit for a given measurement system.

Decomposes a unit into a list of units with the given target units.

Registers a custom unit definition at runtime.

Returns the localized display name for a unit.

Divides a unit by a scalar or another unit.

Returns a list of all known unit categories.

Returns a map of unit categories to their member units.

Returns a list of valid usage types for unit preferences.

Loads custom unit definitions from an Elixir term file (.exs).

Returns the preferred measurement system for a territory.

Multiplies a unit by a scalar or another unit.

Negates a unit's value.

Creates a new unit from a CLDR unit identifier string without a value.

Creates a new unit with a value and a CLDR unit identifier string.

Creates a new unit from a CLDR unit identifier string, raising on error.

Creates a new unit with a value and a CLDR unit identifier string, raising on error.

Subtracts unit_2 from unit_1.

Formats a unit as an iolist.

Formats a unit as a localized string.

Same as to_string/2 but raises on error.

Returns the CLDR category for a unit.

Returns the numeric value of a unit.

Returns a zero-valued unit of the same type.

Returns true if the unit has a zero value.

Types

t()

@type t() :: %Localize.Unit{
  name: String.t(),
  parsed: tuple(),
  usage: String.t() | nil,
  value: value()
}

value()

@type value() :: number() | Decimal.t() | [number()] | nil

Functions

add(unit_1, unit_2)

@spec add(t(), t()) :: {:ok, t()} | {:error, Exception.t() | String.t()}

Adds two convertible units.

The value of unit_2 is converted to the unit type of unit_1 before addition. The result retains the unit type of unit_1.

Arguments

  • unit_1 is a %Localize.Unit{} struct with a value.

  • unit_2 is a %Localize.Unit{} struct with a value.

Returns

  • {:ok, unit} or {:error, exception}.

Examples

iex> {:ok, a} = Localize.Unit.new(1, "kilometer")
iex> {:ok, b} = Localize.Unit.new(500, "meter")
iex> {:ok, result} = Localize.Unit.add(a, b)
iex> result.value
1.5

compare(unit_1, unit_2)

@spec compare(t(), t()) :: :lt | :eq | :gt | {:error, Exception.t()}

Compares two convertible units.

The value of unit_2 is converted to the unit type of unit_1 before comparison.

Arguments

  • unit_1 is a %Localize.Unit{} struct with a value.

  • unit_2 is a %Localize.Unit{} struct with a value.

Returns

  • :lt, :eq, or :gt.

  • {:error, exception} if the units are not convertible.

Examples

iex> {:ok, a} = Localize.Unit.new(1, "kilometer")
iex> {:ok, b} = Localize.Unit.new(500, "meter")
iex> Localize.Unit.compare(a, b)
:gt

compatible?(unit_1, unit_2)

@spec compatible?(t() | String.t(), t() | String.t()) :: boolean()

Returns whether two units are convertible (same category).

Arguments

  • unit_1 is a %Localize.Unit{} struct or a unit name string.

  • unit_2 is a %Localize.Unit{} struct or a unit name string.

Returns

  • true or false.

Examples

iex> Localize.Unit.compatible?("meter", "foot")
true

iex> Localize.Unit.compatible?("meter", "kilogram")
false

convert(source, target)

@spec convert(t(), String.t()) :: {:ok, t()} | {:error, Exception.t()}

Converts a unit to a different target unit.

The source and target units must be convertible (same dimensional base unit). Returns a new unit struct with the converted value and the target unit type.

Arguments

  • unit is a %Localize.Unit{} struct with a value.

  • target is the target unit identifier string (e.g., "foot").

Returns

  • {:ok, unit} where unit is a new %Localize.Unit{} with the converted value and target unit type, or

  • {:error, reason} if the unit has no value, the target cannot be parsed, or the units are not convertible.

Examples

iex> {:ok, meters} = Localize.Unit.new(1, "kilometer")
iex> {:ok, result} = Localize.Unit.convert(meters, "meter")
iex> result.value
1000.0
iex> result.name
"meter"

convert!(unit, target)

@spec convert!(t(), String.t()) :: t() | no_return()

Converts a unit to a different target unit, raising on error.

Same as convert/2 but returns the unit struct directly or raises ArgumentError.

Arguments

  • unit is a %Localize.Unit{} struct with a value.

  • target is the target unit identifier string.

Returns

  • A %Localize.Unit{} struct with the converted value.

Examples

iex> unit = Localize.Unit.new!(1000, "meter")
iex> result = Localize.Unit.convert!(unit, "kilometer")
iex> result.value
1.0

convert_measurement_system(unit, system)

@spec convert_measurement_system(t(), :metric | :us | :uk) ::
  {:ok, t()} | {:error, String.t()}

Converts a unit to the preferred unit for a given measurement system.

Looks up the CLDR unit preference data for the unit's quantity category and the specified measurement system, then converts to the first preferred unit for the "default" usage.

Arguments

  • unit is a %Localize.Unit{} struct with a value.

  • system is the target measurement system: :metric, :us, or :uk.

Returns

  • {:ok, unit} where unit is a new %Localize.Unit{} with the converted value and the preferred unit for that system, or

  • {:error, reason} if the unit has no value, the measurement system is invalid, or no preference is found.

Examples

iex> {:ok, meters} = Localize.Unit.new(100, "meter")
iex> {:ok, result} = Localize.Unit.convert_measurement_system(meters, :us)
iex> result.name
"mile"

decompose(unit, arg2)

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

Decomposes a unit into a list of units with the given target units.

This is used for mixed-unit formatting, for example converting 1.75 meters into [1 meter, 75 centimeters] or 5.25 feet into [5 feet, 3 inches].

Each target unit receives the integer part of the remaining value, except the last unit which receives the remainder.

Arguments

  • unit is a %Localize.Unit{} struct with a value.

  • target_units is a list of unit name strings, ordered from largest to smallest (e.g., ["foot", "inch"]).

Returns

  • {:ok, units} where units is a list of %Localize.Unit{} structs.

  • {:error, exception} if conversion fails.

Examples

iex> {:ok, m} = Localize.Unit.new(1.75, "meter")
iex> {:ok, parts} = Localize.Unit.decompose(m, ["meter", "centimeter"])
iex> Enum.map(parts, fn u -> {u.name, u.value} end)
[{"meter", 1}, {"centimeter", 75.0}]

define_unit(name, definition)

@spec define_unit(String.t(), map()) :: :ok | {:error, String.t()}

Registers a custom unit definition at runtime.

Custom units extend the built-in CLDR unit database with user-defined units. Each custom unit must specify a base unit it converts to, a conversion factor, and a category.

Arguments

  • name is a string unit identifier (e.g., "smoot"). Must start with a lowercase letter and contain only lowercase letters, digits, and hyphens.

  • definition is a map with the following keys:

Options

  • :base_unit (required) — the CLDR base unit this custom unit converts to (e.g., "meter", "kilogram").

  • :factor (required) — conversion multiplier: 1 custom_unit = factor * base_unit.

  • :offset (optional) — additive offset for temperature-like conversions. Defaults to 0.0.

  • :category (required) — the unit category (e.g., "length", "mass").

  • :display (optional) — locale-specific display patterns. A nested map of locale => style => plural_patterns.

Returns

  • :ok on success.

  • {:error, reason} if validation fails.

Examples

iex> Localize.Unit.define_unit("smoot", %{
...>   base_unit: "meter",
...>   factor: 1.7018,
...>   category: "length",
...>   display: %{en: %{long: %{one: "{0} smoot", other: "{0} smoots"}}}
...> })
:ok

display_name(unit_or_name, options \\ [])

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

Returns the localized display name for a unit.

This returns the unit name as it would appear in the locale, without any numeric value (e.g., "meters", "kilograms").

Arguments

  • unit is a %Localize.Unit{} struct or a unit name string.

  • options is a keyword list of options.

Options

  • :locale is a locale identifier. The default is Localize.get_locale().

  • :format is :long, :short, or :narrow. The default is :long.

Returns

  • {:ok, name} or {:error, exception}.

Examples

iex> Localize.Unit.display_name("meter", locale: :en)
{:ok, "meters"}

iex> Localize.Unit.display_name("meter", locale: :en, format: :short)
{:ok, "m"}

div(unit, divisor)

@spec div(t(), number() | t()) :: {:ok, t()} | {:error, Exception.t()}

Divides a unit by a scalar or another unit.

When divided by a number, the value is scaled. When divided by another unit, a per-unit compound is produced (e.g., meter ÷ second).

Arguments

  • unit is a %Localize.Unit{} struct.

  • divisor is a number or a %Localize.Unit{} struct.

Returns

  • {:ok, unit} or {:error, exception}.

Examples

iex> {:ok, m} = Localize.Unit.new(10, "meter")
iex> {:ok, result} = Localize.Unit.div(m, 2)
iex> result.value
5.0

known_categories()

@spec known_categories() :: [String.t()]

Returns a list of all known unit categories.

Returns

  • A list of category name strings.

Examples

iex> cats = Localize.Unit.known_categories()
iex> "length" in cats
true

known_units_by_category()

@spec known_units_by_category() :: %{required(String.t()) => [String.t()]}

Returns a map of unit categories to their member units.

Returns

  • A map of %{category_string => [unit_name_string]}.

Examples

iex> by_cat = Localize.Unit.known_units_by_category()
iex> is_list(by_cat["length"])
true

known_usages()

@spec known_usages() :: [String.t()]

Returns a list of valid usage types for unit preferences.

Returns

  • A sorted list of usage atoms.

Examples

iex> usages = Localize.Unit.known_usages()
iex> "default" in usages
true

load_custom_units(path)

@spec load_custom_units(String.t()) :: {:ok, non_neg_integer()} | {:error, String.t()}

Loads custom unit definitions from an Elixir term file (.exs).

The file must evaluate to a list of maps, each with a :unit key and the standard definition fields (:base_unit, :factor, :category, and optionally :display).

Security

This function uses Code.eval_file/1 to evaluate the given file, which executes arbitrary Elixir code. Only load files from trusted sources. Never call this function with unsanitised user input or paths derived from external data.

Arguments

  • path is the path to the .exs file.

Returns

  • {:ok, count} with the number of units loaded.

  • {:error, reason} on failure.

measurement_system_for_territory(territory)

@spec measurement_system_for_territory(atom()) :: atom()

Returns the preferred measurement system for a territory.

Arguments

  • territory is a territory code atom (e.g., :US, :GB).

Returns

  • :metric, :us, or :uk.

Examples

iex> Localize.Unit.measurement_system_for_territory(:US)
:us

iex> Localize.Unit.measurement_system_for_territory(:FR)
:metric

mult(unit, multiplier)

@spec mult(t(), number() | t()) :: {:ok, t()} | {:error, Exception.t()}

Multiplies a unit by a scalar or another unit.

When multiplied by a number, the value is scaled. When multiplied by another unit, a compound unit is produced (e.g., meter × second).

Arguments

  • unit is a %Localize.Unit{} struct.

  • multiplier is a number or a %Localize.Unit{} struct.

Returns

  • {:ok, unit} or {:error, exception}.

Examples

iex> {:ok, m} = Localize.Unit.new(5, "meter")
iex> {:ok, result} = Localize.Unit.mult(m, 3)
iex> result.value
15

negate(unit)

@spec negate(t()) :: {:ok, t()} | {:error, Exception.t()}

Negates a unit's value.

Arguments

  • unit is a %Localize.Unit{} struct with a value.

Returns

  • {:ok, unit} or {:error, exception}.

new(name)

@spec new(String.t()) :: {:ok, t()} | {:error, Exception.t()}

Creates a new unit from a CLDR unit identifier string without a value.

Arguments

  • name is a unit identifier string such as "meter-per-second".

Returns

  • {:ok, unit} where unit is a %Localize.Unit{} struct, or

  • {:error, reason} if the identifier cannot be parsed.

Examples

iex> {:ok, unit} = Localize.Unit.new("meter")
iex> unit.name
"meter"

new(amount, unit, options \\ [])

@spec new(number() | Decimal.t(), String.t(), keyword()) ::
  {:ok, t()} | {:error, Exception.t()}

Creates a new unit with a value and a CLDR unit identifier string.

Arguments

  • amount is the numeric value (integer, float, or Decimal).

  • unit is a unit identifier string such as "meter-per-second".

  • options is an optional keyword list.

Options

  • :usage is a string specifying the intended usage context for the unit. Valid values include "default", "person", "person-height", "road", "food", "vehicle-fuel", and others defined in the CLDR unit preference data. The usage affects which target unit is selected when calling convert_measurement_system/2.

Returns

  • {:ok, unit} where unit is a %Localize.Unit{} struct, or

  • {:error, reason} if the value is not a valid number, the identifier cannot be parsed, or the usage is invalid.

Examples

iex> {:ok, unit} = Localize.Unit.new(100, "meter")
iex> unit.value
100
iex> unit.name
"meter"

iex> {:ok, unit} = Localize.Unit.new(Decimal.new("3.14"), "kilogram")
iex> unit.value
Decimal.new("3.14")

iex> {:ok, unit} = Localize.Unit.new(180, "centimeter", usage: "person-height")
iex> unit.usage
"person-height"

new!(name)

@spec new!(String.t()) :: t() | no_return()

Creates a new unit from a CLDR unit identifier string, raising on error.

Same as new/1 but returns the struct directly or raises ArgumentError.

Arguments

  • name is a unit identifier string.

Returns

  • A %Localize.Unit{} struct.

Examples

iex> unit = Localize.Unit.new!("meter")
iex> unit.name
"meter"

new!(amount, unit, options \\ [])

@spec new!(number() | Decimal.t(), String.t(), keyword()) :: t() | no_return()

Creates a new unit with a value and a CLDR unit identifier string, raising on error.

Same as new/2 but returns the struct directly or raises ArgumentError.

Arguments

  • amount is the numeric value (integer, float, or Decimal).

  • unit is a unit identifier string.

Returns

  • A %Localize.Unit{} struct.

Examples

iex> unit = Localize.Unit.new!(42, "kilogram")
iex> unit.value
42

sub(unit_1, unit_2)

@spec sub(t(), t()) :: {:ok, t()} | {:error, Exception.t() | String.t()}

Subtracts unit_2 from unit_1.

The value of unit_2 is converted to the unit type of unit_1.

Arguments

  • unit_1 is a %Localize.Unit{} struct with a value.

  • unit_2 is a %Localize.Unit{} struct with a value.

Returns

  • {:ok, unit} or {:error, exception}.

Examples

iex> {:ok, a} = Localize.Unit.new(5, "kilometer")
iex> {:ok, b} = Localize.Unit.new(2000, "meter")
iex> {:ok, result} = Localize.Unit.sub(a, b)
iex> result.value
3.0

to_iolist(unit, options \\ [])

@spec to_iolist(t(), Keyword.t()) :: {:ok, iolist()} | {:error, Exception.t()}

Formats a unit as an iolist.

Same as to_string/2 but returns an iolist for efficient IO operations without an intermediate binary allocation.

Arguments

  • unit is a %Localize.Unit{} struct.

  • options is a keyword list of formatting options (same as to_string/2).

Returns

  • {:ok, iolist} or {:error, exception}.

to_string(unit, options \\ [])

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

Formats a unit as a localized string.

Arguments

Options

  • :locale is a locale identifier atom, string, or a Localize.LanguageTag.t/0. The default is :en.

  • :format is :long, :short, or :narrow. The default is :long.

  • :backend is :nif or :elixir. When :nif is specified and the NIF is available, ICU4C is used for formatting. Otherwise falls back to the pure-Elixir formatter. The default is :elixir.

Returns

  • {:ok, formatted_string} on success.

  • {:error, exception} if the unit cannot be formatted.

Examples

iex> {:ok, unit} = Localize.Unit.new(42, "meter")
iex> Localize.Unit.to_string(unit)
{:ok, "42 meters"}

iex> {:ok, unit} = Localize.Unit.new(1, "meter")
iex> Localize.Unit.to_string(unit)
{:ok, "1 meter"}

iex> {:ok, unit} = Localize.Unit.new(42, "meter")
iex> Localize.Unit.to_string(unit, format: :short)
{:ok, "42 m"}

to_string!(unit, options \\ [])

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

Same as to_string/2 but raises on error.

Arguments

Returns

  • A formatted string.

Raises

  • Raises an exception if the unit cannot be formatted.

unit_category(name)

@spec unit_category(t() | String.t()) :: {:ok, String.t()} | {:error, Exception.t()}

Returns the CLDR category for a unit.

Arguments

  • unit is a %Localize.Unit{} struct or a unit name string.

Returns

  • {:ok, category} where category is a string like "length", or

  • {:error, exception}.

Examples

iex> {:ok, m} = Localize.Unit.new(1, "meter")
iex> Localize.Unit.unit_category(m)
{:ok, "length"}

iex> Localize.Unit.unit_category("kilogram")
{:ok, "mass"}

value(unit)

@spec value(t()) :: value()

Returns the numeric value of a unit.

Arguments

  • unit is a %Localize.Unit{} struct.

Returns

  • The value (number, Decimal, or nil).

Examples

iex> {:ok, m} = Localize.Unit.new(42, "meter")
iex> Localize.Unit.value(m)
42

zero(unit)

@spec zero(t()) :: t()

Returns a zero-valued unit of the same type.

Arguments

  • unit is a %Localize.Unit{} struct.

Returns

  • A new %Localize.Unit{} with value 0.

Examples

iex> {:ok, m} = Localize.Unit.new(42, "meter")
iex> z = Localize.Unit.zero(m)
iex> z.value
0

zero?(unit)

@spec zero?(t()) :: boolean()

Returns true if the unit has a zero value.

Arguments

  • unit is a %Localize.Unit{} struct.

Returns

  • true or false.

Examples

iex> {:ok, m} = Localize.Unit.new(0, "meter")
iex> Localize.Unit.zero?(m)
true