Search code examples
javascriptnode.jsinternationalizationcurrencyecmascript-next

Get List of Supported Currencies


Other than just guessing (like I've done below), is there a more direct and efficient way of reflectively retrieving a list of all currencies supported by your JavaScript environment?

function getSupportedCurrencies() {
  function $(amount, currency) {
    let locale = 'en-US';
    let options = {
      style: 'currency',
      currency: currency,
      currencyDisplay: "name"
    };
    return Intl.NumberFormat(locale, options).format(amount);
  }
  const getAllPossibleThreeLetterWords = () => {
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    const arr = [];
    let text = '';
    for (let i = 0; i < chars.length; i++) {
      for (let x = 0; x < chars.length; x++) {
        for (let j = 0; j < chars.length; j++) {
          text += chars[i];
          text += chars[x];
          text += chars[j];
          arr.push(text);
          text = '';
        }
      }
    }
    return arr;
  };
  let ary = getAllPossibleThreeLetterWords();
  let currencies = [];
  const rx = /(?<= ).+/; // This line doesn't work in Firefox versions older than version 78 due to bug 1225665: https://bugzilla.mozilla.org/show_bug.cgi?id=1225665
  ary.forEach((cur) => {
    let output = $(0, cur).trim();
    if (output.replace(/^[^ ]+ /, '') !== cur) {
      let obj = {};
      obj.code = cur;
      obj.name = output.match(rx)[0];
      currencies.push(obj);
    }
  });
  return currencies;
}
console.log(getSupportedCurrencies());

Update:

The JavaScript specification has been enhanced with Intl.supportedValuesOf("currency"), offering a more efficient method to retrieve a list of supported currencies directly from your environment. This addition significantly simplifies the approach required to list currencies, as demonstrated in the updated code snippet below.

function getSupportedCurrencies() {
    function $(amount, currency) {
        let locale = 'en-US';
        let options = {
            style: 'currency',
            currency: currency,
            currencyDisplay: "name"
        };
        return Intl.NumberFormat(locale, options).format(amount);
    }
    let currencies = [];
    const supportedCurrencies = Intl.supportedValuesOf('currency');
    const rx = /(?<= ).+/;
    supportedCurrencies.forEach((cur) => {
        let output = $(0, cur).trim();
        let obj = {};
        obj.code = cur;
        obj.name = output.match(rx)[0];
        currencies.push(obj);
    });
    return currencies;
}
console.log(getSupportedCurrencies());

This updated method eliminates the need for "brute force guessing" of currency codes, and it performs significantly faster.


Solution

  • You could load a known list via this XML:

    https://www.currency-iso.org/dam/downloads/lists/list_one.xml

    The list was found here: https://www.currency-iso.org/en/home/tables/table-a1.html

    <ISO_4217 Pblshd="2018-08-29">
      <CcyTbl>
        <CcyNtry>
          <CtryNm>
            UNITED KINGDOM OF GREAT BRITAIN AND NORTHERN IRELAND (THE)
          </CtryNm>
          <CcyNm>Pound Sterling</CcyNm>
          <Ccy>GBP</Ccy>
          <CcyNbr>826</CcyNbr>
          <CcyMnrUnts>2</CcyMnrUnts>
        </CcyNtry>
        <CcyNtry>
          <CtryNm>UNITED STATES OF AMERICA (THE)</CtryNm>
          <CcyNm>US Dollar</CcyNm>
          <Ccy>USD</Ccy>
          <CcyNbr>840</CcyNbr>
          <CcyMnrUnts>2</CcyMnrUnts>
        </CcyNtry>
      </CcyTbl>
    </ISO_4217>
    

    var xmlString = getSampleCurrencyXml();
    var xmlData = (new window.DOMParser()).parseFromString(xmlString, "text/xml");
    var knownCodes = [].slice.call(xmlData.querySelectorAll('Ccy')).map(n => n.textContent)
    
    // Fetch the XML instead?
    fetch('https://www.currency-iso.org/dam/downloads/lists/list_one.xml', { cache: 'default' })
      .then(response => response.text())
      .then(xmlStr => (new window.DOMParser()).parseFromString(xmlStr, "text/xml"))
      .then(data => knownCodes = data); // This may not work in the Stack Snippet
    
    console.log(getSupportedCurrencies().map(c => c.code + '\t' + c.name).join('\n'));
    
    function getSupportedCurrencies() {
      function $(amount, currency) {
        return Intl.NumberFormat('en-US', {
          style: 'currency',
          currency: currency,
          currencyDisplay: 'name'
        }).format(amount);
      }
      return knownCodes.reduce((currencies, cur) => {
        return (output => {
          return output.replace(/^[^ ]+ /, '') !== cur ?
            currencies.concat({
              code: cur,
              name: output.match(/(?<= ).+/)[0]
            }) :
            currencies;
        })($(0, cur).trim());
      }, []);
    }
    
    function getSampleCurrencyXml() {
      return `
        <ISO_4217 Pblshd="2018-08-29">
          <CcyTbl>
            <CcyNtry>
              <CtryNm>
                UNITED KINGDOM OF GREAT BRITAIN AND NORTHERN IRELAND (THE)
              </CtryNm>
              <CcyNm>Pound Sterling</CcyNm>
              <Ccy>GBP</Ccy>
              <CcyNbr>826</CcyNbr>
              <CcyMnrUnts>2</CcyMnrUnts>
            </CcyNtry>
            <CcyNtry>
              <CtryNm>UNITED STATES OF AMERICA (THE)</CtryNm>
              <CcyNm>US Dollar</CcyNm>
              <Ccy>USD</Ccy>
              <CcyNbr>840</CcyNbr>
              <CcyMnrUnts>2</CcyMnrUnts>
            </CcyNtry>
          </CcyTbl>
        </ISO_4217>
      `;
    }
    .as-console-wrapper { top: 0; max-height: 100% !important; }


    If you want to generate the codes still, you can use a product iterable.

    The following is based on Python's itertools.product function.

    let ary = product('ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''), 3).map(a => a.join(''));
    
    function product(iterables, repeat) {
      var argv = Array.prototype.slice.call(arguments), argc = argv.length;
      if (argc === 2 && !isNaN(argv[argc - 1])) {
        var copies = [];
        for (var i = 0; i < argv[argc - 1]; i++) { copies.push(argv[0].slice()); }
        argv = copies;
      }
      return argv.reduce((accumulator, value) => {
        var tmp = [];
        accumulator.forEach(a0 => value.forEach(a1 => tmp.push(a0.concat(a1))));
        return tmp;
      }, [[]]);
    }
    

    Demo

    console.log(getSupportedCurrencies().map(c => c.code + '\t' + c.name).join('\n'));
    
    function getSupportedCurrencies() {
      function $(amount, currency) {
        return Intl.NumberFormat('en-US', {
          style: 'currency',
          currency: currency,
          currencyDisplay: 'name'
        }).format(amount);
      }
      let ary = product('ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''), 3).map(a => a.join(''));
      return ary.reduce((currencies, cur) => {
        return (output => {
          return output.replace(/^[^ ]+ /, '') !== cur
            ? currencies.concat({ code : cur, name : output.match(/(?<= ).+/)[0] })
            : currencies;
        })($(0, cur).trim());
      }, []);
    }
    
    function product(iterables, repeat) {
      var argv = Array.prototype.slice.call(arguments), argc = argv.length;
      if (argc === 2 && !isNaN(argv[argc - 1])) {
        var copies = [];
        for (var i = 0; i < argv[argc - 1]; i++) { copies.push(argv[0].slice()); }
        argv = copies;
      }
      return argv.reduce((accumulator, value) => {
        var tmp = [];
        accumulator.forEach(a0 => value.forEach(a1 => tmp.push(a0.concat(a1))));
        return tmp;
      }, [[]]);
    }
    .as-console-wrapper { top: 0; max-height: 100% !important; }