Bug 1342647 - Remove redundant IntlCurrency system and use ICU, r?Waldo draft
authorDaniel Ehrenberg <littledan@igalia.com>
Sat, 25 Feb 2017 01:08:14 +0100
changeset 489656 bf0522bd932d52427fa429cccc17d4860bf1c312
parent 482430 4ec373fafebf79846cd5fde0561ac02fa0bb9647
child 490391 a0aa14e5439de6b6acaf7d5d9ff0641730b8d231
push id46872
push userbmo:littledan@chromium.org
push dateSat, 25 Feb 2017 13:02:03 +0000
reviewersWaldo
bugs1342647
milestone54.0a1
Bug 1342647 - Remove redundant IntlCurrency system and use ICU, r?Waldo Intl.NumberFormat refers to a CurrencyDigits() algorithm which finds the default number of digits to format a currency in. SpiderMonkey implemented this by a table off to the side, based on information scraped from a website. However, ICU has this information already, accessible by the ucurr_getDefaultFractionDigits function. This patch calls out to that function. MozReview-Commit-ID: GU3VZWVNVgr
js/src/builtin/Intl.cpp
js/src/builtin/Intl.h
js/src/builtin/Intl.js
js/src/builtin/IntlCurrency.js
js/src/builtin/make_intl_data.py
js/src/moz.build
js/src/vm/SelfHosting.cpp
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -1363,16 +1363,41 @@ js::intl_CompareStrings(JSContext* cx, u
     }
 
     // Use the UCollator to actually compare the strings.
     RootedString str1(cx, args[1].toString());
     RootedString str2(cx, args[2].toString());
     return intl_CompareStrings(cx, coll, str1, str2, args.rval());
 }
 
+bool
+js::intl_CurrencyDigits(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 1);
+    MOZ_ASSERT(args[0].isString());
+
+    RootedString str(cx, args[0].toString());
+    AutoStableStringChars stableChars(cx);
+    if (!stableChars.initTwoByte(cx, str))
+        return false;
+    mozilla::Range<const char16_t> chars = stableChars.twoByteRange();
+    // This might not be null-terminated, but ICU only ever looks
+    // at the first three UChars, since an ISO currency code always has a
+    // length of three
+    UErrorCode status = U_ZERO_ERROR;
+    int32_t digits = ucurr_getDefaultFractionDigits(Char16ToUChar(chars.begin().get()), &status);
+    if (U_FAILURE(status)) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        return false;
+    }
+    args.rval().setInt32(digits);
+    return true;
+}
+
 
 /******************** NumberFormat ********************/
 
 const ClassOps NumberFormatObject::classOps_ = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
