Functions for parsing numbers and currencies from strings in a locale-aware manner.
The parser handles locale-specific digit transliteration, grouping separators, and decimal separators to convert localized number strings back into Elixir numeric values.
Parsing reads with a locale's own data or not at all. When the
locale is not installed and :allow_runtime_locale_download is
false, formatting falls back to a parent locale's data or to
English, but every function here returns
{:error, %Localize.LocaleNotFoundInCacheError{}}, since with
English data en-AU "$5" would be US dollars. Install the locale
with mix localize.download_locales or allow it to be downloaded.
Summary
Functions
Finds and replaces substrings from a map at the beginning and/or end of a string.
Parses a string in a locale-aware manner and returns a number.
Parses a string that holds one currency amount into its currency and its amount.
Maps a list applying a resolver function to each binary element.
Resolves currencies from strings within a list.
Resolves a currency from the beginning and/or end of a string.
Resolves percent or permille from a string.
Resolves percent and permille symbols from strings within a list.
Scans a string in a locale-aware manner and returns a list of strings and numbers.
Types
Functions
@spec find_and_replace(map(), String.t(), float() | nil) :: {:ok, list()} | {:error, Exception.t()}
Finds and replaces substrings from a map at the beginning and/or end of a string.
Arguments
string_mapis a map of%{search_string => replacement}.stringis the string to search.fuzzyis an optional float for fuzzy matching. Of equally close strings, the alphabetically first is taken.
Returns
{:ok, list}with replacements applied.{:error, exception}if no match found.
Examples
iex> Localize.Number.Parser.find_and_replace(%{"usd" => :USD}, "USD 100")
{:ok, [:USD, " 100"]}
iex> Localize.Number.Parser.find_and_replace(%{"%" => :percent}, "11%")
{:ok, ["11", :percent]}
@spec parse(String.t(), Keyword.t()) :: {:ok, integer() | float() | Decimal.t()} | {:error, Exception.t()}
Parses a string in a locale-aware manner and returns a number.
Arguments
stringis any string. An underscore between two digits is Elixir's digit separator and is ignored, as in"1_000_000.34"; anywhere else it makes the text no number.optionsis a keyword list of options.
Options
:numberis one of:integer,:float,:decimal, ornil. The default isnil(auto-detect).:localeis a locale identifier. The default is:en.:number_systemis a number system name or type.:lenientgoverns how strictly grouping separators must be positioned.true, the default, requires each group to be at least two digits, which is what keeps"3 4 5"three numbers rather than one.falserequires each group to be exactly the locale's grouping size, sofrtakes"1 234 567"and refuses"1 23".:groupingset to:sizeschecks each group against the sizes the locale writes rather than as:lenientsays: the last group of the locale's grouping size, the others of that size or its secondary one, soderefuses"1.23", which a lenient reading takes as 123, anden-INtakes both"12,34,567"and"1,234,567". Suited to amounts of money, where a misread group is a wrong amount. The default isnil.:currencyis a currency code or aLocalize.Currency.t/0. When set, the string is read as an amount of that currency, with the separatorsLocalize.Number.to_string/2formats it with. Two locales separate a currency amount differently from a plain number,de-AT, which formats€ 1.234,56but writes1 234,56plain, andfr-CH, which writes a franc amount1'234.56but a plain number1'234,56. There the string is read with both, and refused should both readings be valid and give different numbers.de-ATgroups the euro with a period, so"1.234"is 1234 euros. The default isnil, a plain number.:root_separatorsset totruealso reads the string with the root locale's separators, the locale-neutral1,234.56, for an amount of something CLDR has no data for, such as a digital token, which is commonly written that way. A string both read, as different numbers, is refused:de"1.234"is 1234 or 1.234. The default isfalse.:separatorsnames the set of decimal and grouping separators to read with, for a locale that has more than one.en-ZAwrites1 234,56with its:standardset and1,234.56with its:usset. A locale without the named set reads with its standard one. The default is:standard.
Returns
{:ok, number}on success, or{:error, %Localize.LocaleNotFoundInCacheError{}}when the locale's data is not installed (see the module documentation), or{:error, exception}if parsing fails.
Examples
iex> Localize.Number.Parser.parse("-1_000_000.34")
{:ok, -1000000.34}
@spec parse_currency_amount(String.t(), Keyword.t()) :: {:ok, {Localize.Currency.currency_code() | term(), Decimal.t()}} | {:error, Exception.t()}
Parses a string that holds one currency amount into its currency and its amount.
The currency, a code, symbol or name, may come before or after the
amount, with or without a space between them. A sign may stand
before the currency, between the currency and the amount, or after
the amount, or the parentheses of an accounting format may hold it,
but not both. The amount is read with the separators the locale uses
for that currency, so fr-CH "1’234.56 CHF" reads as it is written,
and in de-AT and fr-CH, which write plain numbers otherwise, with
those too. Its groups must be the sizes the locale writes
(grouping: :sizes of parse/2), and a period that is none of its
separators is read only where it can be nothing but a decimal point.
Where the text could be two amounts it is
refused rather than one guessed: de "1.23 EUR" is not €123, fr
"1.234 €" is not €1.234, "($-5)" is neither -5 nor 5 dollars,
"-$.50" is refused since Swiss "Fr. –.15" writes a dash for the zero,
and fr "$2.00" is refused since several dollars share "$" there,
where resolve_currency/2 takes the Canadian one. A narrow symbol of
another currency than the locale's own is read only when it is a
currency sign that names that currency alone, in the locale and in
CLDR's root data, so en-AU "€5" is the euro and de-CH "€ 15.40"
too, though de-CH writes the euro "EUR", but Swiss "15 Rp." (15
Rappen) is not the rupiah, whose narrow symbol de-CH gives as "Rp",
and bg "₩5" is refused, since root gives "₩" to both wons.
A historic currency is read only by its code or its name, since its
symbols are reused: ru "6 300 Р" is not the ruble of 1991–1998. One
to three digits, a decimal mark and three more are refused in a
currency of fewer than three decimal places, since they may be a
thousands group: de "1,599 €" and en "$59.568". The currency text,
but not the number, is matched after NFKC normalization, so `ja`
"¥1.00" is the yen, which `ja` writes "¥", while "$5²" is refused.
Bidi controls are ignored. Nothing else may surround the amount.
ex_money's Money.parse/2 is built on this function, and its
parsing guide describes
what is read and what is refused, with examples.
Arguments
stringis the string to parse.optionsis a keyword list of options.
Options
:localeis a locale identifier. The default isLocalize.get_locale/0.:number_systemand:separatorsare as forparse/2.:only,:exceptand:fuzzyare as forresolve_currency/2, except that a fuzzy match is refused where another currency's string is as close:fr"100 dollars" is neither the Fiji nor the Rhodesian dollar.:default_currencyis the currency of an amount that names none: a currency code, or a name or symbol, resolved as currency text in the string would be. Without it, such an amount is an error.:caller_currencyis a function of one argument, called with the currency text, for currencies of the caller's own such as digital tokens. It returns{:ok, currency}when the text names one, or anything else. Text that names no currency of the locale is read as the caller's currency; text that names one of the locale's and a different one of the caller's is ambiguous and refused, as₮is the tugrik and a digital token's symbol, except where it names the locale's own currency:mn"₮5" is the tugrik. Pass it only when the caller has such currencies, since it is called for every amount. Their amounts are read with the locale's separators and the root locale's (root_separators: trueofparse/2), and refused where the two differ. A caller's currency text may have digits in it, as a digital token's identifier "Q9PB7XDNF" does. It is then the first word of the amount or the last, which a space separates from the number, and it is read only where the amount has no other reading:fr"USD1 234" is also 1 234 US dollars, so it is neither.
Returns
{:ok, {currency, amount}}wherecurrencyis a currency code, or what:caller_currencyreturned, andamountis aDecimal.t/0, or{:error, %Localize.UnknownCurrencyError{currency: text}}when the currency text names no currency, or{:error, %Localize.ParseError{reason: :missing_currency}}when the string names no currency and there is no:default_currency, or{:error, %Localize.InvalidAmountError{currency: currency, amount: text}}when the string names a currency but has no number, or one that cannot be read with the locale's separators for it, or{:error, %Localize.ParseError{reason: :unexpected_input}}when the string is not one currency amount, or{:error, %Localize.LocaleNotFoundInCacheError{}}when the locale's data is not installed (see the module documentation), or{:error, exception}for an invalid locale or option.
Examples
iex> Localize.Number.Parser.parse_currency_amount("USD 100", locale: :en)
{:ok, {:USD, Decimal.new("100")}}
iex> Localize.Number.Parser.parse_currency_amount("(1 234,56 €)", locale: :fr)
{:ok, {:EUR, Decimal.new("-1234.56")}}
iex> Localize.Number.Parser.parse_currency_amount("kr-127,54", locale: :da)
{:ok, {:DKK, Decimal.new("-127.54")}}
iex> token = fn "Q9PB7XDNF" -> {:ok, "Q9PB7XDNF"}; _other -> :error end
iex> Localize.Number.Parser.parse_currency_amount("Q9PB7XDNF 5", locale: :en, caller_currency: token)
{:ok, {"Q9PB7XDNF", Decimal.new("5")}}
@spec resolve(list(), (String.t(), Keyword.t() -> term()), Keyword.t()) :: list() | {:error, Exception.t()}
Maps a list applying a resolver function to each binary element.
Arguments
listis a list of terms.resolveris a function that takes a string and options.optionsis a keyword list passed to the resolver.
Options
- The options are passed through unchanged to
resolverfor each binary element. See the resolver function (for exampleresolve_currency/2orresolve_per/2) for the options it supports.
Returns
- The list with binary elements resolved.
Examples
iex> resolver = &Localize.Number.Parser.resolve_currency/2
iex> Localize.Number.Parser.resolve(["100", "USD"], resolver, locale: :en)
["100", :USD]
@spec resolve_currencies([String.t() | number()], Keyword.t()) :: [atom() | String.t() | number()] | {:error, Exception.t()}
Resolves currencies from strings within a list.
Arguments
listis a list of strings and numbers.optionsis a keyword list of options.
Options
:localeis a locale identifier. The default is:en.:onlyis a filter for currencies to include.:exceptis a filter for currencies to exclude.:fuzzyis a float for fuzzy matching viaString.jaro_distance/2. Of equally close currency strings, the alphabetically first is taken.
Returns
- A list with currency strings replaced by currency code atoms.
Examples
iex> Localize.Number.Parser.scan("100 US dollars")
...> |> Localize.Number.Parser.resolve_currencies()
[100, :USD]
@spec resolve_currency(String.t(), Keyword.t()) :: [atom() | String.t()] | {:error, Exception.t()}
Resolves a currency from the beginning and/or end of a string.
A string several of the locale's currencies share names the currency
of the territory whose CLDR locale is nearest the locale, when that
locale is a good fit and no other claimant's is as near: "$" is the
Canadian dollar in fr, as in fr-CA. Otherwise it stays unknown.
Arguments
stringis a string potentially containing a currency name or symbol.optionsis a keyword list of options.
Options
:localeis a locale identifier. The default is:en.:onlyis a filter for currencies to include.:exceptis a filter for currencies to exclude.:fuzzyis a float for fuzzy matching. Of equally close currency strings, the alphabetically first is taken.
Returns
- A list with the currency code and remainder, or
{:error, exception}.
Examples
iex> Localize.Number.Parser.resolve_currency("US dollars")
[:USD]
iex> Localize.Number.Parser.resolve_currency("$100")
[:USD, "100"]
@spec resolve_per(String.t(), Keyword.t()) :: [per() | String.t()] | {:error, Exception.t()}
Resolves percent or permille from a string.
Arguments
stringis a string potentially containing percent or permille symbols.optionsis a keyword list of options.
Options
:localeis a locale identifier atom, string, or aLocalize.LanguageTag.t/0. The default isLocalize.get_locale().:number_systemis a number system name or type atom. The default is the number system of:locale.
Returns
A list with the symbol replaced by
:percentor:permille.{:error, exception}if no symbol found.
Examples
iex> Localize.Number.Parser.resolve_per("11%")
["11", :percent]
@spec resolve_pers([String.t() | number()], Keyword.t()) :: [per() | String.t() | number()] | {:error, Exception.t()}
Resolves percent and permille symbols from strings within a list.
Arguments
listis a list of strings and numbers.optionsis a keyword list of options.
Options
:localeis a locale identifier atom, string, or aLocalize.LanguageTag.t/0. The default isLocalize.get_locale().:number_systemis a number system name or type atom. The default is the number system of:locale.
Returns
- A list with percent/permille strings replaced by
:percentor:permilleatoms.
Examples
iex> Localize.Number.Parser.scan("100%")
...> |> Localize.Number.Parser.resolve_pers()
[100, :percent]
@spec scan(String.t(), Keyword.t()) :: [String.t() | integer() | float() | Decimal.t()] | {:error, Exception.t()}
Scans a string in a locale-aware manner and returns a list of strings and numbers.
In a locale that groups with a space, a space between digits is
read as a grouping separator, which is what lets "1 234,5" be
found as one number under fr. The consequence is that adjacent
numbers separated by a space can be read as a single number when
each run is two digits or more — a list of room numbers or the
parts of a phone number among them. Pass lenient: false to
require exact grouping sizes, which reads those as separate
numbers again:
iex> Localize.Number.Parser.scan("chambres 12 14 16", locale: "fr")
["chambres ", 121416]
iex> Localize.Number.Parser.scan("chambres 12 14 16", locale: "fr", lenient: false)
["chambres ", 12, " ", 14, " ", 16]A number in the text has the shape Localize.Number.Scanner reads:
a sign before the digits and touching them, the locale's group and
decimal separators, an exponent, and leniently a zero fraction
written as a dash, "5.–". Each such run is read by parse/2 with
these options and is a number where that succeeds; otherwise it is
text, as written. An underscore between two digits is Elixir's
digit separator, so "1_000" is 1000, and anywhere else it makes
the run text, so "5_" stays "5_".
Arguments
stringis any string.optionsis a keyword list of options.
Options
:numberis one of:integer,:float,:decimal, ornil. The default isnil(auto-detect).:localeis a locale identifier. The default is:en.:number_systemis a number system name or type.:lenientgoverns how strictly grouping separators must be positioned.true, the default, requires each group to be at least two digits, which is what keeps"3 4 5"three numbers rather than one.falserequires each group to be exactly the locale's grouping size, sofrtakes"1 234 567"and refuses"1 23".:groupingset to:sizeschecks each group against the sizes the locale writes rather than as:lenientsays: the last group of the locale's grouping size, the others of that size or its secondary one, soderefuses"1.23", which a lenient reading takes as 123, anden-INtakes both"12,34,567"and"1,234,567". Suited to amounts of money, where a misread group is a wrong amount. The default isnil.:currencyis a currency code or aLocalize.Currency.t/0. When set, the string is read as an amount of that currency, with the separatorsLocalize.Number.to_string/2formats it with. Two locales separate a currency amount differently from a plain number,de-AT, which formats€ 1.234,56but writes1 234,56plain, andfr-CH, which writes a franc amount1'234.56but a plain number1'234,56. There the string is read with both, and refused should both readings be valid and give different numbers.de-ATgroups the euro with a period, so"1.234"is 1234 euros. The default isnil, a plain number.:root_separatorsset totruealso reads the string with the root locale's separators, the locale-neutral1,234.56, for an amount of something CLDR has no data for, such as a digital token, which is commonly written that way. A string both read, as different numbers, is refused:de"1.234"is 1234 or 1.234. The default isfalse.:separatorsnames the set of decimal and grouping separators to read with, for a locale that has more than one.en-ZAwrites1 234,56with its:standardset and1,234.56with its:usset. A locale without the named set reads with its standard one. The default is:standard.
Returns
- A list of strings and numbers.
Examples
iex> Localize.Number.Parser.scan("The prize is 23")
["The prize is ", 23]
iex> Localize.Number.Parser.scan("1kg")
[1, "kg"]