defmodule Cldr.Currency do
@moduledoc """
Defines a currency structure and a set of functions to manage the validity of a currency code
and to return metadata for currencies.
"""
alias Cldr.Locale
alias Cldr.LanguageTag
@type format ::
:standard
| :accounting
| :short
| :long
| :percent
| :scientific
@type code :: atom()
@type currency_status :: :all | :current | :historic | :tender | :unannotated | :private
@type filter :: list(currency_status | code) | currency_status | code
@type territory :: atom() | String.t()
@type t :: %__MODULE__{
code: code,
alt_code: code,
name: String.t(),
tender: boolean,
symbol: String.t(),
digits: non_neg_integer,
rounding: non_neg_integer,
narrow_symbol: String.t(),
cash_digits: non_neg_integer,
cash_rounding: non_neg_integer,
iso_digits: non_neg_integer,
count: %{},
from: Calendar.year(),
to: Calendar.year()
}
defstruct code: nil,
alt_code: nil,
name: "",
symbol: "",
narrow_symbol: nil,
digits: 0,
rounding: 0,
cash_digits: 0,
cash_rounding: 0,
iso_digits: 0,
tender: false,
count: nil,
from: nil,
to: nil
@table_options [:set, {:read_concurrency, true}]
@default_options [quiet: true]
# Starts the supervisor for the private use
# currencies, delegated to Eternal which
# keeps :ets tables alive as much as is
# possible
@doc false
@spec start_link(Keyword.t()) :: Cldr.Eternal.on_start()
def start_link(options) when is_list(options) do
options = Keyword.merge(@default_options, options)
Cldr.Eternal.start_link(__MODULE__, @table_options, options)
end
@doc false
@spec start_link() :: Cldr.Eternal.on_start()
def start_link do
start_link(@default_options)
end
@doc false
def child_spec(opts) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [opts]},
type: :supervisor,
restart: :permanent,
shutdown: 500
}
end
@doc """
Returns a `Currency` struct created from the arguments.
## Arguments
* `currency` is a private use currency code in a format defined by
[ISO4217](https://en.wikipedia.org/wiki/ISO_4217)
which is `X` followed by two alphanumeric characters.
* `options` is a map of options representing the optional elements of
the `Cldr.Currency.t` struct.
## Options
* `:name` is the name of the currenct. Required.
* `:digits` is the precision of the currency. Required.
* `:symbol` is the currency symbol. Optional.
* `:narrow_symbol` is an alternative narrow symbol. Optional.
* `:round_nearest` is the rounding precision such as `0.05`. Optional.
* `:alt_code` is an alternative currency code for application use.
* `:cash_digits` is the precision of the currency when used as cash. Optional.
* `:cash_round_nearest` is the rounding precision when used as cash
such as `0.05`. Optional.
## Returns
* `{:ok, Cldr.Currency.t}` or
* `{:error, {exception, message}}`
## Example
iex> Cldr.Currency.new(:XAC, name: "XAC currency", digits: 0)
{:ok,
%Cldr.Currency{
alt_code: :XAC,
cash_digits: 0,
cash_rounding: nil,
code: :XAC,
count: %{other: "XAC currency"},
digits: 0,
from: nil,
iso_digits: 0,
name: "XAC currency",
narrow_symbol: nil,
rounding: 0,
symbol: "XAC",
tender: false,
to: nil
}}
iex> Cldr.Currency.new(:XBC)
{:error, {Cldr.CurrencyAlreadyDefined, "Currency :XBC is already defined."}}
iex> MyApp.Cldr.Currency.new(:XAB, name: "Private Use Name")
{:error, "Required options are missing. Required options are [:name, :digits]"}
iex> Cldr.Currency.new(:ZAA, name: "Invalid Private Use Name", digits: 0)
{:error, {Cldr.UnknownCurrencyError, "The currency :ZAA is invalid"}}
"""
@spec new(binary | atom, map | list) :: {:ok, t} | {:error, {module(), String.t()}}
def new(currency, options \\ [])
def new(currency, options) when is_list(options) do
with {:ok, currency_code} <- Cldr.validate_currency(currency),
{:ok, currency_code} <- validate_new_currency(currency_code),
{:ok, options} <- validate_options(currency_code, options) do
currency = struct(__MODULE__, [{:code, currency_code} | options])
store_currency(currency)
end
end
defp validate_options(code, options) do
with {:ok, options} <- assert_options(options, [:name, :digits]) do
options = [
code: code,
alt_code: options[:alt_code] || code,
name: options[:name],
symbol: options[:symbol] || to_string(code),
narrow_symbol: options[:narrow_symbol] || options[:symbol],
digits: options[:digits],
rounding: options[:round_nearest] || 0,
cash_digits: options[:cash_digits] || options[:digits],
cash_rounding: options[:cash_round_nearest] || options[:round_nearest],
iso_digits: options[:digits],
tender: options[:tender] || false,
count: options[:count] || %{other: options[:name]}
]
{:ok, options}
end
end
defp assert_options(options, keys) do
if Enum.all?(keys, &options[&1]) do
{:ok, options}
else
{:error, "Required options are missing. Required options are #{inspect(keys)}"}
end
end
@doc """
Determines is a new currency is already
defined.
## Example
iex> Cldr.Currency.validate_new_currency :XAD
{:ok, :XAD}
iex> Cldr.Currency.validate_new_currency :USD
{:error, {Cldr.CurrencyAlreadyDefined, "Currency :USD is already defined."}}
"""
@spec validate_new_currency(code) :: {:ok, code} | {:error, {module, String.t()}}
def validate_new_currency(code) do
if code in known_currency_codes() do
{:error, {Cldr.CurrencyAlreadyDefined, currency_already_defined_error(code)}}
else
{:ok, code}
end
end
defp store_currency(%Cldr.Currency{code: code} = currency) do
:ets.insert_new(__MODULE__, {code, currency})
{:ok, currency}
rescue
ArgumentError ->
{:error, {Cldr.CurrencyNotSavedError, currency_not_saved_error(code)}}
end
@doc """
Return the display name for a currency.
The display name is useful for UI
uses, for example in menus. The display name
is typically capitalized for stand-alone use
where as the display name returned by
`Cldr.Currency.pluralize/4` is typically
lower-cased for use within sentences.
## Arguments
* `currency` is any currency code returned by `Cldr.Currency.known_currencies/0` or
a `t:Cldr.Currency` struct returned by `Cldr.Currency.currency_for_code/3`
## Options
* `:locale` is any locale returned by `Cldr.Locale.new!/2`. The
default is `Cldr.get_locale/0`
* `:backend` is any module that includes `use Cldr` and therefore
is a `Cldr` backend module. The default is `Cldr.default_backend!/0`
## Returns
* `{:ok, display_name}`
* or `{:error, {exception, reason}}`
## Examples
iex> Cldr.Currency.display_name :AUD, backend: MyApp.Cldr
{:ok, "Australian Dollar"}
iex> Cldr.Currency.display_name "AUD", backend: MyApp.Cldr, locale: "fr"
{:ok, "dollar australien"}
iex> Cldr.Currency.display_name "EUR", backend: MyApp.Cldr, locale: "de"
{:ok, "Euro"}
iex> Cldr.Currency.display_name "ZZZ", backend: MyApp.Cldr
{:error, {Cldr.UnknownCurrencyError, "The currency \\"ZZZ\\" is invalid"}}
"""
@spec display_name(t() | code(), Keyword.t()) ::
{:ok, String.t()} | {:error, {module(), String.t()}}
def display_name(currency, options \\ [])
def display_name(%__MODULE__{} = currency, _options) do
{:ok, currency.name}
end
def display_name(currency_code, options) do
with {:ok, currency_code} <- Cldr.validate_currency(currency_code),
{_locale, backend} = Cldr.locale_and_backend_from(options),
{:ok, currency_data} <- currency_for_code(currency_code, backend, options) do
display_name(currency_data, options)
end
end
@doc """
Return the display name for a currency or
raises and exception on error.
The display name is useful for UI
uses, for example in menus. The display name
is typically capitalized for stand-alone use
where as the display name returned by
`Cldr.Currency.pluralize/4` is typically
lower-cased for use within sentences.
## Arguments
* `currency` is any currency code returned by `Cldr.Currency.known_currencies/0` or
a `t:Cldr.Currency` struct returned by `Cldr.Currency.currency_for_code/3`
## Options
* `:locale` is any locale returned by `Cldr.Locale.new!/2`. The
default is `Cldr.get_locale/0`
* `:backend` is any module that includes `use Cldr` and therefore
is a `Cldr` backend module. The default is `Cldr.default_backend!/0`
## Returns
* `display_name`
* or raises an exception
## Examples
iex> Cldr.Currency.display_name! :AUD, backend: MyApp.Cldr
"Australian Dollar"
iex> Cldr.Currency.display_name! "AUD", backend: MyApp.Cldr, locale: "fr"
"dollar australien"
iex> Cldr.Currency.display_name! "EUR", backend: MyApp.Cldr, locale: "de"
"Euro"
#=> Cldr.Currency.display_name! "ZZZ", backend: MyApp.Cldr
** (Cldr.UnknownCurrencyError) The currency "ZZZ" is invalid
"""
@spec display_name!(t() | code(), Keyword.t()) ::
String.t() | no_return
def display_name!(currency, options \\ []) do
case display_name(currency, options) do
{:ok, display_name} -> display_name
{:error, {exception, reason}} -> raise exception, reason
end
end
@doc """
Returns the appropriate currency display name for the `currency`, based
on the plural rules in effect for the `locale`.
## Arguments
* `number` is an integer, float or `Decimal`
* `currency` is any currency returned by `Cldr.Currency.known_currencies/0`
* `locale` is any valid locale name returned by `Cldr.known_locale_names/1`
or a `Cldr.LanguageTag` struct returned by `Cldr.Locale.new!/2`
* `backend` is any module that includes `use Cldr` and therefore
is a `Cldr` backend module
* `options` is a keyword list of options
## Options
* `:locale` is any locale returned by `Cldr.Locale.new!/2`. The
default is `<backend>.get_locale/1`
## Returns
* `{:ok, plural_string}` or
* `{:error, {exception, message}}`
## Examples
iex> Cldr.Currency.pluralize 1, :USD, MyApp.Cldr
{:ok, "US dollar"}
iex> Cldr.Currency.pluralize 3, :USD, MyApp.Cldr
{:ok, "US dollars"}
iex> Cldr.Currency.pluralize 12, :USD, MyApp.Cldr, locale: "zh"
{:ok, "美元"}
iex> Cldr.Currency.pluralize 12, :USD, MyApp.Cldr, locale: "fr"
{:ok, "dollars des États-Unis"}
iex> Cldr.Currency.pluralize 1, :USD, MyApp.Cldr, locale: "fr"
{:ok, "dollar des États-Unis"}
"""
@spec pluralize(pos_integer, code(), Cldr.backend(), Keyword.t()) ::
{:ok, String.t()} | {:error, {module(), String.t()}}
def pluralize(number, currency, backend, options \\ []) do
locale = Keyword.get_lazy(options, :locale, &backend.get_locale/0)
options = Keyword.put(options, :locale, locale)
with {:ok, currency_code} <- Cldr.validate_currency(currency),
{:ok, locale} <- Cldr.validate_locale(locale, backend),
{:ok, currency_data} <- currency_for_code(currency_code, backend, options) do
counts = Map.get(currency_data, :count)
{:ok, Module.concat(backend, Number.Cardinal).pluralize(number, locale, counts)}
end
end
@doc """
Returns a list of all known currency codes.
## Example
iex> Cldr.Currency.known_currency_codes
"""
@spec known_currency_codes() :: list(atom)
def known_currency_codes do
Cldr.known_currencies() ++ private_currency_codes()
end
@deprecated "Use known_currency_codes/0"
defdelegate known_currencies, to: __MODULE__, as: :known_currency_codes
@doc """
Returns a boolean indicating if the supplied currency code is known.
## Arguments
* `currency_code` is a `binary` or `atom` representing an ISO4217
currency code
## Returns
* `true` or `false`
## Examples
iex> Cldr.Currency.known_currency_code? "AUD"
true
iex> Cldr.Currency.known_currency_code? "GGG"
false
iex> Cldr.Currency.known_currency_code? :XCV
false
"""
@spec known_currency_code?(code()) :: boolean
def known_currency_code?(currency_code) do
with {:ok, currency_code} <- Cldr.validate_currency(currency_code) do
currency_code in known_currency_codes()
else
_other -> false
end
end
@deprecated "Use known_currency_code?/0"
defdelegate known_currency?(currency), to: __MODULE__, as: :known_currency_code?
@doc """
Returns a 2-tuple indicating if the supplied currency code is known.
## Arguments
* `currency_code` is a `binary` or `atom` representing an ISO4217
currency code
## Returns
* `{:ok, currency_code}` or
* `{:error, {exception, reason}}`
## Examples
iex> Cldr.Currency.known_currency_code "AUD"
{:ok, :AUD}
iex> Cldr.Currency.known_currency_code "GGG"
{:error, {Cldr.UnknownCurrencyError, "The currency \\"GGG\\" is invalid"}}
"""
@spec known_currency_code(code()) :: {:ok, code} | {:error, {module, String.t()}}
def known_currency_code(currency_code) do
with {:ok, currency_code} <- Cldr.validate_currency(currency_code) do
if currency_code in known_currency_codes() do
{:ok, currency_code}
else
{:error, {Cldr.UnknownCurrencyError, Cldr.unknown_currency_error(currency_code)}}
end
end
end
@doc """
Returns a list of all private currency codes.
"""
@spec private_currency_codes() :: list(atom)
def private_currency_codes do
Map.keys(private_currencies())
end
@doc """
Returns a map of private currencies.
These comprise all currencies created with
`Cldr.Currency.new/2`.
"""
@spec private_currencies :: %{code => t}
def private_currencies do
__MODULE__
|> :ets.tab2list()
|> Map.new()
rescue
ArgumentError ->
%{}
end
@doc """
Returns the effective currency for a given locale
## Arguments
* `locale` is a `Cldr.LanguageTag` struct returned by `Cldr.Locale.new!/2`
## Returns
* A ISO 4217 currency code as an upcased atom
## Examples
iex> {:ok, locale} = Cldr.validate_locale "en", MyApp.Cldr
iex> Cldr.Currency.currency_from_locale locale
:USD
iex> {:ok, locale} = Cldr.validate_locale "en-AU", MyApp.Cldr
iex> Cldr.Currency.currency_from_locale locale
:AUD
iex> Cldr.Currency.currency_from_locale "en-GB"
:GBP
"""
def currency_from_locale(%LanguageTag{locale: %{currency: nil}} = locale) do
current_currency_from_locale(locale)
end
def currency_from_locale(%LanguageTag{locale: %{currency: currency}}) do
currency
end
def currency_from_locale(%LanguageTag{} = locale) do
current_currency_from_locale(locale)
end
@doc """
Returns the effective currency for a given locale
## Arguments
* `locale` is any valid locale name returned by
`Cldr.known_locale_names/1`
* `backend` is any module that includes `use Cldr` and therefore
is a `Cldr` backend module. The default is `Cldr.default_backend!/0`
## Returns
* A ISO 4217 currency code as an upcased atom
## Examples
iex> Cldr.Currency.currency_from_locale "fr-CH", MyApp.Cldr
:CHF
iex> Cldr.Currency.currency_from_locale "fr-CH-u-cu-INR", MyApp.Cldr
:INR
"""
def currency_from_locale(locale, backend \\ default_backend()) when is_binary(locale) do
with {:ok, locale} <- Cldr.validate_locale(locale, backend) do
currency_from_locale(locale)
end
end
@doc """
Returns the effective currency format for a given locale
## Arguments
* `locale` a `Cldr.LanguageTag` struct returned by `Cldr.Locale.new!/2`
## Returns
* Either `:accounting` or `:currency`
## Examples
iex> {:ok, locale} = Cldr.validate_locale "en", MyApp.Cldr
iex> Cldr.Currency.currency_format_from_locale locale
:currency
iex> {:ok, locale} = Cldr.validate_locale "en-AU-u-cu-eur", MyApp.Cldr
iex> Cldr.Currency.currency_format_from_locale locale
:currency
iex> {:ok, locale} = Cldr.validate_locale "en-AU-u-cu-eur-cf-account", MyApp.Cldr
iex> Cldr.Currency.currency_format_from_locale locale
:accounting
"""
def currency_format_from_locale(%LanguageTag{locale: %{cf: nil}}) do
:currency
end
def currency_format_from_locale(%LanguageTag{locale: %{cf: :standard}}) do
:currency
end
def currency_format_from_locale(%LanguageTag{locale: %{cf: :account}}) do
:accounting
end
def currency_format_from_locale(%LanguageTag{}) do
:currency
end
@doc """
Returns the effective currency format for a given locale
## Arguments
* `locale` a `Cldr.LanguageTag` struct returned by `Cldr.Locale.new!/2`
* `backend` is any module that includes `use Cldr` and therefore
is a `Cldr` backend module. The default is `Cldr.default_backend!/0`
## Returns
* Either `:accounting` or `:currency`
## Examples
iex> Cldr.Currency.currency_format_from_locale "en", MyApp.Cldr
:currency
iex> Cldr.Currency.currency_format_from_locale "en-AU-u-cu-eur", MyApp.Cldr
:currency
iex> Cldr.Currency.currency_format_from_locale "en-AU-u-cu-eur-cf-account", MyApp.Cldr
:accounting
"""
def currency_format_from_locale(locale, backend \\ default_backend()) when is_binary(locale) do
with {:ok, locale} <- Cldr.validate_locale(locale, backend) do
currency_format_from_locale(locale)
end
end
@doc """
Returns a mapping of all ISO3166 territory
codes and a list of historic and the current
currency for those territories.
## Example
iex> Cldr.Currency.territory_currencies |> Map.get(:LT)
%{
EUR: %{from: ~D[2015-01-01], to: nil},
LTL: %{from: nil, to: ~D[2014-12-31]},
LTT: %{from: nil, to: ~D[1993-06-25]},
SUR: %{from: nil, to: ~D[1992-10-01]}
}
"""
@territory_currencies Cldr.Config.territory_currency_data()
def territory_currencies do
@territory_currencies
end
@doc """
Returns a list of currencies associated with
a given territory.
## Arguments
* `territory` is any valid ISO 3166 Alpha-2 territory code.
See `Cldr.validate_territory/1`.
## Returns
* `{:ok, map}` where `map` has as its key a `t:Cldr.Currency`
struct and the value is a map of validity dates for that
currency; or
* `{:error, {exception, reason}}`
## Example
iex> Cldr.Currency.territory_currencies(:LT)
{:ok, %{
EUR: %{from: ~D[2015-01-01], to: nil},
LTL: %{from: nil, to: ~D[2014-12-31]},
LTT: %{from: nil, to: ~D[1993-06-25]},
SUR: %{from: nil, to: ~D[1992-10-01]}
}}
"""
@spec territory_currencies(territory()) ::
{:ok, map()} | {:error, {module(), String.t()}}
def territory_currencies(territory) do
with {:ok, territory} <- Cldr.validate_territory(territory),
{:ok, currencies} <- Map.fetch(territory_currencies(), territory) do
{:ok, currencies}
else
:error ->
{:error,
{Cldr.UnknownCurrencyError, "No currencies for #{inspect(territory)} were found"}}
other ->
other
end
end
@doc """
Returns a list of currencies associated with
a given territory.
## Arguments
* `territory` is any valid ISO 3166 Alpha-2 territory code.
See `Cldr.validate_territory/1`.
## Returns
* `map` where `map` has as its key a `t:Cldr.Currency`
struct and the value is a map of validity dates for that
currency; or
* raises an exception
## Example
iex> Cldr.Currency.territory_currencies!(:LT)
%{
EUR: %{from: ~D[2015-01-01], to: nil},
LTL: %{from: nil, to: ~D[2014-12-31]},
LTT: %{from: nil, to: ~D[1993-06-25]},
SUR: %{from: nil, to: ~D[1992-10-01]}
}
"""
@spec territory_currencies!(territory()) :: map() | no_return()
def territory_currencies!(territory) do
case territory_currencies(territory) do
{:ok, currencies} -> currencies
{:error, {exception, reason}} -> raise exception, reason
end
end
@doc """
Returns a list of historic and the current
currency for a given locale.
## Arguments
* `locale` is any valid locale name returned by `Cldr.known_locale_names/1`
or a `Cldr.LanguageTag` struct returned by `Cldr.Locale.new!/2`
* `backend` is any module that includes `use Cldr` and therefore
is a `Cldr` backend module
## Example
iex> Cldr.Currency.currency_history_for_locale "en", MyApp.Cldr
{:ok,
%{
USD: %{from: ~D[1792-01-01], to: nil},
USN: %{tender: false},
USS: %{from: nil, tender: false, to: ~D[2014-03-01]}
}
}
"""
@spec currency_history_for_locale(LanguageTag.t()) ::
{:ok, map()} | {:error, {atom, binary}}
def currency_history_for_locale(%LanguageTag{} = locale) do
locale
|> Cldr.Locale.territory_from_locale()
|> territory_currencies()
end
@spec currency_history_for_locale(Locale.locale_name() | String.t(), Cldr.backend()) ::
{:ok, map()} | {:error, {module(), String.t()}}
def currency_history_for_locale(locale_name, backend) do
with {:ok, locale} <- Cldr.validate_locale(locale_name, backend) do
currency_history_for_locale(locale)
end
end
@doc """
Returns the current currency from a given locale.
This function does not consider the `U` extenion
parameters `cu` or `rg`. It is recommended to us
`Cldr.Currency.currency_from_locale/1` in most
circumstances.
## Arguments
* `locale` is any valid locale name returned by `Cldr.known_locale_names/1`
or a `Cldr.LanguageTag` struct returned by `Cldr.Locale.new!/2`
* `backend` is any module that includes `use Cldr` and therefore
is a `Cldr` backend module
## Examples
iex> Cldr.Currency.current_currency_from_locale "en", MyApp.Cldr
:USD
iex> Cldr.Currency.current_currency_from_locale "en-AU", MyApp.Cldr
:AUD
"""
@spec current_currency_from_locale(LanguageTag.t()) :: any()
def current_currency_from_locale(%LanguageTag{} = locale) do
locale
|> Cldr.Locale.territory_from_locale()
|> current_currency_for_territory()
end
@spec current_currency_from_locale(Locale.locale_name() | String.t(), Cldr.backend()) ::
code() | nil | {:error, {module(), String.t()}}
def current_currency_from_locale(locale_name, backend) do
with {:ok, locale} <- Cldr.validate_locale(locale_name, backend) do
current_currency_from_locale(locale)
end
end
@doc """
Returns the current currency for a given territory.
## Arguments
* `territory` is any valid territory name returned by
`Cldr.known_territories/0`
## Examples
iex> Cldr.Currency.current_currency_for_territory :US
:USD
iex> Cldr.Currency.current_currency_for_territory :AU
:AUD
"""
@spec current_currency_for_territory(Cldr.Locale.territory()) ::
code() | nil | {:error, {module(), String.t()}}
def current_currency_for_territory(territory) do
with {:ok, territory} <- Cldr.validate_territory(territory),
{:ok, history} <- territory_currencies(territory) do
history
|> Enum.find(fn {_currency, dates} -> Map.has_key?(dates, :to) && is_nil(dates.to) end)
|> elem(0)
end
end
@doc """
Returns the currency metadata for the requested currency code.
## Arguments
* `currency_or_currency_code` is a `binary` or `atom` representation
of an ISO 4217 currency code, or a `t:Cldr.Currency` struct.
* `backend` is any module that includes `use Cldr` and therefore
is a `Cldr` backend module
* `options` is a `Keyword` list of options.
## Options
* `:locale` is any valid locale name returned by `Cldr.known_locale_names/1`
or a `Cldr.LanguageTag` struct returned by `Cldr.Locale.new!/2`
## Examples
iex> Cldr.Currency.currency_for_code("AUD", MyApp.Cldr)
{:ok,
%Cldr.Currency{
cash_digits: 2,
cash_rounding: 0,
code: "AUD",
count: %{one: "Australian dollar", other: "Australian dollars"},
digits: 2,
iso_digits: 2,
name: "Australian Dollar",
narrow_symbol: "$",
rounding: 0,
symbol: "A$",
tender: true
}}
iex> Cldr.Currency.currency_for_code("THB", MyApp.Cldr)
{:ok,
%Cldr.Currency{
cash_digits: 2,
cash_rounding: 0,
code: "THB",
count: %{one: "Thai baht", other: "Thai baht"},
digits: 2,
iso_digits: 2,
name: "Thai Baht",
narrow_symbol: "฿",
rounding: 0,
symbol: "THB",
tender: true
}}
"""
@spec currency_for_code(code() | t(), Cldr.backend(), Keyword.t()) ::
{:ok, t()} | {:error, {module(), String.t()}}
def currency_for_code(currency_or_currency_code, backend, options \\ [])
def currency_for_code(%__MODULE__{} = currency, _backend, _options) do
{:ok, currency}
end
def currency_for_code(currency_code, backend, options) do
{locale, backend} = Cldr.locale_and_backend_from(options[:locale], backend)
with {:ok, code} <- Cldr.validate_currency(currency_code),
{:ok, locale} <- Cldr.validate_locale(locale, backend),
{:ok, currencies} <- currencies_for_locale(locale, backend) do
{:ok, Map.get_lazy(currencies, code, fn -> Map.get(private_currencies(), code) end)}
end
end
@doc """
Returns a map of the metadata for all currencies for
a given locale.
## Arguments
* `locale` is any valid locale name returned by `Cldr.known_locale_names/1`
or a `Cldr.LanguageTag` struct returned by `Cldr.Locale.new!/2`
* `backend` is any module that includes `use Cldr` and therefore
is a `Cldr` backend module
* `currency_status` is `:all`, `:current`, `:historic`,
`unannotated` or `:tender`; or a list of one or more status.
The default is `:all`. See `Cldr.Currency.currency_filter/2`.
## Returns
* `{:ok, currency_map}` or
* `{:error, {exception, reason}}`
## Example
=> Cldr.Currency.currencies_for_locale "en", MyApp.Cldr
{:ok,
%{
FJD: %Cldr.Currency{
cash_digits: 2,
cash_rounding: 0,
code: "FJD",
count: %{one: "Fijian dollar", other: "Fijian dollars"},
digits: 2,
from: nil,
iso_digits: 2,
name: "Fijian Dollar",
narrow_symbol: "$",
rounding: 0,
symbol: "FJD",
tender: true,
to: nil
},
SUR: %Cldr.Currency{
cash_digits: 2,
cash_rounding: 0,
code: "SUR",
count: %{one: "Soviet rouble", other: "Soviet roubles"},
digits: 2,
from: nil,
iso_digits: nil,
name: "Soviet Rouble",
narrow_symbol: nil,
rounding: 0,
symbol: "SUR",
tender: true,
to: nil
},
...
}}
"""
@spec currencies_for_locale(
Locale.locale_name() | LanguageTag.t(),
Cldr.backend(),
only :: filter(),
except :: filter()
) ::
{:ok, map()} | {:error, {module(), String.t()}}
def currencies_for_locale(locale, backend, only \\ :all, except \\ nil) do
Module.concat(backend, Currency).currencies_for_locale(locale, only, except)
end
@doc """
Returns a map of the metadata for all currencies for
a given locale and raises on error.
## Arguments
* `locale` is any valid locale name returned by `Cldr.known_locale_names/1`
or a `Cldr.LanguageTag` struct returned by `Cldr.Locale.new!/2`
* `backend` is any module that includes `use Cldr` and therefore
is a `Cldr` backend module
* `currency_status` is `:all`, `:current`, `:historic`,
`unannotated` or `:tender`; or a list of one or more status.
The default is `:all`. See `Cldr.Currency.currency_filter/2`.
## Returns
* `{:ok, currency_map}` or
* raises an exception
## Example
#=> MyApp.Cldr.Currency.currencies_for_locale! "en"
%{
FJD: %Cldr.Currency{
cash_digits: 2,
cash_rounding: 0,
code: "FJD",
count: %{one: "Fijian dollar", other: "Fijian dollars"},
digits: 2,
from: nil,
iso_digits: 2,
name: "Fijian Dollar",
narrow_symbol: "$",
rounding: 0,
symbol: "FJD",
tender: true,
to: nil
},
SUR: %Cldr.Currency{
cash_digits: 2,
cash_rounding: 0,
code: "SUR",
count: %{one: "Soviet rouble", other: "Soviet roubles"},
digits: 2,
from: nil,
iso_digits: nil,
name: "Soviet Rouble",
narrow_symbol: nil,
rounding: 0,
symbol: "SUR",
tender: true,
to: nil
},
...
}
"""
@spec currencies_for_locale!(
Locale.locale_name() | LanguageTag.t(),
Cldr.backend(),
only :: filter(),
except :: filter()
) ::
map() | no_return()
def currencies_for_locale!(locale, backend, only \\ :all, except \\ nil) do
Module.concat(backend, Currency).currencies_for_locale!(locale, only, except)
end
@doc """
Returns a map that matches a currency string to a
currency code.
A currency string is a localised name or symbol
representing a currency in a locale-specific manner.
## Arguments
* `locale` is any valid locale name returned by `Cldr.known_locale_names/1`
or a `Cldr.LanguageTag` struct returned by `Cldr.Locale.new!/2`.
* `:only` is `:all`, `:current`, `:historic`,
`unannotated` or `:tender`; or a list of one or more status
The default is `:all`. See `Cldr.Currency.currency_filter/2`.
## Returns
* `{:ok, currency_string_map}` or
* `{:error, {exception, reason}}`
## Example
=> Cldr.Currency.currency_strings "en", MyApp.Cldr
{:ok,
%{
"mexican silver pesos" => :MXP,
"sudanese dinar" => :SDD,
"bad" => :BAD,
"rsd" => :RSD,
"swazi lilangeni" => :SZL,
"zairean new zaire" => :ZRN,
"guyanaese dollars" => :GYD,
"equatorial guinean ekwele" => :GQE,
...
}}
# Currencies match all currency status'
=> Cldr.Currency.currency_strings "en", MyApp.Cldr, [:tender, :current, :unannotated]
{:ok,
%{
"rsd" => :RSD,
"swazi lilangeni" => :SZL,
"guyanaese dollars" => :GYD,
"syrian pound" => :SYP,
"scr" => :SCR,
"bangladeshi takas" => :BDT,
"netherlands antillean guilders" => :ANG,
"pen" => :PEN,
...
}}
"""
@spec currency_strings(
Cldr.LanguageTag.t() | Cldr.Locale.locale_name(),
only :: filter(),
except :: filter()
) ::
{:ok, map()} | {:error, {module(), String.t()}}
def currency_strings(locale, backend, only \\ :all, except \\ nil) do
Module.concat(backend, Currency).currency_strings(locale, only, except)
end
@doc """
Returns a map that matches a currency string to a
currency code or raises an exception.
A currency string is a localised name or symbol
representing a currency in a locale-specific manner.
## Arguments
* `locale` is any valid locale name returned by `Cldr.known_locale_names/1`
or a `Cldr.LanguageTag` struct returned by `Cldr.Locale.new!/2`.
* `currency_status` is `:all`, `:current`, `:historic`,
`unannotated` or `:tender`; or a list of one or more status.
The default is `:all`. See `Cldr.Currency.currency_filter/2`.
## Returns
* `{:ok, currency_string_map}` or
* raises an exception
## Example
=> Cldr.Currency.currency_strings! "en", MyApp.Cldr
%{
"mexican silver pesos" => :MXP,
"sudanese dinar" => :SDD,
"bad" => :BAD,
"rsd" => :RSD,
"swazi lilangeni" => :SZL,
"zairean new zaire" => :ZRN,
"guyanaese dollars" => :GYD,
"equatorial guinean ekwele" => :GQE,
...
}
"""
@spec currency_strings!(
LanguageTag.t() | Locale.locale_name(),
only :: filter(),
except :: filter()
) ::
map() | no_return
def currency_strings!(locale, backend, only \\ :all, except \\ nil) do
case Module.concat(backend, Currency).currency_strings(locale, only, except) do
{:ok, currency_strings} -> currency_strings
{:error, {exception, reason}} -> raise exception, reason
end
end
@doc """
Returns the strings associated with a currency
in a given locale.
## Arguments
* `currency` is an ISO4217 currency code
* `locale` is any valid locale name returned by `Cldr.known_locale_names/1`
or a `Cldr.LanguageTag` struct returned by `Cldr.Locale.new!/2`.
* `backend` is any module that includes `use Cldr` and therefore
is a `Cldr` backend module
## Returns
* A list of strings or
* `{:error, {exception, reason}}`
## Example
iex> Cldr.Currency.strings_for_currency(:AUD, "en", MyApp.Cldr) |> Enum.sort
["a$", "aud", "australian dollar", "australian dollars"]
iex> Cldr.Currency.strings_for_currency(:AUD, "de", MyApp.Cldr) |> Enum.sort
["au$", "aud", "australische dollar", "australischer dollar"]
iex> Cldr.Currency.strings_for_currency(:AUD, "zh", MyApp.Cldr) |> Enum.sort
["au$", "aud", "澳大利亚元"]
"""
@spec strings_for_currency(t(), LanguageTag.t | Locale.locale_name, Cldr.backend) ::
[String.t()]
def strings_for_currency(currency, locale, backend) do
module = Module.concat(backend, Currency)
with {:ok, currency_strings} <- module.currency_strings(locale),
{:ok, currency} <- Cldr.validate_currency(currency) do
Enum.filter(currency_strings, fn {_k, v} -> v == currency end)
|> Enum.map(fn {k, _v} -> k end)
end
end
@doc """
Return only those currencies meeting the
filter criteria.
## Arguments
* `currency` is a `t:Cldr.Currency`, a list of `t:Cldr.Currency` or a
map where the values of each item is a `Cldr.Currency.t`
* `only` is `:all`, `:current`, `:historic`, `:tender`
`unannotated` or a list of one or more status or currency codes.
The default is `:all`
* `except` is `:current`, `:historic`, `:tender`
`unannotated` or a list of one or more status or currency codes.
The default is `nil`
## Currency Status
A currency may be in current use or of historic interest only. It
may or may not be legal tender. And it may mostly be used as a financial
instrument. To help return the most useful currencies the
currency status code acts as follows:
* `:all`, the default, returns all currencies
* `:current` returns those currencies that have a `:to`
date of nil and which also is a known ISO4217 currency
* `:historic` is the opposite of `:current`
* `:tender` is a currency that is legal tender
* `:unannotated` is a currency that doesn't have
"(some string)" in its name. These are usually
financial instruments.
"""
@spec currency_filter(t() | [t()] | map(), currency_status()) :: list(t())
def currency_filter(currencies, only \\ :all, except \\ nil)
def currency_filter(currencies, :all, nil) do
currencies
end
def currency_filter(currencies, only, except) when not is_list(only) do
currency_filter(currencies, [only], except)
end
def currency_filter(currencies, only, except) when not is_list(except) do
currency_filter(currencies, only, [except])
end
def currency_filter(%Cldr.Currency{} = currency, only, except) do
currency_filter([currency], only, except)
end
def currency_filter(currencies, only, except) when is_map(currencies) do
currencies
|> Map.values()
|> currency_filter(only, except)
|> Map.new(fn currency -> {String.to_atom(currency.code), currency} end)
end
def currency_filter(currencies, only, except) do
expand_filter(currencies, :only, only) -- expand_filter(currencies, :except, except)
end
defp expand_filter(currencies, :only, [:all]) do
currencies
end
defp expand_filter(_currencies, :except, [nil]) do
[]
end
defp expand_filter(currencies, _, filter_list) do
Enum.flat_map(filter_list, fn filter ->
case filter do
:historic ->
Enum.filter(currencies, &historic?/1)
:tender ->
Enum.filter(currencies, &tender?/1)
:current ->
Enum.filter(currencies, ¤t?/1)
:annotated ->
Enum.filter(currencies, &annotated?/1)
:unannotated ->
Enum.filter(currencies, &unannotated?/1)
:private ->
private_currencies()
code when is_binary(code) ->
Enum.filter(currencies, fn currency ->
currency.code == code
end)
code when is_atom(code) ->
code = to_string(code)
Enum.filter(currencies, fn currency ->
currency.code == code
end)
end
end)
|> Enum.uniq()
end
@doc """
Returns a boolean indicating if a given
currency is historic.
Historic means that the currency is no longer
in use.
## Arguments
* `currency` is a `t:Cldr.Currency`
## Returns
* `true` or `false`
"""
@spec historic?(currency :: t()) :: boolean()
def historic?(%Cldr.Currency{} = currency) do
is_nil(currency.iso_digits) ||
(is_integer(currency.to) && currency.to < Date.utc_today().year)
end
@doc """
Returns a boolean indicating if a given
currency is legal tender.
Legal tender is anything recognized by law
as a means to settle a public or private debt or
meet a financial obligation.
## Arguments
* `currency` is a `t:Cldr.Currency`
## Returns
* `true` or `false`
"""
@spec tender?(currency :: t()) :: boolean()
def tender?(%Cldr.Currency{} = currency) do
!!currency.tender
end
@doc """
Returns a boolean indicating if a given
currency is current.
Current means that the currency is in current
use.
## Arguments
* `currency` is a `t:Cldr.Currency`
## Returns
* `true` or `false`
"""
@spec current?(currency :: t()) :: boolean()
def current?(%Cldr.Currency{} = currency) do
!is_nil(currency.iso_digits) && is_nil(currency.to)
end
@doc """
Returns a boolean indicating if a given
currency is annotated.
Annotated means that the currency description
has annotations (comments inside parenthesis).
This is mostly found in currency codes used as
financial instruments (not legal tender).
## Arguments
* `currency` is a `t:Cldr.Currency`
## Returns
* `true` or `false`
"""
@spec annotated?(currency :: t()) :: boolean()
def annotated?(%Cldr.Currency{} = currency) do
String.contains?(currency.name, "(")
end
@doc """
Returns a boolean indicating if a given
currency is unannotated.
Annotated means that the currency description
has annotations (comments inside parenthesis).
This is mostly found in currency codes used as
financial instruments (not legal tender).
## Arguments
* `currency` is a `t:Cldr.Currency`
## Returns
* `true` or `false`
"""
@spec unannotated?(currency :: t()) :: boolean()
def unannotated?(%Cldr.Currency{} = currency) do
!annotated?(currency)
end
# Sort the list by string. If the string is the same
# then sort historic currencies after the current one
@doc false
def string_comparator({k, v1}, {k, v2}, currencies) do
cond do
historic?(currencies[v1]) ->
false
historic?(currencies[v2]) ->
true
true ->
raise "String #{inspect(k)} has two current currencies of #{inspect(v1)} and " <>
"#{inspect(v2)}."
end
end
def string_comparator({k1, _v1}, {k2, _v2}, _currencies) do
k1 < k2
end
# Its possible that more than one currency will have a string
# in common with another currency. One example is `:AFA` and
# `:AFN`. As in this csae, its most common when a country
# changes to a new currency with the same name.
# The strategy is to remove the duplicate string from the
# currency that is historic.
@doc false
def remove_duplicate_strings(strings, currencies) do
strings
|> Enum.sort(fn a, b -> string_comparator(a, b, currencies) end)
|> remove_duplicates(currencies)
end
defp remove_duplicates([{_, _}] = currency, _currencies) do
currency
end
# Same string, different code -> omit the 2nd one since
# we sort historic currencies after the current ones
defp remove_duplicates([{c1, code1} | [{c1, _code2} | rest]], currencies) do
remove_duplicates([{c1, code1} | rest], currencies)
end
# Not a duplicate, process the rest of the list
defp remove_duplicates([{c1, code1} | rest], currencies) do
[{c1, code1} | remove_duplicates(rest, currencies)]
end
@doc false
def invert_currency_strings(currency_strings) do
Enum.reduce(currency_strings, [], fn {code, strings}, acc ->
[Enum.map(strings, fn string -> {string, code} end) | acc]
end)
|> List.flatten()
end
defp currency_already_defined_error(code) do
"Currency #{inspect(code)} is already defined."
end
defp currency_not_saved_error(code) do
"""
The currency #{inspect(code)} could not be defined.
This is probably because the table is not defined
in which the new currency information is saved.
Please ensure you have the `Cldr.Currency` supervisor
defined as a child in your application supervisor tree.
"""
end
# TODO remove for CLDR 3.0
if Code.ensure_loaded?(Cldr) && function_exported?(Cldr, :default_backend!, 0) do
defp default_backend() do
Cldr.default_backend!()
end
else
defp default_backend() do
Cldr.default_backend()
end
end
defimpl Cldr.DisplayName do
def display_name(currency, options) do
{:ok, display_name} = Cldr.Currency.display_name(currency, options)
display_name
end
end
end