HiveBrain v1.2.0
Get Started
← Back to all entries
snippetjavascriptTip

Modeling money, currencies & exchange rates using JavaScript

Submitted by: @import:30-seconds-of-code··
0
Viewed 0 times
moneyjavascriptmodelingratesexchangecurrenciesusing

Problem

Working with money, currencies and exchange rates is pretty common, yet it's no easy task no matter the language. While JavaScript doesn't have a lot of useful features built into it, Intl can give us a head start with some parts of the process.
The first step in modeling any sort of monetary value is to have a structure for currency. Luckily, Intl has this problem solved for us. You can use Intl.NumberFormat with style: 'currency' to get a formatter for a specific currency. This formatter can then be used to format a number into a currency string.
> [!NOTE]
>
> We've previously covered formatting a number into a currency string, which might cover some simpler use cases.

Solution

const isoCodes = Intl.supportedValuesOf('currency');

const currencyFields = ['symbol', 'narrowSymbol', 'name'];

const allCurrencies = new Map(
  isoCodes.map(code => {
    const format = currencyDisplay => value =>
      Intl.NumberFormat(undefined, {
        style: 'currency',
        currency: code,
        currencyDisplay,
      })
        .format(value)
        .trim();
    return [code, Object.freeze({ code, format })];
  })
);
// Returns a Map object with all currency information
// {
//   'USD': { code: 'USD', format: [Function] },
//   'EUR': { code: 'EUR', format: [Function] },
//   ...
// }


> [!NOTE]
>
> We've previously covered formatting a number into a currency string, which might cover some simpler use cases.
In order to retrieve all supported currencies, we can use Intl.supportedValuesOf() with 'currency' as the argument. This will return an array of the ISO 4217 currency codes supported by the environment. Then, using Map(), Array.prototype.map() and Intl.NumberFormat, we can create an object for all currencies, that will format values on demand.
Notice how Object.freeze() is used to prevent the object from being modified. This is important because we don't want to accidentally change the currency information.
Having set up all the currency information, we need a way to retrieve it when we need it. Getting the same currency object for the same currency code will be important later down the line for comparisons. As we have a Map object, we can use Map.prototype.get() to retrieve the currency object. As a safeguard, we should ensure the currency code matches the key, using String.prototype.toUpperCase().

Code Snippets

const isoCodes = Intl.supportedValuesOf('currency');

const currencyFields = ['symbol', 'narrowSymbol', 'name'];

const allCurrencies = new Map(
  isoCodes.map(code => {
    const format = currencyDisplay => value =>
      Intl.NumberFormat(undefined, {
        style: 'currency',
        currency: code,
        currencyDisplay,
      })
        .format(value)
        .trim();
    return [code, Object.freeze({ code, format })];
  })
);
// Returns a Map object with all currency information
// {
//   'USD': { code: 'USD', format: [Function] },
//   'EUR': { code: 'EUR', format: [Function] },
//   ...
// }
const getCurrencyFromCode = code => {
  const isoCode = code.toUpperCase();
  return allCurrencies.get(isoCode);
};

const currency = getCurrencyFromCode('usd');
currency.format('symbol')(1000); // '$1,000.00'
class Currency {
  static get(code) {
    const currency = getCurrencyFromCode(code);
    if (!currency)
      throw new RangeError(`Invalid currency ISO code "${code}"`);
    return currency;
  }

  static wrap(currency) {
    if (
      typeof currency === 'object' &&
      getCurrencyFromCode(currency.code) === currency
    )
      return currency;

    return Currency.get(currency);
  }
}

const usd = Currency.get('usd');
usd.format('symbol')(1000); // '$1,000.00'

const usd2 = Currency.wrap(usd);
usd === usd2; // true

Context

From 30-seconds-of-code: modeling-money-currency-exchange-rates

Revisions (0)

No revisions yet.