# `Localize.Unit`
[🔗](https://github.com/elixir-localize/localize/blob/v0.6.0/lib/localize/unit.ex#L1)

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](https://www.unicode.org/reports/tr35/tr35-general.html#unit-syntax).
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.

# `t`

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

# `value`

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

# `add`

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

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

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

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

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

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

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

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

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

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

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

```elixir
@spec known_units_by_category() :: %{required(String.t()) =&gt; [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`

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

```elixir
@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 {: .warning}
>
> 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`

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

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

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

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

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

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

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

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

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

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

Formats a unit as a localized string.

### Arguments

* `unit` is a `t:Localize.Unit.t/0` struct.

* `options` is a keyword list of options.

### Options

* `:locale` is a locale identifier atom, string, or a
  `t: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!`

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

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

### Arguments

* `unit` is a `t:Localize.Unit.t/0` struct.

* `options` is a keyword list of options.

### Returns

* A formatted string.

### Raises

* Raises an exception if the unit cannot be formatted.

# `unit_category`

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

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

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

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

---

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