--- a/js/src/builtin/Intl.h
+++ b/js/src/builtin/Intl.h
@@ -241,16 +241,26 @@ intl_availableCollations(JSContext* cx, 
  *
  * Spec: ECMAScript Internationalization API Specification, 10.3.2.
  *
  * Usage: result = intl_CompareStrings(collator, x, y)
  */
 extern MOZ_MUST_USE bool
 intl_CompareStrings(JSContext* cx, unsigned argc, Value* vp);
 
+/**
+ * Finds the default number of decimal digits for a currency code
+ *
+ * Spec: https://tc39.github.io/ecma402/#sec-currencydigits
+ *
+ * Usage: result = intl_CurrencyDigits(currency)
+ */
+extern MOZ_MUST_USE bool
+intl_CurrencyDigits(JSContext* cx, unsigned argc, Value* vp);
+
 
 /******************** NumberFormat ********************/
 
 class NumberFormatObject : public NativeObject
 {
   public:
     static const Class class_;
 
--- a/js/src/builtin/Intl.js
+++ b/js/src/builtin/Intl.js
@@ -9,16 +9,17 @@
          JSMSG_INVALID_OPTION_VALUE: false, JSMSG_INVALID_DIGITS_VALUE: false,
          JSMSG_INVALID_CURRENCY_CODE: false,
          JSMSG_UNDEFINED_CURRENCY: false, JSMSG_INVALID_TIME_ZONE: false,
          JSMSG_DATE_NOT_FINITE: false, JSMSG_INVALID_KEYS_TYPE: false,
          JSMSG_INVALID_KEY: false,
          intl_Collator_availableLocales: false,
          intl_availableCollations: false,
          intl_CompareStrings: false,
+         intl_CurrencyDigits: false,
          intl_NumberFormat_availableLocales: false,
          intl_numberingSystem: false,
          intl_FormatNumber: false,
          intl_DateTimeFormat_availableLocales: false,
          intl_availableCalendars: false,
          intl_patternForSkeleton: false,
          intl_FormatDateTime: false,
          intl_SelectPluralRule: false,
@@ -2008,19 +2009,17 @@ function InitializeNumberFormat(numberFo
 function getCurrencyDigitsRE() {
     return internalIntlRegExps.currencyDigitsRE ||
            (internalIntlRegExps.currencyDigitsRE = RegExpCreate("^[A-Z]{3}$"));
 }
 function CurrencyDigits(currency) {
     assert(typeof currency === "string", "CurrencyDigits");
     assert(regexp_test_no_statics(getCurrencyDigitsRE(), currency), "CurrencyDigits");
 
-    if (callFunction(std_Object_hasOwnProperty, currencyDigits, currency))
-        return currencyDigits[currency];
-    return 2;
+    return intl_CurrencyDigits(currency);
 }
 
 
 /**
  * Returns the subset of the given locale list for which this locale list has a
  * matching (possibly fallback) locale. Locales appear in the same order in the
  * returned list as in the input list.
  *
deleted file mode 100644
--- a/js/src/builtin/IntlCurrency.js
+++ /dev/null
@@ -1,76 +0,0 @@
-// Generated by make_intl_data.py. DO NOT EDIT.
-// Version: 2017-01-01
-
-/**
- * Mapping from currency codes to the number of decimal digits used for them.
- * Default is 2 digits.
- *
- * Spec: ISO 4217 Currency and Funds Code List.
- * http://www.currency-iso.org/en/home/tables/table-a1.html
- */
-var currencyDigits = {
-    // Bahraini Dinar (BAHRAIN)
-    BHD: 3,
-    // Burundi Franc (BURUNDI)
-    BIF: 0,
-    // Unidad de Fomento (CHILE)
-    CLF: 4,
-    // Chilean Peso (CHILE)
-    CLP: 0,
-    // Djibouti Franc (DJIBOUTI)
-    DJF: 0,
-    // Guinea Franc (GUINEA)
-    GNF: 0,
-    // Iraqi Dinar (IRAQ)
-    IQD: 3,
-    // Iceland Krona (ICELAND)
-    ISK: 0,
-    // Jordanian Dinar (JORDAN)
-    JOD: 3,
-    // Yen (JAPAN)
-    JPY: 0,
-    // Comoro Franc (COMOROS (THE))
-    KMF: 0,
-    // Won (KOREA (THE REPUBLIC OF))
-    KRW: 0,
-    // Kuwaiti Dinar (KUWAIT)
-    KWD: 3,
-    // Libyan Dinar (LIBYA)
-    LYD: 3,
-    // Rial Omani (OMAN)
-    OMR: 3,
-    // Guarani (PARAGUAY)
-    PYG: 0,
-    // Rwanda Franc (RWANDA)
-    RWF: 0,
-    // Tunisian Dinar (TUNISIA)
-    TND: 3,
-    // Uganda Shilling (UGANDA)
-    UGX: 0,
-    // Uruguay Peso en Unidades Indexadas (URUIURUI) (URUGUAY)
-    UYI: 0,
-    // Dong (VIET NAM)
-    VND: 0,
-    // Vatu (VANUATU)
-    VUV: 0,
-    // CFA Franc BEAC (CAMEROON)
-    // CFA Franc BEAC (CENTRAL AFRICAN REPUBLIC (THE))
-    // CFA Franc BEAC (CHAD)
-    // CFA Franc BEAC (CONGO (THE))
-    // CFA Franc BEAC (EQUATORIAL GUINEA)
-    // CFA Franc BEAC (GABON)
-    XAF: 0,
-    // CFA Franc BCEAO (BENIN)
-    // CFA Franc BCEAO (BURKINA FASO)
-    // CFA Franc BCEAO (CÔTE D'IVOIRE)
-    // CFA Franc BCEAO (GUINEA-BISSAU)
-    // CFA Franc BCEAO (MALI)
-    // CFA Franc BCEAO (NIGER (THE))
-    // CFA Franc BCEAO (SENEGAL)
-    // CFA Franc BCEAO (TOGO)
-    XOF: 0,
-    // CFP Franc (FRENCH POLYNESIA)
-    // CFP Franc (NEW CALEDONIA)
-    // CFP Franc (WALLIS AND FUTUNA)
-    XPF: 0,
-};
--- a/js/src/builtin/make_intl_data.py
+++ b/js/src/builtin/make_intl_data.py
@@ -964,57 +964,16 @@ def writeCurrencyFile(published, currenc
  */""")
         println(u"var currencyDigits = {")
         for (currency, entries) in groupby(sorted(currencies, key=itemgetter(0)), itemgetter(0)):
             for (_, minorUnits, currencyName, countryName) in entries:
                 println(u"    // {} ({})".format(currencyName, countryName))
             println(u"    {}: {},".format(currency, minorUnits))
         println(u"};")
 
-def updateCurrency(topsrcdir, args):
-    """ Update the IntlCurrency.js file. """
-    import xml.etree.ElementTree as ET
-    from random import randint
-
-    url = args.url
-    out = args.out
-    filename = args.file
-
-    print("Arguments:")
-    print("\tDownload url: %s" % url)
-    print("\tLocal currency file: %s" % filename)
-    print("\tOutput file: %s" % out)
-    print("")
-
-    def updateFrom(currencyFile):
-        print("Processing currency code list file...")
-        tree = ET.parse(currencyFile)
-        published = tree.getroot().attrib["Pblshd"]
-        currencies = readCurrencyFile(tree)
-
-        print("Writing IntlCurrency file...")
-        writeCurrencyFile(published, currencies, out)
-
-    if filename is not None:
-        print("Always make sure you have the newest currency code list file!")
-        updateFrom(filename)
-    else:
-        print("Downloading currency & funds code list...")
-        request = urllib2.Request(url)
-        # Fake a random user agent string to circumvent the bot detection from
-        # currency-iso.org...
-        request.add_header("User-agent", "Mozilla/5.0 (Mobile; rv:{0}.0) Gecko/{0}.0 Firefox/{0}.0".format(randint(1, 999)))
-        with closing(urllib2.urlopen(request)) as currencyFile:
-            fname = urlparse.urlsplit(currencyFile.geturl()).path.split("/")[-1]
-            with tempfile.NamedTemporaryFile(suffix=fname) as currencyTmpFile:
-                print("File stored in %s" % currencyTmpFile.name)
-                currencyTmpFile.write(currencyFile.read())
-                currencyTmpFile.flush()
-                updateFrom(currencyTmpFile.name)
-
 if __name__ == "__main__":
     import argparse
 
     # This script must reside in js/src/builtin to work correctly.
     (thisDir, thisFile) = os.path.split(os.path.abspath(sys.argv[0]))
     dirPaths = os.path.normpath(thisDir).split(os.sep)
     if "/".join(dirPaths[-3:]) != "js/src/builtin":
         raise RuntimeError("%s must reside in js/src/builtin" % sys.argv[0])
@@ -1070,12 +1029,11 @@ if __name__ == "__main__":
                                  help="Download url for the currency & funds code list (default: "
                                       "%(default)s)")
     parser_currency.add_argument("--out",
                                  default="IntlCurrency.js",
                                  help="Output file (default: %(default)s)")
     parser_currency.add_argument("file",
                                  nargs="?",
                                  help="Local currency code list file, if omitted uses <URL>")
-    parser_currency.set_defaults(func=partial(updateCurrency, topsrcdir))
 
     args = parser.parse_args()
     args.func(args)
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -762,17 +762,16 @@ selfhosted.inputs = [
     'builtin/Utilities.js',
     'builtin/Array.js',
     'builtin/Classes.js',
     'builtin/Date.js',
     'builtin/Error.js',
     'builtin/Function.js',
     'builtin/Generator.js',
     'builtin/Intl.js',
-    'builtin/IntlCurrency.js',
     'builtin/IntlData.js',
     'builtin/Iterator.js',
     'builtin/Map.js',
     'builtin/Module.js',
     'builtin/Number.js',
     'builtin/Object.js',
     'builtin/Promise.js',
     'builtin/Reflect.js',
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2603,16 +2603,17 @@ static const JSFunctionSpec intrinsic_fu
 
     // See builtin/Intl.h for descriptions of the intl_* functions.
     JS_FN("intl_availableCalendars", intl_availableCalendars, 1,0),
     JS_FN("intl_availableCollations", intl_availableCollations, 1,0),
     JS_FN("intl_canonicalizeTimeZone", intl_canonicalizeTimeZone, 1,0),
     JS_FN("intl_Collator", intl_Collator, 2,0),
     JS_FN("intl_Collator_availableLocales", intl_Collator_availableLocales, 0,0),
     JS_FN("intl_CompareStrings", intl_CompareStrings, 3,0),
+    JS_FN("intl_CurrencyDigits", intl_CurrencyDigits, 1,0),
     JS_FN("intl_DateTimeFormat", intl_DateTimeFormat, 2,0),
     JS_FN("intl_DateTimeFormat_availableLocales", intl_DateTimeFormat_availableLocales, 0,0),
     JS_FN("intl_defaultTimeZone", intl_defaultTimeZone, 0,0),
     JS_FN("intl_defaultTimeZoneOffset", intl_defaultTimeZoneOffset, 0,0),
     JS_FN("intl_FormatDateTime", intl_FormatDateTime, 2,0),
     JS_FN("intl_FormatNumber", intl_FormatNumber, 2,0),
     JS_FN("intl_GetCalendarInfo", intl_GetCalendarInfo, 1,0),
     JS_FN("intl_ComputeDisplayNames", intl_ComputeDisplayNames, 3,0),