currency
Handles currency amounts, provides currency information and formatting.
Powered by CLDR v38, in just ~30kb of data.
Backstory: https://bojanz.github.io/price-currency-handling-go/
Features
- All currency codes, their numeric codes and fraction digits.
- Currency symbols and formats for all locales.
- Amount struct, with value semantics (Fowler's Money pattern)
- Formatter, for formatting amounts and parsing formatted amounts.
amount, _ := currency.NewAmount("275.98", "EUR")
total, _ := amount.Mul("4")
locale := currency.NewLocale("fr")
formatter := currency.NewFormatter(locale)
fmt.Println(formatter.Format(total))
total, _ = total.Convert("IRR", "45.538")
total = total.Round()
locale = currency.NewLocale("fa")
formatter = currency.NewFormatter(locale)
fmt.Println(formatter.Format(total))
Design goals
Real decimal implementation under the hood.
Currency amounts can't be floats. Storing integer minor units (2.99 => 299)
becomes problematic once there are multiple currencies (difficult to sort in the
DB), or there is a need for sub-minor-unit precision (due to merchant or tax
requirements, etc). A real arbitrary-precision decimal type is required. Since
Go doesn't have one natively, a userspace implementation is used, provided by
the cockroachdb/apd package. The Amount struct provides an easy to use
abstraction on top of it, allowing the underlying implementation to be replaced
in the future without a backwards compatibility break.
Smart filtering of CLDR data.
The "modern" subset of CLDR locales is used, reducing the list from ~560 to ~370 locales.
Once gathered, locales are filtered to remove all data not used by this package,
and then deduplicated by parent (e.g. don't keep fr-CH
if fr
has the
same data).
Currency symbols are grouped together to avoid repetition. For example:
"ARS": {
{"ARS", []string{"en", "fr-CA"}},
{"$", []string{"es-AR"}},
{"$AR", []string{"fr"}},
}
Currency names are not included because they are rarely shown, but need
significant space. Instead, they can be fetched on the frontend via Intl.DisplayNames.