summaryrefslogtreecommitdiffstats
path: root/js/src/tests/non262/Intl
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/tests/non262/Intl')
-rw-r--r--js/src/tests/non262/Intl/Array/shell.js0
-rw-r--r--js/src/tests/non262/Intl/Array/toLocaleString-date.js53
-rw-r--r--js/src/tests/non262/Intl/Array/toLocaleString-number.js34
-rw-r--r--js/src/tests/non262/Intl/Array/toLocaleString.js35
-rw-r--r--js/src/tests/non262/Intl/Collator/browser.js0
-rw-r--r--js/src/tests/non262/Intl/Collator/call.js74
-rw-r--r--js/src/tests/non262/Intl/Collator/caseFirst.js197
-rw-r--r--js/src/tests/non262/Intl/Collator/compare.js137
-rw-r--r--js/src/tests/non262/Intl/Collator/construct-newtarget.js81
-rw-r--r--js/src/tests/non262/Intl/Collator/cross-compartment.js22
-rw-r--r--js/src/tests/non262/Intl/Collator/shell.js0
-rw-r--r--js/src/tests/non262/Intl/Collator/supportedLocalesOf.js355
-rw-r--r--js/src/tests/non262/Intl/Collator/toStringTag.js29
-rw-r--r--js/src/tests/non262/Intl/Date/browser.js0
-rw-r--r--js/src/tests/non262/Intl/Date/shell.js0
-rw-r--r--js/src/tests/non262/Intl/Date/toLocaleDateString_timeZone.js77
-rw-r--r--js/src/tests/non262/Intl/Date/toLocaleString_timeZone.js77
-rw-r--r--js/src/tests/non262/Intl/Date/toLocaleTimeString_timeZone.js77
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/browser.js3
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/calendar-aliases.js35
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/calendar-option.js67
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/call.js182
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/construct-newtarget.js81
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/cross-compartment.js70
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/dateTimeStyle.js57
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/day-period-hour-cycle.js102
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/day-period-standalone.js160
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/day-period.js40
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/format.js47
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/formatRange-gregorian-proleptic.js28
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/formatRange-hour-cycle.js448
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/formatRange-matches-format-output.js58
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/formatToParts.js96
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/format_timeZone-non-meta.js62
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/format_timeZone.js104
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/fractional-second-digits-append-item.js67
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/fractional-second-digits.js43
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/hourCycle.js142
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/islamic.js89
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/japanese-gannen-year.js29
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/mozExtensions.js18
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/numberingSystem-option.js60
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/options-property-accesses.js85
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/related-year.js185
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/shell.js34
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/standalone-month.js11
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/supportedLocalesOf.js371
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/timeZone.js139
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/timeZone_backward_links.js136
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/timeZone_backzone.js115
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/timeZone_backzone_links.js38
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/timeZone_notbackward_links.js50
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/timeZone_version.js17
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/toStringTag.js29
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/tz-environment-variable.js67
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/unwrapping.js247
-rw-r--r--js/src/tests/non262/Intl/DisplayNames/browser.js0
-rw-r--r--js/src/tests/non262/Intl/DisplayNames/calendar.js18
-rw-r--r--js/src/tests/non262/Intl/DisplayNames/currency.js150
-rw-r--r--js/src/tests/non262/Intl/DisplayNames/dateTimeField.js173
-rw-r--r--js/src/tests/non262/Intl/DisplayNames/dayPeriod.js80
-rw-r--r--js/src/tests/non262/Intl/DisplayNames/language.js195
-rw-r--r--js/src/tests/non262/Intl/DisplayNames/month.js109
-rw-r--r--js/src/tests/non262/Intl/DisplayNames/quarter.js74
-rw-r--r--js/src/tests/non262/Intl/DisplayNames/region.js157
-rw-r--r--js/src/tests/non262/Intl/DisplayNames/script.js134
-rw-r--r--js/src/tests/non262/Intl/DisplayNames/shell.js6
-rw-r--r--js/src/tests/non262/Intl/DisplayNames/weekday.js77
-rw-r--r--js/src/tests/non262/Intl/ListFormat/browser.js0
-rw-r--r--js/src/tests/non262/Intl/ListFormat/conjunction-type.js112
-rw-r--r--js/src/tests/non262/Intl/ListFormat/cross-compartment.js42
-rw-r--r--js/src/tests/non262/Intl/ListFormat/disjunction-type.js108
-rw-r--r--js/src/tests/non262/Intl/ListFormat/same-compartment.js40
-rw-r--r--js/src/tests/non262/Intl/ListFormat/shell.js21
-rw-r--r--js/src/tests/non262/Intl/ListFormat/supported-locales.js18
-rw-r--r--js/src/tests/non262/Intl/ListFormat/unit-type.js149
-rw-r--r--js/src/tests/non262/Intl/Locale/apply-options-to-tag-canonicalize-twice.js11
-rw-r--r--js/src/tests/non262/Intl/Locale/browser.js0
-rw-r--r--js/src/tests/non262/Intl/Locale/coerce-options-before-validating-tag.js10
-rw-r--r--js/src/tests/non262/Intl/Locale/cross-compartment.js28
-rw-r--r--js/src/tests/non262/Intl/Locale/grandfathered.js75
-rw-r--r--js/src/tests/non262/Intl/Locale/likely-subtags-generated.js3465
-rw-r--r--js/src/tests/non262/Intl/Locale/likely-subtags.js61
-rw-r--r--js/src/tests/non262/Intl/Locale/same-compartment.js26
-rw-r--r--js/src/tests/non262/Intl/Locale/shell.js0
-rw-r--r--js/src/tests/non262/Intl/Locale/surface.js98
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/StringBuffer.js37
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/bigint-int64.js40
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/browser.js0
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/call.js182
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/construct-newtarget.js81
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/cross-compartment.js70
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/currency-narrow-symbol.js40
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/currency-sign-accounting.js192
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/duplicate-singleton-variant.js49
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/format-as-code-or-name.js75
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/format.js55
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/formatToParts.js357
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/formatting-NaN.js35
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/negativeZeroFractionDigits.js21
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/notation-compact-long.js134
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/notation-compact-short.js136
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/notation-engineering.js136
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/notation-scientific.js136
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/numberingSystem-format.js18
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/numberingSystem-option.js60
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/options-emulate-undefined.js13
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/remove-unicode-extensions.js25
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/shell.js48
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/sign-display.js187
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/significantDigitsOfZero.js38
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/supportedLocalesOf.js371
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/toStringTag.js29
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/unit-compound-combinations.js63
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/unit-formatToParts-has-unit-field.js88
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/unit-well-formed.js250
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/unit.js104
-rw-r--r--js/src/tests/non262/Intl/NumberFormat/unwrapping.js247
-rw-r--r--js/src/tests/non262/Intl/PluralRules/browser.js0
-rw-r--r--js/src/tests/non262/Intl/PluralRules/call.js53
-rw-r--r--js/src/tests/non262/Intl/PluralRules/construct-newtarget.js76
-rw-r--r--js/src/tests/non262/Intl/PluralRules/cross-compartment.js22
-rw-r--r--js/src/tests/non262/Intl/PluralRules/negativeZeroFractionDigits.js21
-rw-r--r--js/src/tests/non262/Intl/PluralRules/pluralrules.js18
-rw-r--r--js/src/tests/non262/Intl/PluralRules/resolvedOptions-overridden-species.js25
-rw-r--r--js/src/tests/non262/Intl/PluralRules/rounding.js17
-rw-r--r--js/src/tests/non262/Intl/PluralRules/select.js63
-rw-r--r--js/src/tests/non262/Intl/PluralRules/shell.js0
-rw-r--r--js/src/tests/non262/Intl/PluralRules/supportedLocalesOf.js369
-rw-r--r--js/src/tests/non262/Intl/README.txt27
-rw-r--r--js/src/tests/non262/Intl/RelativeTimeFormat/browser.js0
-rw-r--r--js/src/tests/non262/Intl/RelativeTimeFormat/construct-newtarget.js21
-rw-r--r--js/src/tests/non262/Intl/RelativeTimeFormat/cross-compartment.js22
-rw-r--r--js/src/tests/non262/Intl/RelativeTimeFormat/format.js145
-rw-r--r--js/src/tests/non262/Intl/RelativeTimeFormat/locale-fallback-handling.js15
-rw-r--r--js/src/tests/non262/Intl/RelativeTimeFormat/numbering-system.js22
-rw-r--r--js/src/tests/non262/Intl/RelativeTimeFormat/numberingSystem-option.js60
-rw-r--r--js/src/tests/non262/Intl/RelativeTimeFormat/relativetimeformat.js15
-rw-r--r--js/src/tests/non262/Intl/RelativeTimeFormat/shell.js0
-rw-r--r--js/src/tests/non262/Intl/RelativeTimeFormat/supportedLocalesOf.js373
-rw-r--r--js/src/tests/non262/Intl/String/shell.js0
-rw-r--r--js/src/tests/non262/Intl/String/toLocaleLowerCase.js64
-rw-r--r--js/src/tests/non262/Intl/String/toLocaleUpperCase.js64
-rw-r--r--js/src/tests/non262/Intl/TypedArray/shell.js0
-rw-r--r--js/src/tests/non262/Intl/TypedArray/toLocaleString.js103
-rw-r--r--js/src/tests/non262/Intl/best-available-locale-from-default-locale.js107
-rw-r--r--js/src/tests/non262/Intl/browser.js0
-rw-r--r--js/src/tests/non262/Intl/default-locale-shell.js16
-rw-r--r--js/src/tests/non262/Intl/duplicate-variants.js46
-rw-r--r--js/src/tests/non262/Intl/extensions/browser.js0
-rw-r--r--js/src/tests/non262/Intl/extensions/options-value-emulates-undefined.js29
-rw-r--r--js/src/tests/non262/Intl/extensions/shell.js0
-rw-r--r--js/src/tests/non262/Intl/extensions/unicode-extension-sequences.js74
-rw-r--r--js/src/tests/non262/Intl/fallback-symbol.js22
-rw-r--r--js/src/tests/non262/Intl/four-letter-language-codes.js22
-rw-r--r--js/src/tests/non262/Intl/getCalendarInfo.js83
-rw-r--r--js/src/tests/non262/Intl/getCanonicalLocales-overridden-arg-length.js20
-rw-r--r--js/src/tests/non262/Intl/getCanonicalLocales-overridden-push.js16
-rw-r--r--js/src/tests/non262/Intl/getCanonicalLocales-overridden-set.js16
-rw-r--r--js/src/tests/non262/Intl/getCanonicalLocales-overridden-species.js23
-rw-r--r--js/src/tests/non262/Intl/getCanonicalLocales-weird-cases.js25
-rw-r--r--js/src/tests/non262/Intl/getCanonicalLocales-with-duplicates.js15
-rw-r--r--js/src/tests/non262/Intl/getCanonicalLocales.js37
-rw-r--r--js/src/tests/non262/Intl/getDisplayNames.js246
-rw-r--r--js/src/tests/non262/Intl/getLocaleInfo.js38
-rw-r--r--js/src/tests/non262/Intl/resolved-locale-sorted-unicode-extension-keys.js109
-rw-r--r--js/src/tests/non262/Intl/shell.js328
-rw-r--r--js/src/tests/non262/Intl/tolower-ascii-equivalent.js47
-rw-r--r--js/src/tests/non262/Intl/unicode-bcp47-locale-ids-extlangs.js21
-rw-r--r--js/src/tests/non262/Intl/unicode-bcp47-locale-ids-grandfathered.js52
-rw-r--r--js/src/tests/non262/Intl/unicode-bcp47-locale-ids-language-mappings.js39
-rw-r--r--js/src/tests/non262/Intl/unicode-bcp47-locale-ids-languages-mappings-complex.js21
-rw-r--r--js/src/tests/non262/Intl/unicode-bcp47-locale-ids-region-mappings-complex.js57
-rw-r--r--js/src/tests/non262/Intl/unicode-bcp47-locale-ids-region-mappings.js22
-rw-r--r--js/src/tests/non262/Intl/unicode-bcp47-locale-ids-sign-languages.js18
-rw-r--r--js/src/tests/non262/Intl/unicode-bcp47-locale-ids-transformed-ext.js71
-rw-r--r--js/src/tests/non262/Intl/unicode-bcp47-locale-ids-unicode-ext.js12
-rw-r--r--js/src/tests/non262/Intl/unicode-bcp47-locale-ids-variants-legacy-mappings.js15
-rw-r--r--js/src/tests/non262/Intl/unicode-bcp47-locale-ids-variants-sorted.js31
-rw-r--r--js/src/tests/non262/Intl/variant-with-preferred-value.js58
180 files changed, 16945 insertions, 0 deletions
diff --git a/js/src/tests/non262/Intl/Array/shell.js b/js/src/tests/non262/Intl/Array/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Intl/Array/shell.js
diff --git a/js/src/tests/non262/Intl/Array/toLocaleString-date.js b/js/src/tests/non262/Intl/Array/toLocaleString-date.js
new file mode 100644
index 0000000000..7711c60643
--- /dev/null
+++ b/js/src/tests/non262/Intl/Array/toLocaleString-date.js
@@ -0,0 +1,53 @@
+if (typeof Intl === "object") {
+ const localeSep = [,,].toLocaleString();
+
+ const date = new Date(Date.UTC(2012, 11, 12, 3, 0, 0));
+
+ assertEq([date].toLocaleString("en-us", {timeZone: "UTC"}), "12/12/2012, 3:00:00 AM");
+ assertEq([date].toLocaleString(["de", "en"], {timeZone: "UTC"}), "12.12.2012, 03:00:00");
+ assertEq([date].toLocaleString("th-th", {timeZone: "UTC"}), "12/12/2555 03:00:00");
+ assertEq([date].toLocaleString("th-th-u-nu-thai", {timeZone: "UTC"}), "๑๒/๑๒/๒๕๕๕ ๐๓:๐๐:๐๐");
+
+ const sampleValues = [
+ date, new Date(0),
+ ];
+ const sampleLocales = [
+ void 0,
+ "en",
+ "th-th-u-nu-thai",
+ "ja-jp",
+ "ar-ma-u-ca-islamicc",
+ ["tlh", "de"],
+ ];
+ const numericFormatOptions = {
+ timeZone: "UTC",
+ year: "numeric", month: "numeric", day: "numeric",
+ hour: "numeric", minute: "numeric", second: "numeric",
+ };
+ const longFormatOptions = {
+ timeZone: "UTC",
+ year: "numeric", month: "long", day: "numeric",
+ hour: "numeric", minute: "numeric", second: "numeric"
+ };
+ const sampleOptions = [
+ {timeZone: "UTC"},
+ longFormatOptions,
+ ];
+
+ for (let locale of sampleLocales) {
+ for (let options of sampleOptions) {
+ let dtfOptions;
+ if (options === longFormatOptions) {
+ dtfOptions = longFormatOptions;
+ } else {
+ dtfOptions = numericFormatOptions;
+ }
+ let dtf = new Intl.DateTimeFormat(locale, dtfOptions);
+ let expected = sampleValues.map(dtf.format).join(localeSep);
+ assertEq(sampleValues.toLocaleString(locale, options), expected);
+ }
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/Array/toLocaleString-number.js b/js/src/tests/non262/Intl/Array/toLocaleString-number.js
new file mode 100644
index 0000000000..6ca8a17837
--- /dev/null
+++ b/js/src/tests/non262/Intl/Array/toLocaleString-number.js
@@ -0,0 +1,34 @@
+if (typeof Intl === "object") {
+ const localeSep = [,,].toLocaleString();
+
+ assertEq([NaN].toLocaleString("ar"), "ليس رقم");
+ assertEq([NaN].toLocaleString(["zh-hant", "ar"]), "非數值");
+ assertEq([Infinity].toLocaleString("dz"), "གྲངས་མེད");
+ assertEq([-Infinity].toLocaleString(["fr", "en"]), "-∞");
+
+ const sampleValues = [
+ -0, +0, -1, +1, -2, +2, -0.5, +0.5,
+ ];
+ const sampleLocales = [
+ void 0,
+ "en",
+ "th-th-u-nu-thai",
+ ["tlh", "de"],
+ ];
+ const sampleOptions = [
+ void 0,
+ {},
+ {style: "percent"},
+ {style: "currency", currency: "USD", minimumIntegerDigits: 4},
+ ];
+ for (let locale of sampleLocales) {
+ for (let options of sampleOptions) {
+ let nf = new Intl.NumberFormat(locale, options);
+ let expected = sampleValues.map(nf.format).join(localeSep);
+ assertEq(sampleValues.toLocaleString(locale, options), expected);
+ }
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/Array/toLocaleString.js b/js/src/tests/non262/Intl/Array/toLocaleString.js
new file mode 100644
index 0000000000..f94e531f89
--- /dev/null
+++ b/js/src/tests/non262/Intl/Array/toLocaleString.js
@@ -0,0 +1,35 @@
+if (typeof Intl === "object") {
+ const localeSep = [,,].toLocaleString();
+
+ // Missing arguments are passed as |undefined|.
+ const objNoArgs = {
+ toLocaleString() {
+ assertEq(arguments.length, 2);
+ assertEq(arguments[0], undefined);
+ assertEq(arguments[1], undefined);
+ return "pass";
+ }
+ };
+ // - Single element case.
+ assertEq([objNoArgs].toLocaleString(), "pass");
+ // - More than one element.
+ assertEq([objNoArgs, objNoArgs].toLocaleString(), "pass" + localeSep + "pass");
+
+ // Ensure "locales" and "options" arguments are passed to the array elements.
+ const locales = {}, options = {};
+ const objWithArgs = {
+ toLocaleString() {
+ assertEq(arguments.length, 2);
+ assertEq(arguments[0], locales);
+ assertEq(arguments[1], options);
+ return "pass";
+ }
+ };
+ // - Single element case.
+ assertEq([objWithArgs].toLocaleString(locales, options), "pass");
+ // - More than one element.
+ assertEq([objWithArgs, objWithArgs].toLocaleString(locales, options), "pass" + localeSep + "pass");
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/Collator/browser.js b/js/src/tests/non262/Intl/Collator/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Intl/Collator/browser.js
diff --git a/js/src/tests/non262/Intl/Collator/call.js b/js/src/tests/non262/Intl/Collator/call.js
new file mode 100644
index 0000000000..089764a2cb
--- /dev/null
+++ b/js/src/tests/non262/Intl/Collator/call.js
@@ -0,0 +1,74 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+function IsIntlService(c) {
+ return typeof c === "function" &&
+ c.hasOwnProperty("prototype") &&
+ c.prototype.hasOwnProperty("resolvedOptions");
+}
+
+function IsObject(o) {
+ return Object(o) === o;
+}
+
+function thisValues() {
+ const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsIntlService);
+
+ return [
+ // Primitive values.
+ ...[undefined, null, true, "abc", Symbol(), 123],
+
+ // Object values.
+ ...[{}, [], /(?:)/, function(){}, new Proxy({}, {})],
+
+ // Intl objects.
+ ...[].concat(...intlConstructors.map(ctor => {
+ let args = [];
+ if (ctor === Intl.DisplayNames) {
+ // Intl.DisplayNames can't be constructed without any arguments.
+ args = [undefined, {type: "language"}];
+ }
+
+ return [
+ // Instance of an Intl constructor.
+ new ctor(...args),
+
+ // Instance of a subclassed Intl constructor.
+ new class extends ctor {}(...args),
+
+ // Object inheriting from an Intl constructor prototype.
+ Object.create(ctor.prototype),
+
+ // Intl object not inheriting from its default prototype.
+ Object.setPrototypeOf(new ctor(...args), Object.prototype),
+ ];
+ })),
+ ];
+}
+
+// Invoking [[Call]] for Intl.Collator always returns a new Collator instance.
+for (let thisValue of thisValues()) {
+ let obj = Intl.Collator.call(thisValue);
+ assertEq(Object.is(obj, thisValue), false);
+ assertEq(obj instanceof Intl.Collator, true);
+
+ // Ensure Intl.[[FallbackSymbol]] wasn't installed on |thisValue|.
+ if (IsObject(thisValue))
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+}
+
+// Intl.Collator doesn't use the legacy Intl constructor compromise semantics.
+for (let thisValue of thisValues()) {
+ // Ensure instanceof operator isn't invoked for Intl.Collator.
+ Object.defineProperty(Intl.Collator, Symbol.hasInstance, {
+ get() {
+ assertEq(false, true, "@@hasInstance operator called");
+ }, configurable: true
+ });
+ let obj = Intl.Collator.call(thisValue);
+ delete Intl.Collator[Symbol.hasInstance];
+ assertEq(Object.is(obj, thisValue), false);
+ assertEq(obj instanceof Intl.Collator, true);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/Collator/caseFirst.js b/js/src/tests/non262/Intl/Collator/caseFirst.js
new file mode 100644
index 0000000000..d183c8fdd5
--- /dev/null
+++ b/js/src/tests/non262/Intl/Collator/caseFirst.js
@@ -0,0 +1,197 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Locales which use caseFirst=off for the standard (sort) collation type.
+const defaultLocales = Intl.Collator.supportedLocalesOf(["en", "de", "es", "sv", "ar", "zh", "ja"]);
+
+// Locales which use caseFirst=upper for the standard (sort) collation type.
+const upperFirstLocales = Intl.Collator.supportedLocalesOf(["cu", "da", "mt"]);
+
+// Default collation for zh (pinyin) reorders "á" before "a" at secondary strength level.
+const accentReordered = ["zh"];
+
+const allLocales = [...defaultLocales, ...upperFirstLocales];
+
+
+// Check default "caseFirst" option is resolved correctly.
+for (let locale of defaultLocales) {
+ let col = new Intl.Collator(locale, {usage: "sort"});
+ assertEq(col.resolvedOptions().caseFirst, "false");
+}
+for (let locale of upperFirstLocales) {
+ let col = new Intl.Collator(locale, {usage: "sort"});
+ assertEq(col.resolvedOptions().caseFirst, "upper");
+}
+for (let locale of allLocales) {
+ let col = new Intl.Collator(locale, {usage: "search"});
+ assertEq(col.resolvedOptions().caseFirst, "false");
+}
+
+
+const collOptions = {usage: "sort"};
+const primary = {sensitivity: "base"};
+const secondary = {sensitivity: "accent"};
+const tertiary = {sensitivity: "variant"};
+const caseLevel = {sensitivity: "case"};
+const strengths = [primary, secondary, tertiary, caseLevel];
+
+// "A" is sorted after "a" when caseFirst=off is the default and strength is tertiary.
+for (let locale of defaultLocales) {
+ let col = new Intl.Collator(locale, Object.assign({}, collOptions, tertiary));
+
+ assertEq(col.compare("A", "a"), 1);
+ assertEq(col.compare("a", "A"), -1);
+}
+for (let locale of defaultLocales.filter(loc => !accentReordered.includes(loc))) {
+ let col = new Intl.Collator(locale, Object.assign({}, collOptions, tertiary));
+
+ assertEq(col.compare("A", "á"), -1);
+ assertEq(col.compare("á", "A"), 1);
+}
+
+// Also sorted after "a" with the sensitivity=case collator.
+for (let locale of defaultLocales) {
+ let col = new Intl.Collator(locale, Object.assign({}, collOptions, caseLevel));
+
+ assertEq(col.compare("A", "a"), 1);
+ assertEq(col.compare("a", "A"), -1);
+
+ assertEq(col.compare("A", "á"), 1);
+ assertEq(col.compare("á", "A"), -1);
+}
+
+
+// "A" is sorted before "a" when caseFirst=upper is the default and strength is tertiary.
+for (let locale of upperFirstLocales) {
+ let col = new Intl.Collator(locale, Object.assign({}, collOptions, tertiary));
+
+ assertEq(col.compare("A", "a"), -1);
+ assertEq(col.compare("a", "A"), 1);
+
+ assertEq(col.compare("A", "á"), -1);
+ assertEq(col.compare("á", "A"), 1);
+}
+
+// Also sorted before "a" with the sensitivity=case collator.
+for (let locale of upperFirstLocales) {
+ let col = new Intl.Collator(locale, Object.assign({}, collOptions, caseLevel));
+
+ assertEq(col.compare("A", "a"), -1);
+ assertEq(col.compare("a", "A"), 1);
+
+ assertEq(col.compare("A", "á"), -1);
+ assertEq(col.compare("á", "A"), 1);
+}
+
+
+// caseFirst=upper doesn't change the sort order when strength is below tertiary.
+for (let locale of allLocales) {
+ let col = new Intl.Collator(locale, Object.assign({}, collOptions, secondary));
+
+ assertEq(col.compare("A", "a"), 0);
+ assertEq(col.compare("a", "A"), 0);
+}
+for (let locale of allLocales.filter(loc => !accentReordered.includes(loc))) {
+ let col = new Intl.Collator(locale, Object.assign({}, collOptions, secondary));
+
+ assertEq(col.compare("A", "á"), -1);
+ assertEq(col.compare("á", "A"), 1);
+}
+
+for (let locale of allLocales) {
+ let col = new Intl.Collator(locale, Object.assign({}, collOptions, primary));
+
+ assertEq(col.compare("A", "a"), 0);
+ assertEq(col.compare("a", "A"), 0);
+
+ assertEq(col.compare("A", "á"), 0);
+ assertEq(col.compare("á", "A"), 0);
+}
+
+
+// caseFirst=upper doesn't change the sort order when there's a primary difference.
+for (let locale of allLocales) {
+ for (let strength of strengths) {
+ let col = new Intl.Collator(locale, Object.assign({}, collOptions, strength));
+
+ assertEq(col.compare("A", "b"), -1);
+ assertEq(col.compare("a", "B"), -1);
+ }
+}
+
+
+// caseFirst set through Unicode extension tag.
+for (let locale of allLocales) {
+ let colKfFalse = new Intl.Collator(locale + "-u-kf-false", {});
+ let colKfLower = new Intl.Collator(locale + "-u-kf-lower", {});
+ let colKfUpper = new Intl.Collator(locale + "-u-kf-upper", {});
+
+ assertEq(colKfFalse.resolvedOptions().caseFirst, "false");
+ assertEq(colKfFalse.compare("A", "a"), 1);
+ assertEq(colKfFalse.compare("a", "A"), -1);
+
+ assertEq(colKfLower.resolvedOptions().caseFirst, "lower");
+ assertEq(colKfLower.compare("A", "a"), 1);
+ assertEq(colKfLower.compare("a", "A"), -1);
+
+ assertEq(colKfUpper.resolvedOptions().caseFirst, "upper");
+ assertEq(colKfUpper.compare("A", "a"), -1);
+ assertEq(colKfUpper.compare("a", "A"), 1);
+}
+
+
+// caseFirst set through options value.
+for (let locale of allLocales) {
+ let colKfFalse = new Intl.Collator(locale, {caseFirst: "false"});
+ let colKfLower = new Intl.Collator(locale, {caseFirst: "lower"});
+ let colKfUpper = new Intl.Collator(locale, {caseFirst: "upper"});
+
+ assertEq(colKfFalse.resolvedOptions().caseFirst, "false");
+ assertEq(colKfFalse.compare("A", "a"), 1);
+ assertEq(colKfFalse.compare("a", "A"), -1);
+
+ assertEq(colKfLower.resolvedOptions().caseFirst, "lower");
+ assertEq(colKfLower.compare("A", "a"), 1);
+ assertEq(colKfLower.compare("a", "A"), -1);
+
+ assertEq(colKfUpper.resolvedOptions().caseFirst, "upper");
+ assertEq(colKfUpper.compare("A", "a"), -1);
+ assertEq(colKfUpper.compare("a", "A"), 1);
+}
+
+
+// Test Unicode extension tag and options value, the latter should win.
+for (let locale of allLocales) {
+ let colKfFalse = new Intl.Collator(locale + "-u-kf-upper", {caseFirst: "false"});
+ let colKfLower = new Intl.Collator(locale + "-u-kf-upper", {caseFirst: "lower"});
+ let colKfUpper = new Intl.Collator(locale + "-u-kf-lower", {caseFirst: "upper"});
+
+ assertEq(colKfFalse.resolvedOptions().caseFirst, "false");
+ assertEq(colKfFalse.compare("A", "a"), 1);
+ assertEq(colKfFalse.compare("a", "A"), -1);
+
+ assertEq(colKfLower.resolvedOptions().caseFirst, "lower");
+ assertEq(colKfLower.compare("A", "a"), 1);
+ assertEq(colKfLower.compare("a", "A"), -1);
+
+ assertEq(colKfUpper.resolvedOptions().caseFirst, "upper");
+ assertEq(colKfUpper.compare("A", "a"), -1);
+ assertEq(colKfUpper.compare("a", "A"), 1);
+}
+
+// Ensure languages are properly detected when additional subtags are present.
+if (Intl.Collator.supportedLocalesOf("da").length !== 0) {
+ assertEq(new Intl.Collator("da-DK", {usage: "sort"}).resolvedOptions().caseFirst, "upper");
+ assertEq(new Intl.Collator("da-Latn-DK", {usage: "sort"}).resolvedOptions().caseFirst, "upper");
+}
+if (Intl.Collator.supportedLocalesOf("mt").length !== 0) {
+ assertEq(new Intl.Collator("mt-MT", {usage: "sort"}).resolvedOptions().caseFirst, "upper");
+ assertEq(new Intl.Collator("mt-Latn-MT", {usage: "sort"}).resolvedOptions().caseFirst, "upper");
+}
+
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/Collator/compare.js b/js/src/tests/non262/Intl/Collator/compare.js
new file mode 100644
index 0000000000..6f57c722dd
--- /dev/null
+++ b/js/src/tests/non262/Intl/Collator/compare.js
@@ -0,0 +1,137 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tests the compare function with a diverse set of locales and options.
+
+var input = [
+ "Argentina",
+ "Oerlikon",
+ "Offenbach",
+ "Sverige",
+ "Vaticano",
+ "Zimbabwe",
+ "la France",
+ "¡viva España!",
+ "Österreich",
+ "中国",
+ "日本",
+ "한국",
+];
+
+var collator, expected;
+
+function assertEqualArray(actual, expected, collator) {
+ var description = JSON.stringify(collator.resolvedOptions());
+ assertEq(actual.length, expected.length, "array length, " + description);
+ for (var i = 0; i < actual.length; i++) {
+ assertEq(actual[i], expected[i], "element " + i + ", " + description);
+ }
+}
+
+
+// Locale en-US; default options.
+collator = new Intl.Collator("en-US");
+expected = [
+ "¡viva España!",
+ "Argentina",
+ "la France",
+ "Oerlikon",
+ "Offenbach",
+ "Österreich",
+ "Sverige",
+ "Vaticano",
+ "Zimbabwe",
+ "한국",
+ "中国",
+ "日本",
+];
+assertEqualArray(input.sort(collator.compare), expected, collator);
+
+// Locale sv-SE; default options.
+// Swedish treats "Ö" as a separate character, which sorts after "Z".
+collator = new Intl.Collator("sv-SE");
+expected = [
+ "¡viva España!",
+ "Argentina",
+ "la France",
+ "Oerlikon",
+ "Offenbach",
+ "Sverige",
+ "Vaticano",
+ "Zimbabwe",
+ "Österreich",
+ "한국",
+ "中国",
+ "日本",
+];
+assertEqualArray(input.sort(collator.compare), expected, collator);
+
+// Locale sv-SE; ignore punctuation.
+collator = new Intl.Collator("sv-SE", {ignorePunctuation: true});
+expected = [
+ "Argentina",
+ "la France",
+ "Oerlikon",
+ "Offenbach",
+ "Sverige",
+ "Vaticano",
+ "¡viva España!",
+ "Zimbabwe",
+ "Österreich",
+ "한국",
+ "中国",
+ "日本",
+];
+assertEqualArray(input.sort(collator.compare), expected, collator);
+
+// Locale de-DE; default options.
+// In German standard sorting, umlauted characters are treated as variants
+// of their base characters: ä ≅ a, ö ≅ o, ü ≅ u.
+collator = new Intl.Collator("de-DE");
+expected = [
+ "¡viva España!",
+ "Argentina",
+ "la France",
+ "Oerlikon",
+ "Offenbach",
+ "Österreich",
+ "Sverige",
+ "Vaticano",
+ "Zimbabwe",
+ "한국",
+ "中国",
+ "日本",
+];
+assertEqualArray(input.sort(collator.compare), expected, collator);
+
+// Locale de-DE; phonebook sort order.
+// In German phonebook sorting, umlauted characters are expanded to two-vowel
+// sequences: ä → ae, ö → oe, ü → ue.
+collator = new Intl.Collator("de-DE-u-co-phonebk");
+expected = [
+ "¡viva España!",
+ "Argentina",
+ "la France",
+ "Oerlikon",
+ "Österreich",
+ "Offenbach",
+ "Sverige",
+ "Vaticano",
+ "Zimbabwe",
+ "한국",
+ "中国",
+ "日本",
+];
+assertEqualArray(input.sort(collator.compare), expected, collator);
+
+
+// Test the .name property of the "compare" getter.
+var desc = Object.getOwnPropertyDescriptor(Intl.Collator.prototype, "compare");
+assertEq(desc !== undefined, true);
+assertEq(typeof desc.get, "function");
+assertEq(desc.get.name, "get compare");
+
+
+reportCompare(0, 0, 'ok');
diff --git a/js/src/tests/non262/Intl/Collator/construct-newtarget.js b/js/src/tests/non262/Intl/Collator/construct-newtarget.js
new file mode 100644
index 0000000000..5db1abf373
--- /dev/null
+++ b/js/src/tests/non262/Intl/Collator/construct-newtarget.js
@@ -0,0 +1,81 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+// Test subclassing %Intl.Collator% works correctly.
+class MyCollator extends Intl.Collator {}
+
+var obj = new MyCollator();
+assertEq(obj instanceof MyCollator, true);
+assertEq(obj instanceof Intl.Collator, true);
+assertEq(Object.getPrototypeOf(obj), MyCollator.prototype);
+
+obj = Reflect.construct(MyCollator, []);
+assertEq(obj instanceof MyCollator, true);
+assertEq(obj instanceof Intl.Collator, true);
+assertEq(Object.getPrototypeOf(obj), MyCollator.prototype);
+
+obj = Reflect.construct(MyCollator, [], MyCollator);
+assertEq(obj instanceof MyCollator, true);
+assertEq(obj instanceof Intl.Collator, true);
+assertEq(Object.getPrototypeOf(obj), MyCollator.prototype);
+
+obj = Reflect.construct(MyCollator, [], Intl.Collator);
+assertEq(obj instanceof MyCollator, false);
+assertEq(obj instanceof Intl.Collator, true);
+assertEq(Object.getPrototypeOf(obj), Intl.Collator.prototype);
+
+
+// Set a different constructor as NewTarget.
+obj = Reflect.construct(MyCollator, [], Array);
+assertEq(obj instanceof MyCollator, false);
+assertEq(obj instanceof Intl.Collator, false);
+assertEq(obj instanceof Array, true);
+assertEq(Object.getPrototypeOf(obj), Array.prototype);
+
+obj = Reflect.construct(Intl.Collator, [], Array);
+assertEq(obj instanceof Intl.Collator, false);
+assertEq(obj instanceof Array, true);
+assertEq(Object.getPrototypeOf(obj), Array.prototype);
+
+
+// The prototype defaults to %CollatorPrototype% if null.
+function NewTargetNullPrototype() {}
+NewTargetNullPrototype.prototype = null;
+
+obj = Reflect.construct(Intl.Collator, [], NewTargetNullPrototype);
+assertEq(obj instanceof Intl.Collator, true);
+assertEq(Object.getPrototypeOf(obj), Intl.Collator.prototype);
+
+obj = Reflect.construct(MyCollator, [], NewTargetNullPrototype);
+assertEq(obj instanceof MyCollator, false);
+assertEq(obj instanceof Intl.Collator, true);
+assertEq(Object.getPrototypeOf(obj), Intl.Collator.prototype);
+
+
+// "prototype" property is retrieved exactly once.
+var trapLog = [], getLog = [];
+var ProxiedConstructor = new Proxy(Intl.Collator, new Proxy({
+ get(target, propertyKey, receiver) {
+ getLog.push(propertyKey);
+ return Reflect.get(target, propertyKey, receiver);
+ }
+}, {
+ get(target, propertyKey, receiver) {
+ trapLog.push(propertyKey);
+ return Reflect.get(target, propertyKey, receiver);
+ }
+}));
+
+obj = Reflect.construct(Intl.Collator, [], ProxiedConstructor);
+assertEqArray(trapLog, ["get"]);
+assertEqArray(getLog, ["prototype"]);
+assertEq(obj instanceof Intl.Collator, true);
+assertEq(Object.getPrototypeOf(obj), Intl.Collator.prototype);
+
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/Collator/cross-compartment.js b/js/src/tests/non262/Intl/Collator/cross-compartment.js
new file mode 100644
index 0000000000..a8cf3134ca
--- /dev/null
+++ b/js/src/tests/non262/Intl/Collator/cross-compartment.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+var otherGlobal = newGlobal();
+
+var collator = new Intl.Collator();
+var ccwCollator = new otherGlobal.Intl.Collator();
+
+// Test Intl.Collator.prototype.compare with a CCW object.
+var Intl_Collator_compare_get = Object.getOwnPropertyDescriptor(Intl.Collator.prototype, "compare").get;
+
+assertEq(Intl_Collator_compare_get.call(ccwCollator)("a", "A"),
+ Intl_Collator_compare_get.call(collator)("a", "A"));
+
+// Test Intl.Collator.prototype.resolvedOptions with a CCW object.
+var Intl_Collator_resolvedOptions = Intl.Collator.prototype.resolvedOptions;
+
+assertEq(deepEqual(Intl_Collator_resolvedOptions.call(ccwCollator),
+ Intl_Collator_resolvedOptions.call(collator)),
+ true);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/Collator/shell.js b/js/src/tests/non262/Intl/Collator/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Intl/Collator/shell.js
diff --git a/js/src/tests/non262/Intl/Collator/supportedLocalesOf.js b/js/src/tests/non262/Intl/Collator/supportedLocalesOf.js
new file mode 100644
index 0000000000..7fbebca094
--- /dev/null
+++ b/js/src/tests/non262/Intl/Collator/supportedLocalesOf.js
@@ -0,0 +1,355 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||xulRuntime.shell)
+// -- test in browser only that ICU has locale data for all Mozilla languages
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This array contains the locales that ICU supports in
+// collation whose languages Mozilla localizes Firefox into.
+// Current as of ICU 50.1.2 and Firefox March 2013.
+var locales = [
+ "af",
+ "af-NA",
+ "af-ZA",
+ "ar",
+ "ar-001",
+ "ar-AE",
+ "ar-BH",
+ "ar-DJ",
+ "ar-DZ",
+ "ar-EG",
+ "ar-EH",
+ "ar-ER",
+ "ar-IL",
+ "ar-IQ",
+ "ar-JO",
+ "ar-KM",
+ "ar-KW",
+ "ar-LB",
+ "ar-LY",
+ "ar-MA",
+ "ar-MR",
+ "ar-OM",
+ "ar-PS",
+ "ar-QA",
+ "ar-SA",
+ "ar-SD",
+ "ar-SO",
+ "ar-SY",
+ "ar-TD",
+ "ar-TN",
+ "ar-YE",
+ "as",
+ "as-IN",
+ "be",
+ "be-BY",
+ "bg",
+ "bg-BG",
+ "bn",
+ "bn-BD",
+ "bn-IN",
+ "bs",
+ "bs-Cyrl",
+ "bs-Cyrl-BA",
+ "bs-Latn",
+ "bs-Latn-BA",
+ "ca",
+ "ca-AD",
+ "ca-ES",
+ "cs",
+ "cs-CZ",
+ "cy",
+ "cy-GB",
+ "da",
+ "da-DK",
+ "de",
+ "de-AT",
+ "de-BE",
+ "de-CH",
+ "de-DE",
+ "de-LI",
+ "de-LU",
+ "el",
+ "el-CY",
+ "el-GR",
+ "en",
+ "en-150",
+ "en-AG",
+ "en-AS",
+ "en-AU",
+ "en-BB",
+ "en-BE",
+ "en-BM",
+ "en-BS",
+ "en-BW",
+ "en-BZ",
+ "en-CA",
+ "en-CM",
+ "en-DM",
+ "en-FJ",
+ "en-FM",
+ "en-GB",
+ "en-GD",
+ "en-GG",
+ "en-GH",
+ "en-GI",
+ "en-GM",
+ "en-GU",
+ "en-HK",
+ "en-IE",
+ "en-IM",
+ "en-IN",
+ "en-JE",
+ "en-JM",
+ "en-KE",
+ "en-KI",
+ "en-KN",
+ "en-KY",
+ "en-LC",
+ "en-LR",
+ "en-LS",
+ "en-MG",
+ "en-MH",
+ "en-MP",
+ "en-MT",
+ "en-MU",
+ "en-MW",
+ "en-NA",
+ "en-NG",
+ "en-NZ",
+ "en-PG",
+ "en-PH",
+ "en-PK",
+ "en-PR",
+ "en-PW",
+ "en-SB",
+ "en-SC",
+ "en-SG",
+ "en-SL",
+ "en-SS",
+ "en-TC",
+ "en-TO",
+ "en-TT",
+ "en-TZ",
+ "en-UG",
+ "en-UM",
+ "en-US",
+ "en-US-POSIX",
+ "en-VC",
+ "en-VG",
+ "en-VI",
+ "en-VU",
+ "en-WS",
+ "en-ZA",
+ "en-ZM",
+ "en-ZW",
+ "eo",
+ "es",
+ "es-419",
+ "es-AR",
+ "es-BO",
+ "es-CL",
+ "es-CO",
+ "es-CR",
+ "es-CU",
+ "es-DO",
+ "es-EA",
+ "es-EC",
+ "es-ES",
+ "es-GQ",
+ "es-GT",
+ "es-HN",
+ "es-IC",
+ "es-MX",
+ "es-NI",
+ "es-PA",
+ "es-PE",
+ "es-PH",
+ "es-PR",
+ "es-PY",
+ "es-SV",
+ "es-US",
+ "es-UY",
+ "es-VE",
+ "et",
+ "et-EE",
+ "fa",
+ "fa-AF",
+ "fa-IR",
+ "fi",
+ "fi-FI",
+ "fr",
+ "fr-BE",
+ "fr-BF",
+ "fr-BI",
+ "fr-BJ",
+ "fr-BL",
+ "fr-CA",
+ "fr-CD",
+ "fr-CF",
+ "fr-CG",
+ "fr-CH",
+ "fr-CI",
+ "fr-CM",
+ "fr-DJ",
+ "fr-DZ",
+ "fr-FR",
+ "fr-GA",
+ "fr-GN",
+ "fr-GP",
+ "fr-GQ",
+ "fr-HT",
+ "fr-KM",
+ "fr-LU",
+ "fr-MA",
+ "fr-MC",
+ "fr-MF",
+ "fr-MG",
+ "fr-ML",
+ "fr-MQ",
+ "fr-MR",
+ "fr-MU",
+ "fr-NC",
+ "fr-NE",
+ "fr-PF",
+ "fr-RE",
+ "fr-RW",
+ "fr-SC",
+ "fr-SN",
+ "fr-SY",
+ "fr-TD",
+ "fr-TG",
+ "fr-TN",
+ "fr-VU",
+ "ga",
+ "ga-IE",
+ "gu",
+ "gu-IN",
+ "he",
+ "he-IL",
+ "hi",
+ "hi-IN",
+ "hr",
+ "hr-BA",
+ "hr-HR",
+ "hu",
+ "hu-HU",
+ "hy",
+ "hy-AM",
+ "id",
+ "id-ID",
+ "is",
+ "is-IS",
+ "it",
+ "it-CH",
+ "it-IT",
+ "it-SM",
+ "ja",
+ "ja-JP",
+ "kk",
+ "kk-KZ",
+ "km",
+ "km-KH",
+ "kn",
+ "kn-IN",
+ "ko",
+ "ko-KP",
+ "ko-KR",
+ "lt",
+ "lt-LT",
+ "lv",
+ "lv-LV",
+ "mk",
+ "mk-MK",
+ "ml",
+ "ml-IN",
+ "mr",
+ "mr-IN",
+ "nb",
+ "nb-NO",
+ "nl",
+ "nl-AW",
+ "nl-BE",
+ "nl-CW",
+ "nl-NL",
+ "nl-SR",
+ "nl-SX",
+ "nn",
+ "nn-NO",
+ "or",
+ "or-IN",
+ "pa",
+ "pa-Arab",
+ "pa-Arab-PK",
+ "pa-Guru",
+ "pa-Guru-IN",
+ "pl",
+ "pl-PL",
+ "pt",
+ "pt-AO",
+ "pt-BR",
+ "pt-CV",
+ "pt-GW",
+ "pt-MO",
+ "pt-MZ",
+ "pt-PT",
+ "pt-ST",
+ "pt-TL",
+ "ro",
+ "ro-MD",
+ "ro-RO",
+ "ru",
+ "ru-BY",
+ "ru-KG",
+ "ru-KZ",
+ "ru-MD",
+ "ru-RU",
+ "ru-UA",
+ "si",
+ "si-LK",
+ "sk",
+ "sk-SK",
+ "sl",
+ "sl-SI",
+ "sq",
+ "sq-AL",
+ "sq-MK",
+ "sr",
+ "sr-Cyrl",
+ "sr-Cyrl-BA",
+ "sr-Cyrl-ME",
+ "sr-Cyrl-RS",
+ "sr-Latn",
+ "sr-Latn-BA",
+ "sr-Latn-ME",
+ "sr-Latn-RS",
+ "sv",
+ "sv-AX",
+ "sv-FI",
+ "sv-SE",
+ "te",
+ "te-IN",
+ "th",
+ "th-TH",
+ "tr",
+ "tr-CY",
+ "tr-TR",
+ "uk",
+ "uk-UA",
+ "vi",
+ "vi-VN",
+ "zh",
+ "zh-Hans",
+ "zh-Hans-CN",
+ "zh-Hans-SG",
+ "zh-Hant",
+ "zh-Hant-HK",
+ "zh-Hant-MO",
+ "zh-Hant-TW",
+];
+
+
+var count = Intl.Collator.supportedLocalesOf(locales).length;
+
+reportCompare(locales.length, count, "Number of supported locales in Intl.Collator");
diff --git a/js/src/tests/non262/Intl/Collator/toStringTag.js b/js/src/tests/non262/Intl/Collator/toStringTag.js
new file mode 100644
index 0000000000..e092418df9
--- /dev/null
+++ b/js/src/tests/non262/Intl/Collator/toStringTag.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var desc = Object.getOwnPropertyDescriptor(Intl.Collator.prototype, Symbol.toStringTag);
+
+assertEq(desc !== undefined, true);
+assertEq(desc.value, "Intl.Collator");
+assertEq(desc.writable, false);
+assertEq(desc.enumerable, false);
+assertEq(desc.configurable, true);
+
+assertEq(Object.prototype.toString.call(Intl.Collator.prototype), "[object Intl.Collator]");
+assertEq(Object.prototype.toString.call(new Intl.Collator), "[object Intl.Collator]");
+
+Object.defineProperty(Intl.Collator.prototype, Symbol.toStringTag, {value: "Collator"});
+
+assertEq(Object.prototype.toString.call(Intl.Collator.prototype), "[object Collator]");
+assertEq(Object.prototype.toString.call(new Intl.Collator), "[object Collator]");
+
+delete Intl.Collator.prototype[Symbol.toStringTag];
+
+assertEq(Object.prototype.toString.call(Intl.Collator.prototype), "[object Object]");
+assertEq(Object.prototype.toString.call(new Intl.Collator), "[object Object]");
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/Date/browser.js b/js/src/tests/non262/Intl/Date/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Intl/Date/browser.js
diff --git a/js/src/tests/non262/Intl/Date/shell.js b/js/src/tests/non262/Intl/Date/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Intl/Date/shell.js
diff --git a/js/src/tests/non262/Intl/Date/toLocaleDateString_timeZone.js b/js/src/tests/non262/Intl/Date/toLocaleDateString_timeZone.js
new file mode 100644
index 0000000000..345befb678
--- /dev/null
+++ b/js/src/tests/non262/Intl/Date/toLocaleDateString_timeZone.js
@@ -0,0 +1,77 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const defaultLocale = "en-US";
+const defaultDate = Date.UTC(2012, 12-1, 6, 12, 0, 0);
+const defaultOptions = { timeZoneName: "short" };
+
+const tests = [
+ {
+ timeZone: "UTC",
+ result: "12/6/2012, UTC",
+ },
+ {
+ timeZone: "America/Los_Angeles",
+ result: "12/6/2012, PST",
+ },
+ {
+ timeZone: "Europe/Berlin", locale: "de",
+ options: { timeZoneName: "short" },
+ result: "6.12.2012, MEZ",
+ },
+ {
+ timeZone: "Europe/Paris", locale: "fr",
+ options: { timeZoneName: "long" },
+ result: "06/12/2012 à heure normale d’Europe centrale",
+ },
+ {
+ timeZone: "Asia/Shanghai", locale: "zh-Hans-CN",
+ options: { timeZoneName: "long" },
+ result: "2012/12/6 中国标准时间",
+ },
+ {
+ timeZone: { toString: () => "Australia/Melbourne" }, locale: "en-AU",
+ result: "06/12/2012, AEDT",
+ },
+];
+
+for (let {timeZone, result, locale = defaultLocale, date = defaultDate, options = defaultOptions} of tests) {
+ let s = new Date(date).toLocaleDateString(locale, Object.assign({timeZone}, options));
+ assertEq(s, result);
+}
+
+
+// |undefined| or absent "timeZone" option selects the default time zone.
+{
+ let locale = defaultLocale;
+ let date = defaultDate;
+ let options = defaultOptions;
+
+ let absentTz = new Date(date).toLocaleDateString(locale, Object.assign({}, options));
+ let undefinedTz = new Date(date).toLocaleDateString(locale, Object.assign({timeZone: undefined}, options));
+ assertEq(undefinedTz, absentTz);
+}
+
+
+// RangeError is thrown for invalid time zone names.
+for (let timeZone of ["", "undefined", "UTC\0", "Vienna", "Africa", "America/NewYork"]) {
+ assertThrowsInstanceOf(() => {
+ new Date(defaultDate).toLocaleDateString(undefined, {timeZone});
+ }, RangeError);
+}
+
+// RangeError is thrown for these values, because ToString(<value>)
+// isn't a valid time zone name.
+for (let timeZone of [null, 0, 0.5, true, false, [], {}, function() {}]) {
+ assertThrowsInstanceOf(() => {
+ new Date(defaultDate).toLocaleDateString(undefined, {timeZone});
+ }, RangeError);
+}
+
+// ToString(<symbol>) throws TypeError.
+assertThrowsInstanceOf(() => {
+ new Date(defaultDate).toLocaleDateString(undefined, {timeZone: Symbol()});
+}, TypeError);
+
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/Date/toLocaleString_timeZone.js b/js/src/tests/non262/Intl/Date/toLocaleString_timeZone.js
new file mode 100644
index 0000000000..63e3700342
--- /dev/null
+++ b/js/src/tests/non262/Intl/Date/toLocaleString_timeZone.js
@@ -0,0 +1,77 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const defaultLocale = "en-US";
+const defaultDate = Date.UTC(2012, 12-1, 6, 12, 0, 0);
+const defaultOptions = {};
+
+const tests = [
+ {
+ timeZone: "UTC",
+ result: "12/6/2012, 12:00:00 PM",
+ },
+ {
+ timeZone: "America/Los_Angeles",
+ result: "12/6/2012, 4:00:00 AM",
+ },
+ {
+ timeZone: "Europe/Berlin", locale: "de",
+ options: { timeZoneName: "short" },
+ result: "6.12.2012, 13:00:00 MEZ",
+ },
+ {
+ timeZone: "Europe/Paris", locale: "fr",
+ options: { timeZoneName: "long" },
+ result: "06/12/2012 à 13:00:00 heure normale d’Europe centrale",
+ },
+ {
+ timeZone: "Asia/Shanghai", locale: "zh-Hans-CN",
+ options: { timeZoneName: "long" },
+ result: "2012/12/6 中国标准时间 下午8:00:00",
+ },
+ {
+ timeZone: { toString: () => "Australia/Melbourne" }, locale: "en-AU",
+ result: "06/12/2012, 11:00:00 pm",
+ },
+];
+
+for (let {timeZone, result, locale = defaultLocale, date = defaultDate, options = defaultOptions} of tests) {
+ let s = new Date(date).toLocaleString(locale, Object.assign({timeZone}, options));
+ assertEq(s, result);
+}
+
+
+// |undefined| or absent "timeZone" option selects the default time zone.
+{
+ let locale = defaultLocale;
+ let date = defaultDate;
+ let options = defaultOptions;
+
+ let absentTz = new Date(date).toLocaleString(locale, Object.assign({}, options));
+ let undefinedTz = new Date(date).toLocaleString(locale, Object.assign({timeZone: undefined}, options));
+ assertEq(undefinedTz, absentTz);
+}
+
+
+// RangeError is thrown for invalid time zone names.
+for (let timeZone of ["", "undefined", "UTC\0", "Vienna", "Africa", "America/NewYork"]) {
+ assertThrowsInstanceOf(() => {
+ new Date(defaultDate).toLocaleString(undefined, {timeZone});
+ }, RangeError);
+}
+
+// RangeError is thrown for these values, because ToString(<value>)
+// isn't a valid time zone name.
+for (let timeZone of [null, 0, 0.5, true, false, [], {}, function() {}]) {
+ assertThrowsInstanceOf(() => {
+ new Date(defaultDate).toLocaleString(undefined, {timeZone});
+ }, RangeError);
+}
+
+// ToString(<symbol>) throws TypeError.
+assertThrowsInstanceOf(() => {
+ new Date(defaultDate).toLocaleString(undefined, {timeZone: Symbol()});
+}, TypeError);
+
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/Date/toLocaleTimeString_timeZone.js b/js/src/tests/non262/Intl/Date/toLocaleTimeString_timeZone.js
new file mode 100644
index 0000000000..14799a2874
--- /dev/null
+++ b/js/src/tests/non262/Intl/Date/toLocaleTimeString_timeZone.js
@@ -0,0 +1,77 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const defaultLocale = "en-US";
+const defaultDate = Date.UTC(2012, 12-1, 6, 12, 0, 0);
+const defaultOptions = {};
+
+const tests = [
+ {
+ timeZone: "UTC",
+ result: "12:00:00 PM",
+ },
+ {
+ timeZone: "America/Los_Angeles",
+ result: "4:00:00 AM",
+ },
+ {
+ timeZone: "Europe/Berlin", locale: "de",
+ options: { timeZoneName: "short" },
+ result: "13:00:00 MEZ",
+ },
+ {
+ timeZone: "Europe/Paris", locale: "fr",
+ options: { timeZoneName: "long" },
+ result: "13:00:00 heure normale d’Europe centrale",
+ },
+ {
+ timeZone: "Asia/Shanghai", locale: "zh-Hans-CN",
+ options: { timeZoneName: "long" },
+ result: "中国标准时间 下午8:00:00",
+ },
+ {
+ timeZone: { toString: () => "Australia/Melbourne" }, locale: "en-AU",
+ result: "11:00:00 pm",
+ },
+];
+
+for (let {timeZone, result, locale = defaultLocale, date = defaultDate, options = defaultOptions} of tests) {
+ let s = new Date(date).toLocaleTimeString(locale, Object.assign({timeZone}, options));
+ assertEq(s, result);
+}
+
+
+// |undefined| or absent "timeZone" option selects the default time zone.
+{
+ let locale = defaultLocale;
+ let date = defaultDate;
+ let options = defaultOptions;
+
+ let absentTz = new Date(date).toLocaleTimeString(locale, Object.assign({}, options));
+ let undefinedTz = new Date(date).toLocaleTimeString(locale, Object.assign({timeZone: undefined}, options));
+ assertEq(undefinedTz, absentTz);
+}
+
+
+// RangeError is thrown for invalid time zone names.
+for (let timeZone of ["", "undefined", "UTC\0", "Vienna", "Africa", "America/NewYork"]) {
+ assertThrowsInstanceOf(() => {
+ new Date(defaultDate).toLocaleTimeString(undefined, {timeZone});
+ }, RangeError);
+}
+
+// RangeError is thrown for these values, because ToString(<value>)
+// isn't a valid time zone name.
+for (let timeZone of [null, 0, 0.5, true, false, [], {}, function() {}]) {
+ assertThrowsInstanceOf(() => {
+ new Date(defaultDate).toLocaleTimeString(undefined, {timeZone});
+ }, RangeError);
+}
+
+// ToString(<symbol>) throws TypeError.
+assertThrowsInstanceOf(() => {
+ new Date(defaultDate).toLocaleTimeString(undefined, {timeZone: Symbol()});
+}, TypeError);
+
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/browser.js b/js/src/tests/non262/Intl/DateTimeFormat/browser.js
new file mode 100644
index 0000000000..5665e7ed44
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/browser.js
@@ -0,0 +1,3 @@
+if (typeof setTimeZone === "undefined") {
+ var setTimeZone = SpecialPowers.Cu.getJSTestingFunctions().setTimeZone;
+}
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/calendar-aliases.js b/js/src/tests/non262/Intl/DateTimeFormat/calendar-aliases.js
new file mode 100644
index 0000000000..901adcb45f
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/calendar-aliases.js
@@ -0,0 +1,35 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Ensure ethiopic-amete-alem is resolved to ethioaa instead of ethiopic.
+function testEthiopicAmeteAlem() {
+ var locale = "am-ET-u-nu-latn";
+ var opts = {timeZone: "Africa/Addis_Ababa"};
+ var dtfEthiopicAmeteAlem = new Intl.DateTimeFormat(`${locale}-ca-ethiopic-amete-alem`, opts);
+ var dtfEthioaa = new Intl.DateTimeFormat(`${locale}-ca-ethioaa`, opts);
+ var dtfEthiopic = new Intl.DateTimeFormat(`${locale}-ca-ethiopic`, opts);
+
+ var date = new Date(2016, 1 - 1, 1);
+
+ assertEq(dtfEthiopicAmeteAlem.format(date), dtfEthioaa.format(date));
+ assertEq(dtfEthiopicAmeteAlem.format(date) === dtfEthiopic.format(date), false);
+}
+
+// Ensure islamicc is resolved to islamic-civil.
+function testIslamicCivil() {
+ var locale = "ar-SA-u-nu-latn";
+ var opts = {timeZone: "Asia/Riyadh"};
+ var dtfIslamicCivil = new Intl.DateTimeFormat(`${locale}-ca-islamic-civil`, opts);
+ var dtfIslamicc = new Intl.DateTimeFormat(`${locale}-ca-islamicc`, opts);
+ var dtfIslamic = new Intl.DateTimeFormat(`${locale}-ca-islamic`, opts);
+
+ var date = new Date(2016, 1 - 1, 1);
+
+ assertEq(dtfIslamicCivil.format(date), dtfIslamicc.format(date));
+ assertEq(dtfIslamicCivil.format(date) === dtfIslamic.format(date), false);
+}
+
+testEthiopicAmeteAlem();
+testIslamicCivil();
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/calendar-option.js b/js/src/tests/non262/Intl/DateTimeFormat/calendar-option.js
new file mode 100644
index 0000000000..cbf73df5c9
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/calendar-option.js
@@ -0,0 +1,67 @@
+const defaultLocale = "en";
+const defaultCalendar = new Intl.DateTimeFormat(defaultLocale).resolvedOptions().calendar;
+
+function createWithLocale(locale, calendar) {
+ return new Intl.DateTimeFormat(locale, {calendar});
+}
+
+function create(calendar) {
+ return createWithLocale(defaultLocale, calendar);
+}
+
+// Empty string should throw.
+assertThrowsInstanceOf(() => create(""), RangeError);
+
+// Trailing \0 should throw.
+assertThrowsInstanceOf(() => create("gregory\0"), RangeError);
+
+// Too short or too long strings should throw.
+assertThrowsInstanceOf(() => create("a"), RangeError);
+assertThrowsInstanceOf(() => create("toolongstring"), RangeError);
+
+// Throw even when prefix is valid.
+assertThrowsInstanceOf(() => create("gregory-toolongstring"), RangeError);
+
+// |calendar| can be set to |undefined|.
+let dtf = create(undefined);
+assertEq(dtf.resolvedOptions().calendar, defaultCalendar);
+
+// Unsupported calendars are ignored.
+dtf = create("xxxxxxxx");
+assertEq(dtf.resolvedOptions().calendar, defaultCalendar);
+
+// Calendars in options overwrite Unicode extension keyword.
+dtf = createWithLocale(`${defaultLocale}-u-ca-iso8601`, "japanese");
+assertEq(dtf.resolvedOptions().locale, defaultLocale);
+assertEq(dtf.resolvedOptions().calendar, "japanese");
+
+// |calendar| option ignores case.
+dtf = create("CHINESE");
+assertEq(dtf.resolvedOptions().locale, defaultLocale);
+assertEq(dtf.resolvedOptions().calendar, "chinese");
+
+const calendars = [
+ "buddhist", "chinese", "coptic", "dangi", "ethioaa", "ethiopic-amete-alem",
+ "ethiopic", "gregory", "hebrew", "indian", "islamic", "islamic-umalqura",
+ "islamic-tbla", "islamic-civil", "islamic-rgsa", "iso8601", "japanese",
+ "persian", "roc", "islamicc",
+];
+
+// https://github.com/tc39/proposal-intl-locale/issues/96
+const canonical = {
+ "islamicc": "islamic-civil",
+ "ethiopic-amete-alem": "ethioaa",
+};
+
+for (let calendar of calendars) {
+ let dtf1 = new Intl.DateTimeFormat(`${defaultLocale}-u-ca-${calendar}`);
+ let dtf2 = new Intl.DateTimeFormat(defaultLocale, {calendar});
+
+ assertEq(dtf1.resolvedOptions().calendar, canonical[calendar] ?? calendar);
+ assertEq(dtf2.resolvedOptions().calendar, canonical[calendar] ?? calendar);
+
+ assertEq(dtf2.format(0), dtf1.format(0));
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/call.js b/js/src/tests/non262/Intl/DateTimeFormat/call.js
new file mode 100644
index 0000000000..e2b7334789
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/call.js
@@ -0,0 +1,182 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+function IsIntlService(c) {
+ return typeof c === "function" &&
+ c.hasOwnProperty("prototype") &&
+ c.prototype.hasOwnProperty("resolvedOptions");
+}
+
+function IsObject(o) {
+ return Object(o) === o;
+}
+
+function IsPrimitive(o) {
+ return Object(o) !== o;
+}
+
+function thisValues() {
+ const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsIntlService);
+
+ return [
+ // Primitive values.
+ ...[undefined, null, true, "abc", Symbol(), 123],
+
+ // Object values.
+ ...[{}, [], /(?:)/, function(){}, new Proxy({}, {})],
+
+ // Intl objects.
+ ...[].concat(...intlConstructors.map(ctor => {
+ let args = [];
+ if (ctor === Intl.DisplayNames) {
+ // Intl.DisplayNames can't be constructed without any arguments.
+ args = [undefined, {type: "language"}];
+ }
+
+ return [
+ // Instance of an Intl constructor.
+ new ctor(...args),
+
+ // Instance of a subclassed Intl constructor.
+ new class extends ctor {}(...args),
+
+ // Object inheriting from an Intl constructor prototype.
+ Object.create(ctor.prototype),
+
+ // Intl object not inheriting from its default prototype.
+ Object.setPrototypeOf(new ctor(...args), Object.prototype),
+ ];
+ })),
+ ];
+}
+
+const intlFallbackSymbol = Object.getOwnPropertySymbols(Intl.DateTimeFormat.call(Object.create(Intl.DateTimeFormat.prototype)))[0];
+
+// Invoking [[Call]] for Intl.DateTimeFormat returns a new instance unless called
+// with an instance inheriting from Intl.DateTimeFormat.prototype.
+for (let thisValue of thisValues()) {
+ let obj = Intl.DateTimeFormat.call(thisValue);
+
+ if (!Intl.DateTimeFormat.prototype.isPrototypeOf(thisValue)) {
+ assertEq(Object.is(obj, thisValue), false);
+ assertEq(obj instanceof Intl.DateTimeFormat, true);
+ if (IsObject(thisValue))
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+ } else {
+ assertEq(Object.is(obj, thisValue), true);
+ assertEq(obj instanceof Intl.DateTimeFormat, true);
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+ }
+}
+
+// Intl.DateTimeFormat uses the legacy Intl constructor compromise semantics.
+// - Test when InstanceofOperator(thisValue, %DateTimeFormat%) returns true.
+for (let thisValue of thisValues().filter(IsObject)) {
+ let hasInstanceCalled = false;
+ Object.defineProperty(Intl.DateTimeFormat, Symbol.hasInstance, {
+ value() {
+ assertEq(hasInstanceCalled, false);
+ hasInstanceCalled = true;
+ return true;
+ }, configurable: true
+ });
+ let obj = Intl.DateTimeFormat.call(thisValue);
+ delete Intl.DateTimeFormat[Symbol.hasInstance];
+
+ assertEq(Object.is(obj, thisValue), true);
+ assertEq(hasInstanceCalled, true);
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+}
+// - Test when InstanceofOperator(thisValue, %DateTimeFormat%) returns false.
+for (let thisValue of thisValues().filter(IsObject)) {
+ let hasInstanceCalled = false;
+ Object.defineProperty(Intl.DateTimeFormat, Symbol.hasInstance, {
+ value() {
+ assertEq(hasInstanceCalled, false);
+ hasInstanceCalled = true;
+ return false;
+ }, configurable: true
+ });
+ let obj = Intl.DateTimeFormat.call(thisValue);
+ delete Intl.DateTimeFormat[Symbol.hasInstance];
+
+ assertEq(Object.is(obj, thisValue), false);
+ assertEq(obj instanceof Intl.DateTimeFormat, true);
+ assertEq(hasInstanceCalled, true);
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+}
+// - Test with primitive values.
+for (let thisValue of thisValues().filter(IsPrimitive)) {
+ // Ensure @@hasInstance is not called.
+ Object.defineProperty(Intl.DateTimeFormat, Symbol.hasInstance, {
+ value() { assertEq(true, false); }, configurable: true
+ });
+ let obj = Intl.DateTimeFormat.call(thisValue);
+ delete Intl.DateTimeFormat[Symbol.hasInstance];
+
+ assertEq(Object.is(obj, thisValue), false);
+ assertEq(obj instanceof Intl.DateTimeFormat, true);
+}
+
+// Throws an error when attempting to install [[FallbackSymbol]] twice.
+{
+ let thisValue = Object.create(Intl.DateTimeFormat.prototype);
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+
+ assertEq(Intl.DateTimeFormat.call(thisValue), thisValue);
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+
+ assertThrowsInstanceOf(() => Intl.DateTimeFormat.call(thisValue), TypeError);
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+}
+
+// Throws an error when the thisValue is non-extensible.
+{
+ let thisValue = Object.create(Intl.DateTimeFormat.prototype);
+ Object.preventExtensions(thisValue);
+
+ assertThrowsInstanceOf(() => Intl.DateTimeFormat.call(thisValue), TypeError);
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+}
+
+// [[FallbackSymbol]] is installed as a frozen property holding an Intl.DateTimeFormat instance.
+{
+ let thisValue = Object.create(Intl.DateTimeFormat.prototype);
+ Intl.DateTimeFormat.call(thisValue);
+
+ let desc = Object.getOwnPropertyDescriptor(thisValue, intlFallbackSymbol);
+ assertEq(desc !== undefined, true);
+ assertEq(desc.writable, false);
+ assertEq(desc.enumerable, false);
+ assertEq(desc.configurable, false);
+ assertEq(desc.value instanceof Intl.DateTimeFormat, true);
+}
+
+// Ensure [[FallbackSymbol]] is installed last by changing the [[Prototype]]
+// during initialization.
+{
+ let thisValue = {};
+ let options = {
+ get hour12() {
+ Object.setPrototypeOf(thisValue, Intl.DateTimeFormat.prototype);
+ return false;
+ }
+ };
+ let obj = Intl.DateTimeFormat.call(thisValue, undefined, options);
+ assertEq(Object.is(obj, thisValue), true);
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+}
+{
+ let thisValue = Object.create(Intl.DateTimeFormat.prototype);
+ let options = {
+ get hour12() {
+ Object.setPrototypeOf(thisValue, Object.prototype);
+ return false;
+ }
+ };
+ let obj = Intl.DateTimeFormat.call(thisValue, undefined, options);
+ assertEq(Object.is(obj, thisValue), false);
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/construct-newtarget.js b/js/src/tests/non262/Intl/DateTimeFormat/construct-newtarget.js
new file mode 100644
index 0000000000..bbc08c79a1
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/construct-newtarget.js
@@ -0,0 +1,81 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+// Test subclassing %Intl.DateTimeFormat% works correctly.
+class MyDateTimeFormat extends Intl.DateTimeFormat {}
+
+var obj = new MyDateTimeFormat();
+assertEq(obj instanceof MyDateTimeFormat, true);
+assertEq(obj instanceof Intl.DateTimeFormat, true);
+assertEq(Object.getPrototypeOf(obj), MyDateTimeFormat.prototype);
+
+obj = Reflect.construct(MyDateTimeFormat, []);
+assertEq(obj instanceof MyDateTimeFormat, true);
+assertEq(obj instanceof Intl.DateTimeFormat, true);
+assertEq(Object.getPrototypeOf(obj), MyDateTimeFormat.prototype);
+
+obj = Reflect.construct(MyDateTimeFormat, [], MyDateTimeFormat);
+assertEq(obj instanceof MyDateTimeFormat, true);
+assertEq(obj instanceof Intl.DateTimeFormat, true);
+assertEq(Object.getPrototypeOf(obj), MyDateTimeFormat.prototype);
+
+obj = Reflect.construct(MyDateTimeFormat, [], Intl.DateTimeFormat);
+assertEq(obj instanceof MyDateTimeFormat, false);
+assertEq(obj instanceof Intl.DateTimeFormat, true);
+assertEq(Object.getPrototypeOf(obj), Intl.DateTimeFormat.prototype);
+
+
+// Set a different constructor as NewTarget.
+obj = Reflect.construct(MyDateTimeFormat, [], Array);
+assertEq(obj instanceof MyDateTimeFormat, false);
+assertEq(obj instanceof Intl.DateTimeFormat, false);
+assertEq(obj instanceof Array, true);
+assertEq(Object.getPrototypeOf(obj), Array.prototype);
+
+obj = Reflect.construct(Intl.DateTimeFormat, [], Array);
+assertEq(obj instanceof Intl.DateTimeFormat, false);
+assertEq(obj instanceof Array, true);
+assertEq(Object.getPrototypeOf(obj), Array.prototype);
+
+
+// The prototype defaults to %DateTimeFormatPrototype% if null.
+function NewTargetNullPrototype() {}
+NewTargetNullPrototype.prototype = null;
+
+obj = Reflect.construct(Intl.DateTimeFormat, [], NewTargetNullPrototype);
+assertEq(obj instanceof Intl.DateTimeFormat, true);
+assertEq(Object.getPrototypeOf(obj), Intl.DateTimeFormat.prototype);
+
+obj = Reflect.construct(MyDateTimeFormat, [], NewTargetNullPrototype);
+assertEq(obj instanceof MyDateTimeFormat, false);
+assertEq(obj instanceof Intl.DateTimeFormat, true);
+assertEq(Object.getPrototypeOf(obj), Intl.DateTimeFormat.prototype);
+
+
+// "prototype" property is retrieved exactly once.
+var trapLog = [], getLog = [];
+var ProxiedConstructor = new Proxy(Intl.DateTimeFormat, new Proxy({
+ get(target, propertyKey, receiver) {
+ getLog.push(propertyKey);
+ return Reflect.get(target, propertyKey, receiver);
+ }
+}, {
+ get(target, propertyKey, receiver) {
+ trapLog.push(propertyKey);
+ return Reflect.get(target, propertyKey, receiver);
+ }
+}));
+
+obj = Reflect.construct(Intl.DateTimeFormat, [], ProxiedConstructor);
+assertEqArray(trapLog, ["get"]);
+assertEqArray(getLog, ["prototype"]);
+assertEq(obj instanceof Intl.DateTimeFormat, true);
+assertEq(Object.getPrototypeOf(obj), Intl.DateTimeFormat.prototype);
+
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/cross-compartment.js b/js/src/tests/non262/Intl/DateTimeFormat/cross-compartment.js
new file mode 100644
index 0000000000..dac99bdc19
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/cross-compartment.js
@@ -0,0 +1,70 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+var otherGlobal = newGlobal();
+
+var dateTimeFormat = new Intl.DateTimeFormat();
+var ccwDateTimeFormat = new otherGlobal.Intl.DateTimeFormat();
+
+// Test Intl.DateTimeFormat.prototype.format with a CCW object.
+var Intl_DateTimeFormat_format_get = Object.getOwnPropertyDescriptor(Intl.DateTimeFormat.prototype, "format").get;
+
+assertEq(Intl_DateTimeFormat_format_get.call(ccwDateTimeFormat)(0),
+ Intl_DateTimeFormat_format_get.call(dateTimeFormat)(0));
+
+// Test Intl.DateTimeFormat.prototype.formatToParts with a CCW object.
+var Intl_DateTimeFormat_formatToParts = Intl.DateTimeFormat.prototype.formatToParts;
+
+assertEq(deepEqual(Intl_DateTimeFormat_formatToParts.call(ccwDateTimeFormat, 0),
+ Intl_DateTimeFormat_formatToParts.call(dateTimeFormat, 0)),
+ true);
+
+// Test Intl.DateTimeFormat.prototype.resolvedOptions with a CCW object.
+var Intl_DateTimeFormat_resolvedOptions = Intl.DateTimeFormat.prototype.resolvedOptions;
+
+assertEq(deepEqual(Intl_DateTimeFormat_resolvedOptions.call(ccwDateTimeFormat),
+ Intl_DateTimeFormat_resolvedOptions.call(dateTimeFormat)),
+ true);
+
+// Special case for Intl.DateTimeFormat: The Intl fallback symbol.
+
+function fallbackSymbol(global) {
+ var DTF = global.Intl.DateTimeFormat;
+ return Object.getOwnPropertySymbols(DTF.call(Object.create(DTF.prototype)))[0];
+}
+
+const intlFallbackSymbol = fallbackSymbol(this);
+const otherIntlFallbackSymbol = fallbackSymbol(otherGlobal);
+assertEq(intlFallbackSymbol === otherIntlFallbackSymbol, false);
+
+// Test when the fallback symbol points to a CCW DateTimeFormat object.
+var objWithFallbackCCWDateTimeFormat = {
+ __proto__: Intl.DateTimeFormat.prototype,
+ [intlFallbackSymbol]: ccwDateTimeFormat,
+};
+
+assertEq(Intl_DateTimeFormat_format_get.call(objWithFallbackCCWDateTimeFormat)(0),
+ Intl_DateTimeFormat_format_get.call(dateTimeFormat)(0));
+
+assertEq(deepEqual(Intl_DateTimeFormat_resolvedOptions.call(objWithFallbackCCWDateTimeFormat),
+ Intl_DateTimeFormat_resolvedOptions.call(dateTimeFormat)),
+ true);
+
+// Ensure the fallback symbol(s) are not accessed for CCW DateTimeFormat objects.
+var ccwDateTimeFormatWithPoisonedFallback = new otherGlobal.Intl.DateTimeFormat();
+Object.setPrototypeOf(ccwDateTimeFormatWithPoisonedFallback, Intl.DateTimeFormat.prototype);
+Object.defineProperty(ccwDateTimeFormatWithPoisonedFallback, intlFallbackSymbol, {
+ get() { throw new Error(); }
+});
+Object.defineProperty(ccwDateTimeFormatWithPoisonedFallback, otherIntlFallbackSymbol, {
+ get() { throw new Error(); }
+});
+
+assertEq(Intl_DateTimeFormat_format_get.call(ccwDateTimeFormatWithPoisonedFallback)(0),
+ Intl_DateTimeFormat_format_get.call(dateTimeFormat)(0));
+
+assertEq(deepEqual(Intl_DateTimeFormat_resolvedOptions.call(ccwDateTimeFormatWithPoisonedFallback),
+ Intl_DateTimeFormat_resolvedOptions.call(dateTimeFormat)),
+ true);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/dateTimeStyle.js b/js/src/tests/non262/Intl/DateTimeFormat/dateTimeStyle.js
new file mode 100644
index 0000000000..1dd4c35316
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/dateTimeStyle.js
@@ -0,0 +1,57 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Date style
+
+var dtf = new Intl.DateTimeFormat("en-US", {dateStyle: 'long'});
+assertEq(dtf.resolvedOptions().dateStyle, 'long');
+assertEq(dtf.resolvedOptions().hasOwnProperty('year'), false);
+assertEq(dtf.resolvedOptions().hasOwnProperty('month'), false);
+assertEq(dtf.resolvedOptions().hasOwnProperty('day'), false);
+
+// Time style
+
+var dtf = new Intl.DateTimeFormat("en-US", {timeStyle: 'long'});
+assertEq(dtf.resolvedOptions().timeStyle, 'long');
+assertEq(dtf.resolvedOptions().hasOwnProperty('hour'), false);
+assertEq(dtf.resolvedOptions().hasOwnProperty('minute'), false);
+assertEq(dtf.resolvedOptions().hasOwnProperty('second'), false);
+
+// Date/Time style
+
+var dtf = new Intl.DateTimeFormat("en-US", {dateStyle: 'medium', timeStyle: 'medium'});
+assertEq(dtf.resolvedOptions().timeStyle, 'medium');
+assertEq(dtf.resolvedOptions().dateStyle, 'medium');
+assertEq(dtf.resolvedOptions().hasOwnProperty('hour'), false);
+assertEq(dtf.resolvedOptions().hasOwnProperty('minute'), false);
+assertEq(dtf.resolvedOptions().hasOwnProperty('second'), false);
+assertEq(dtf.resolvedOptions().hasOwnProperty('year'), false);
+assertEq(dtf.resolvedOptions().hasOwnProperty('month'), false);
+assertEq(dtf.resolvedOptions().hasOwnProperty('day'), false);
+
+// Error when mixing date/timeStyle with other date-time options.
+
+assertThrowsInstanceOf(() => {
+ new Intl.DateTimeFormat("en-US", {dateStyle: 'medium', year: 'numeric'});
+}, TypeError);
+
+assertThrowsInstanceOf(() => {
+ new Intl.DateTimeFormat("en-US", {timeStyle: 'medium', year: 'numeric'});
+}, TypeError);
+
+// Error when using dateStyle in toLocaleTimeString.
+
+assertThrowsInstanceOf(() => {
+ new Date().toLocaleTimeString("en-US", {dateStyle: 'long'});
+}, TypeError);
+
+// Error when using dateStyle in toLocaleDateString.
+
+assertThrowsInstanceOf(() => {
+ new Date().toLocaleDateString("en-US", {timeStyle: 'long'});
+}, TypeError);
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, 'ok');
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/day-period-hour-cycle.js b/js/src/tests/non262/Intl/DateTimeFormat/day-period-hour-cycle.js
new file mode 100644
index 0000000000..594b262443
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/day-period-hour-cycle.js
@@ -0,0 +1,102 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||release_or_beta)
+
+const {
+ Year, Month, Day, DayPeriod, Hour, Minute, Literal
+} = DateTimeFormatParts;
+
+// If the locale defaults to a 24-hour-cycle, the "dayPeriod" option is ignored if an "hour" option
+// is also present, unless the hour-cycle is manually set to a 12-hour-cycle.
+
+const tests = [
+ {
+ date: new Date("2019-01-01T12:00:00"),
+ options: { dayPeriod: "short", hour: "numeric", },
+ locales: {
+ en: [Hour("12"), Literal(" "), DayPeriod("noon")],
+ de: [Hour("12"), Literal(" Uhr")],
+ },
+ },
+ {
+ date: new Date("2019-01-01T12:00:00"),
+ options: { dayPeriod: "short", hour: "numeric", hour12: true },
+ locales: {
+ en: [Hour("12"), Literal(" "), DayPeriod("noon")],
+ de: [Hour("12"), Literal(" "), DayPeriod("mittags")],
+ },
+ },
+ {
+ date: new Date("2019-01-01T12:00:00"),
+ options: { dayPeriod: "short", hour: "numeric", hour12: false },
+ locales: {
+ en: [Hour("12")],
+ de: [Hour("12"), Literal(" Uhr")],
+ },
+ },
+ {
+ date: new Date("2019-01-01T12:00:00"),
+ options: { dayPeriod: "short", hour: "numeric", hourCycle: "h12" },
+ locales: {
+ en: [Hour("12"), Literal(" "), DayPeriod("noon")],
+ de: [Hour("12"), Literal(" "), DayPeriod("mittags")],
+ },
+ },
+ {
+ date: new Date("2019-01-01T12:00:00"),
+ options: { dayPeriod: "short", hour: "numeric", hourCycle: "h11" },
+ locales: {
+ en: [Hour("0"), Literal(" "), DayPeriod("noon")],
+ de: [Hour("0"), Literal(" "), DayPeriod("mittags")],
+ },
+ },
+ {
+ date: new Date("2019-01-01T12:00:00"),
+ options: { dayPeriod: "short", hour: "numeric", hourCycle: "h23" },
+ locales: {
+ en: [Hour("12")],
+ de: [Hour("12"), Literal(" Uhr")],
+ },
+ },
+ {
+ date: new Date("2019-01-01T12:00:00"),
+ options: { dayPeriod: "short", hour: "numeric", hourCycle: "h24" },
+ locales: {
+ en: [Hour("12")],
+ de: [Hour("12"), Literal(" Uhr")],
+ },
+ },
+
+ // The default hour-cycle is irrelevant when an "hour" option isn't present.
+ {
+ date: new Date("2019-01-01T12:00:00"),
+ options: { dayPeriod: "short", day: "numeric", month: "numeric", year: "numeric" },
+ locales: {
+ en: [Month("1"), Literal("/"), Day("1"), Literal("/"), Year("2019"), Literal(", "), DayPeriod("noon")],
+ de: [Day("1"), Literal("."), Month("1"), Literal("."), Year("2019"), Literal(", "), DayPeriod("mittags")],
+ },
+ },
+
+ // ICU replacement pattern for missing <appendItem> entries in CLDR.
+ {
+ date: new Date("2019-01-01T12:00:00"),
+ options: { dayPeriod: "short", minute: "numeric" },
+ locales: {
+ en: [Minute("0"), Literal(" (AM/PM: "), DayPeriod("noon"), Literal(")")],
+ de: [Minute("0"), Literal(" (Tageshälfte: "), DayPeriod("mittags"), Literal(")")],
+ },
+ },
+];
+
+for (let {date, options, locales} of tests) {
+ for (let [locale, parts] of Object.entries(locales)) {
+ let dtf = new Intl.DateTimeFormat(locale, options);
+
+ assertEq(dtf.format(date), parts.map(({value}) => value).join(""),
+ `locale=${locale}, date=${date}, options=${JSON.stringify(options)}`);
+
+ assertDeepEq(dtf.formatToParts(date), parts,
+ `locale=${locale}, date=${date}, options=${JSON.stringify(options)}`);
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/day-period-standalone.js b/js/src/tests/non262/Intl/DateTimeFormat/day-period-standalone.js
new file mode 100644
index 0000000000..e888643f1f
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/day-period-standalone.js
@@ -0,0 +1,160 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||release_or_beta)
+
+// Tests using various locales to cover all day period types:
+// "midnight", "noon", "morning1", "morning2", "afternoon1", "afternoon2",
+// "evening1", "evening2", "night1", "night2".
+
+const tests = [
+ {
+ // ICU doesn't support "midnight" and instead uses "night1" resp. "night2".
+ // ICU bug: https://unicode-org.atlassian.net/projects/ICU/issues/ICU-12278
+ date: new Date("2019-01-01T00:00:00"),
+ locales: {
+ en: { narrow: "at night", short: "at night", long: "at night" },
+ de: { narrow: "nachts", short: "nachts", long: "nachts" },
+ th: { narrow: "กลางคืน", short: "กลางคืน", long: "กลางคืน" },
+ ja: { narrow: "夜中", short: "夜中", long: "夜中" },
+ }
+ },
+ {
+ date: new Date("2019-01-01T03:00:00"),
+ locales: {
+ en: { narrow: "at night", short: "at night", long: "at night" },
+ de: { narrow: "nachts", short: "nachts", long: "nachts" },
+ th: { narrow: "กลางคืน", short: "กลางคืน", long: "กลางคืน" },
+ ja: { narrow: "夜中", short: "夜中", long: "夜中" },
+ }
+ },
+ {
+ date: new Date("2019-01-01T04:00:00"),
+ locales: {
+ en: { narrow: "at night", short: "at night", long: "at night" },
+ de: { narrow: "nachts", short: "nachts", long: "nachts" },
+ th: { narrow: "กลางคืน", short: "กลางคืน", long: "กลางคืน" },
+ ja: { narrow: "朝", short: "朝", long: "朝" },
+ }
+ },
+ {
+ date: new Date("2019-01-01T05:00:00"),
+ locales: {
+ en: { narrow: "at night", short: "at night", long: "at night" },
+ de: { narrow: "morgens", short: "morgens", long: "morgens" },
+ th: { narrow: "กลางคืน", short: "กลางคืน", long: "กลางคืน" },
+ ja: { narrow: "朝", short: "朝", long: "朝" },
+ }
+ },
+ {
+ date: new Date("2019-01-01T06:00:00"),
+ locales: {
+ en: { narrow: "in the morning", short: "in the morning", long: "in the morning" },
+ de: { narrow: "morgens", short: "morgens", long: "morgens" },
+ th: { narrow: "เช้า", short: "ในตอนเช้า", long: "ในตอนเช้า" },
+ ja: { narrow: "朝", short: "朝", long: "朝" },
+ }
+ },
+ {
+ date: new Date("2019-01-01T10:00:00"),
+ locales: {
+ en: { narrow: "in the morning", short: "in the morning", long: "in the morning" },
+ de: { narrow: "vorm.", short: "vorm.", long: "vormittags" },
+ th: { narrow: "เช้า", short: "ในตอนเช้า", long: "ในตอนเช้า" },
+ ja: { narrow: "朝", short: "朝", long: "朝" },
+ }
+ },
+ {
+ date: new Date("2019-01-01T12:00:00"),
+ locales: {
+ en: { narrow: "n", short: "noon", long: "noon" },
+ de: { narrow: "mittags", short: "mittags", long: "mittags" },
+ th: { narrow: "เที่ยง", short: "เที่ยง", long: "เที่ยง" },
+ ja: { narrow: "正午", short: "正午", long: "正午" },
+ }
+ },
+ {
+ date: new Date("2019-01-01T13:00:00"),
+ locales: {
+ en: { narrow: "in the afternoon", short: "in the afternoon", long: "in the afternoon" },
+ de: { narrow: "nachm.", short: "nachm.", long: "nachmittags" },
+ th: { narrow: "บ่าย", short: "บ่าย", long: "บ่าย" },
+ ja: { narrow: "昼", short: "昼", long: "昼" },
+ }
+ },
+ {
+ date: new Date("2019-01-01T15:00:00"),
+ locales: {
+ en: { narrow: "in the afternoon", short: "in the afternoon", long: "in the afternoon" },
+ de: { narrow: "nachm.", short: "nachm.", long: "nachmittags" },
+ th: { narrow: "บ่าย", short: "บ่าย", long: "บ่าย" },
+ ja: { narrow: "昼", short: "昼", long: "昼" },
+ }
+ },
+ {
+ date: new Date("2019-01-01T16:00:00"),
+ locales: {
+ en: { narrow: "in the afternoon", short: "in the afternoon", long: "in the afternoon" },
+ de: { narrow: "nachm.", short: "nachm.", long: "nachmittags" },
+ th: { narrow: "เย็น", short: "ในตอนเย็น", long: "ในตอนเย็น" },
+ ja: { narrow: "夕方", short: "夕方", long: "夕方" },
+ }
+ },
+ {
+ date: new Date("2019-01-01T18:00:00"),
+ locales: {
+ en: { narrow: "in the evening", short: "in the evening", long: "in the evening" },
+ de: { narrow: "abends", short: "abends", long: "abends" },
+ th: { narrow: "ค่ำ", short: "ค่ำ", long: "ค่ำ" },
+ ja: { narrow: "夕方", short: "夕方", long: "夕方" },
+ }
+ },
+ {
+ date: new Date("2019-01-01T19:00:00"),
+ locales: {
+ en: { narrow: "in the evening", short: "in the evening", long: "in the evening" },
+ de: { narrow: "abends", short: "abends", long: "abends" },
+ th: { narrow: "ค่ำ", short: "ค่ำ", long: "ค่ำ" },
+ ja: { narrow: "夜", short: "夜", long: "夜" },
+ }
+ },
+ {
+ date: new Date("2019-01-01T21:00:00"),
+ locales: {
+ en: { narrow: "at night", short: "at night", long: "at night" },
+ de: { narrow: "abends", short: "abends", long: "abends" },
+ th: { narrow: "กลางคืน", short: "กลางคืน", long: "กลางคืน" },
+ ja: { narrow: "夜", short: "夜", long: "夜" },
+ }
+ },
+ {
+ date: new Date("2019-01-01T22:00:00"),
+ locales: {
+ en: { narrow: "at night", short: "at night", long: "at night" },
+ de: { narrow: "abends", short: "abends", long: "abends" },
+ th: { narrow: "กลางคืน", short: "กลางคืน", long: "กลางคืน" },
+ ja: { narrow: "夜", short: "夜", long: "夜" },
+ }
+ },
+ {
+ date: new Date("2019-01-01T23:00:00"),
+ locales: {
+ en: { narrow: "at night", short: "at night", long: "at night" },
+ de: { narrow: "abends", short: "abends", long: "abends" },
+ th: { narrow: "กลางคืน", short: "กลางคืน", long: "กลางคืน" },
+ ja: { narrow: "夜中", short: "夜中", long: "夜中" },
+ }
+ },
+];
+
+for (let {date, locales} of tests) {
+ for (let [locale, formats] of Object.entries(locales)) {
+ for (let [dayPeriod, expected] of Object.entries(formats)) {
+ let dtf = new Intl.DateTimeFormat(locale, {dayPeriod});
+
+ assertEq(dtf.format(date), expected,
+ `locale=${locale}, date=${date}, dayPeriod=${dayPeriod}`);
+ assertDeepEq(dtf.formatToParts(date), [{type: "dayPeriod", value: expected}]);
+ }
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/day-period.js b/js/src/tests/non262/Intl/DateTimeFormat/day-period.js
new file mode 100644
index 0000000000..c21890e1b4
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/day-period.js
@@ -0,0 +1,40 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||release_or_beta)
+
+const {
+ Weekday, DayPeriod, Literal
+} = DateTimeFormatParts;
+
+const tests = [
+ // https://unicode-org.atlassian.net/browse/ICU-20741
+ {
+ date: new Date("2019-01-01T12:00:00"),
+ options: { dayPeriod: "long", weekday: "long", },
+ locales: {
+ "en-001": [Weekday("Tuesday"), Literal(", "), DayPeriod("noon")],
+ },
+ },
+
+ // https://unicode-org.atlassian.net/browse/ICU-20740
+ {
+ date: new Date("2019-01-01T12:00:00"),
+ options: { dayPeriod: "narrow", weekday: "long", },
+ locales: {
+ "bs-Cyrl": [Weekday("уторак"), Literal(" "), DayPeriod("подне")],
+ },
+ },
+];
+
+for (let {date, options, locales} of tests) {
+ for (let [locale, parts] of Object.entries(locales)) {
+ let dtf = new Intl.DateTimeFormat(locale, options);
+
+ assertEq(dtf.format(date), parts.map(({value}) => value).join(""),
+ `locale=${locale}, date=${date}, options=${JSON.stringify(options)}`);
+
+ assertDeepEq(dtf.formatToParts(date), parts,
+ `locale=${locale}, date=${date}, options=${JSON.stringify(options)}`);
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/format.js b/js/src/tests/non262/Intl/DateTimeFormat/format.js
new file mode 100644
index 0000000000..2b1d23365f
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/format.js
@@ -0,0 +1,47 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tests the format function with a diverse set of locales and options.
+// Always use UTC to avoid dependencies on test environment.
+
+var format;
+var date = Date.UTC(2012, 11, 12, 3, 0, 0);
+var longFormatOptions = {timeZone: "UTC",
+ year: "numeric", month: "long", day: "numeric",
+ hour: "numeric", minute: "numeric", second: "numeric"};
+
+// Locale en-US; default options.
+format = new Intl.DateTimeFormat("en-us", {timeZone: "UTC"});
+assertEq(format.format(date), "12/12/2012");
+
+// Locale th-TH; default options.
+// Thailand uses the Buddhist calendar.
+format = new Intl.DateTimeFormat("th-th", {timeZone: "UTC"});
+assertEq(format.format(date), "12/12/2555");
+
+// Locale th-TH; long format, Thai digits.
+format = new Intl.DateTimeFormat("th-th-u-nu-thai", longFormatOptions);
+assertEq(format.format(date), "๑๒ ธันวาคม ๒๕๕๕ ๐๓:๐๐:๐๐");
+
+// Locale ja-JP; long format.
+format = new Intl.DateTimeFormat("ja-jp", longFormatOptions);
+assertEq(format.format(date), "2012年12月12日 3:00:00");
+
+// Locale ar-MA; long format, Islamic civilian calendar.
+format = new Intl.DateTimeFormat("ar-ma-u-ca-islamicc", longFormatOptions);
+assertEq(format.format(date), "28 محرم 1434 هـ 03:00:00");
+
+// Locale en-IE: timeZoneName for crash test
+format = new Intl.DateTimeFormat("en-IE", {timeZone: "UTC", timeZoneName: "short"});
+assertEq(format.format(date), "12/12/2012, UTC");
+
+// Test the .name property of the "format" getter.
+var desc = Object.getOwnPropertyDescriptor(Intl.DateTimeFormat.prototype, "format");
+assertEq(desc !== undefined, true);
+assertEq(typeof desc.get, "function");
+assertEq(desc.get.name, "get format");
+
+
+reportCompare(0, 0, 'ok');
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/formatRange-gregorian-proleptic.js b/js/src/tests/non262/Intl/DateTimeFormat/formatRange-gregorian-proleptic.js
new file mode 100644
index 0000000000..1423b0fdf3
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/formatRange-gregorian-proleptic.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||release_or_beta)
+
+// Ensure formatRange() uses a proleptic Gregorian calendar.
+// ICU bug: https://unicode-org.atlassian.net/browse/ICU-20705
+
+let dtf = new Intl.DateTimeFormat("en", {timeZone: "UTC"});
+
+// The Gregorian calendar was first introduced in 15 October 1582.
+const firstGregorian = new Date("1582-10-15");
+assertEq(dtf.formatRange(firstGregorian, firstGregorian), dtf.format(firstGregorian));
+
+// To deal with the ten days' difference between the Julian and the Gregorian calendar, some days
+// were skipped, so that 4 October 1582 was followed by 15 October 1582.
+const lastJulian = new Date("1582-10-04");
+assertEq(dtf.formatRange(lastJulian, lastJulian), dtf.format(lastJulian));
+
+function rangePart(source, parts) {
+ return parts.filter(x => x.source === source).map(x => x.value).join("");
+}
+
+// Test with non-empty range.
+assertEq(rangePart("startRange", dtf.formatRangeToParts(lastJulian, firstGregorian)),
+ dtf.format(lastJulian));
+assertEq(rangePart("endRange", dtf.formatRangeToParts(lastJulian, firstGregorian)),
+ dtf.format(firstGregorian));
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/formatRange-hour-cycle.js b/js/src/tests/non262/Intl/DateTimeFormat/formatRange-hour-cycle.js
new file mode 100644
index 0000000000..c835bb9d06
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/formatRange-hour-cycle.js
@@ -0,0 +1,448 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||release_or_beta)
+
+// Test formatRange supports the different hour-cycle options.
+//
+// ICU bugs:
+// https://unicode-org.atlassian.net/browse/ICU-21154
+// https://unicode-org.atlassian.net/browse/ICU-21155
+// https://unicode-org.atlassian.net/browse/ICU-21156
+
+// Test locales which default to a 12-hour and a 24-hour clock.
+
+let tests = {
+ "en": [
+ // Midnight to morning.
+ {
+ start: 0,
+ end: 10,
+ data: [
+ "12 – 10 AM",
+ "0 AM – 10 AM", // This result, along with others in this file, is caused by ICU-21156.
+ "12 – 10 AM",
+ "00 – 10",
+ "24 – 10",
+ ],
+ },
+ // Midnight to noon.
+ {
+ start: 0,
+ end: 12,
+ data: [
+ "12 AM – 12 PM",
+ "0 AM – 0 PM",
+ "12 AM – 12 PM",
+ "00 – 12",
+ "24 – 12",
+ ],
+ },
+ // Midnight to evening.
+ {
+ start: 0,
+ end: 22,
+ data: [
+ "12 AM – 10 PM",
+ "0 AM – 10 PM",
+ "12 AM – 10 PM",
+ "00 – 22",
+ "24 – 22",
+ ],
+ },
+ // Midnight to midnight.
+ {
+ start: 0,
+ end: 24,
+ data: [
+ "1/1/1970, 12 AM – 1/2/1970, 12 AM",
+ "1/1/1970, 0 AM – 1/2/1970, 0 AM",
+ "1/1/1970, 12 AM – 1/2/1970, 12 AM",
+ "1/1/1970, 00 – 1/2/1970, 00",
+ "1/1/1970, 24 – 1/2/1970, 24",
+ ],
+ },
+
+ // Morning to morning.
+ {
+ start: 1,
+ end: 10,
+ data: [
+ "1 – 10 AM",
+ "1 AM – 10 AM",
+ "1 – 10 AM",
+ "01 – 10",
+ "01 – 10",
+ ],
+ },
+ // Morning to noon.
+ {
+ start: 1,
+ end: 12,
+ data: [
+ "1 AM – 12 PM",
+ "1 AM – 0 PM",
+ "1 AM – 12 PM",
+ "01 – 12",
+ "01 – 12",
+ ],
+ },
+ // Morning to evening.
+ {
+ start: 1,
+ end: 22,
+ data: [
+ "1 AM – 10 PM",
+ "1 AM – 10 PM",
+ "1 AM – 10 PM",
+ "01 – 22",
+ "01 – 22",
+ ],
+ },
+ // Morning to midnight.
+ {
+ start: 1,
+ end: 24,
+ data: [
+ "1/1/1970, 1 AM – 1/2/1970, 12 AM",
+ "1/1/1970, 1 AM – 1/2/1970, 0 AM",
+ "1/1/1970, 1 AM – 1/2/1970, 12 AM",
+ "1/1/1970, 01 – 1/2/1970, 00",
+ "1/1/1970, 01 – 1/2/1970, 24",
+ ],
+ },
+
+ // Noon to morning.
+ {
+ start: 12,
+ end: 24 + 1,
+ data: [
+ "1/1/1970, 12 PM – 1/2/1970, 1 AM",
+ "1/1/1970, 0 PM – 1/2/1970, 1 AM",
+ "1/1/1970, 12 PM – 1/2/1970, 1 AM",
+ "1/1/1970, 12 – 1/2/1970, 01",
+ "1/1/1970, 12 – 1/2/1970, 01",
+ ],
+ },
+ // Noon to noon.
+ {
+ start: 12,
+ end: 24 + 12,
+ data: [
+ "1/1/1970, 12 PM – 1/2/1970, 12 PM",
+ "1/1/1970, 0 PM – 1/2/1970, 0 PM",
+ "1/1/1970, 12 PM – 1/2/1970, 12 PM",
+ "1/1/1970, 12 – 1/2/1970, 12",
+ "1/1/1970, 12 – 1/2/1970, 12",
+ ],
+ },
+ // Noon to evening.
+ {
+ start: 12,
+ end: 22,
+ data: [
+ "12 – 10 PM",
+ "0 PM – 10 PM",
+ "12 – 10 PM",
+ "12 – 22",
+ "12 – 22",
+ ],
+ },
+ // Noon to midnight.
+ {
+ start: 12,
+ end: 24,
+ data: [
+ "1/1/1970, 12 PM – 1/2/1970, 12 AM",
+ "1/1/1970, 0 PM – 1/2/1970, 0 AM",
+ "1/1/1970, 12 PM – 1/2/1970, 12 AM",
+ "1/1/1970, 12 – 1/2/1970, 00",
+ "1/1/1970, 12 – 1/2/1970, 24",
+ ],
+ },
+
+ // Evening to morning.
+ {
+ start: 22,
+ end: 24 + 1,
+ data: [
+ "1/1/1970, 10 PM – 1/2/1970, 1 AM",
+ "1/1/1970, 10 PM – 1/2/1970, 1 AM",
+ "1/1/1970, 10 PM – 1/2/1970, 1 AM",
+ "1/1/1970, 22 – 1/2/1970, 01",
+ "1/1/1970, 22 – 1/2/1970, 01",
+ ],
+ },
+ // Evening to noon.
+ {
+ start: 22,
+ end: 24 + 12,
+ data: [
+ "1/1/1970, 10 PM – 1/2/1970, 12 PM",
+ "1/1/1970, 10 PM – 1/2/1970, 0 PM",
+ "1/1/1970, 10 PM – 1/2/1970, 12 PM",
+ "1/1/1970, 22 – 1/2/1970, 12",
+ "1/1/1970, 22 – 1/2/1970, 12",
+ ],
+ },
+ // Evening to evening.
+ {
+ start: 22,
+ end: 23,
+ data: [
+ "10 – 11 PM",
+ "10 PM – 11 PM",
+ "10 – 11 PM",
+ "22 – 23",
+ "22 – 23",
+ ],
+ },
+ // Evening to midnight.
+ {
+ start: 22,
+ end: 24,
+ data: [
+ "1/1/1970, 10 PM – 1/2/1970, 12 AM",
+ "1/1/1970, 10 PM – 1/2/1970, 0 AM",
+ "1/1/1970, 10 PM – 1/2/1970, 12 AM",
+ "1/1/1970, 22 – 1/2/1970, 00",
+ "1/1/1970, 22 – 1/2/1970, 24",
+ ],
+ },
+ ],
+
+ "de": [
+ // Midnight to morning.
+ {
+ start: 0,
+ end: 10,
+ data: [
+ "00–10 Uhr",
+ "0 Uhr AM – 10 Uhr AM",
+ "12 – 10 Uhr AM",
+ "00–10 Uhr",
+ "24 Uhr – 10 Uhr",
+ ],
+ },
+ // Midnight to noon.
+ {
+ start: 0,
+ end: 12,
+ data: [
+ "00–12 Uhr",
+ "0 Uhr AM – 0 Uhr PM",
+ "12 Uhr AM – 12 Uhr PM",
+ "00–12 Uhr",
+ "24 Uhr – 12 Uhr",
+ ],
+ },
+ // Midnight to evening.
+ {
+ start: 0,
+ end: 22,
+ data: [
+ "00–22 Uhr",
+ "0 Uhr AM – 10 Uhr PM",
+ "12 Uhr AM – 10 Uhr PM",
+ "00–22 Uhr",
+ "24 Uhr – 22 Uhr",
+ ],
+ },
+ // Midnight to midnight.
+ {
+ start: 0,
+ end: 24,
+ data: [
+ "1.1.1970, 00 Uhr – 2.1.1970, 00 Uhr",
+ "1.1.1970, 0 Uhr AM – 2.1.1970, 0 Uhr AM",
+ "1.1.1970, 12 Uhr AM – 2.1.1970, 12 Uhr AM",
+ "1.1.1970, 00 Uhr – 2.1.1970, 00 Uhr",
+ "1.1.1970, 24 Uhr – 2.1.1970, 24 Uhr",
+ ],
+ },
+
+ // Morning to morning.
+ {
+ start: 1,
+ end: 10,
+ data: [
+ "01–10 Uhr",
+ "1 Uhr AM – 10 Uhr AM",
+ "1 – 10 Uhr AM",
+ "01–10 Uhr",
+ "01 Uhr – 10 Uhr",
+ ],
+ },
+ // Morning to noon.
+ {
+ start: 1,
+ end: 12,
+ data: [
+ "01–12 Uhr",
+ "1 Uhr AM – 0 Uhr PM",
+ "1 Uhr AM – 12 Uhr PM",
+ "01–12 Uhr",
+ "01 Uhr – 12 Uhr",
+ ],
+ },
+ // Morning to evening.
+ {
+ start: 1,
+ end: 22,
+ data: [
+ "01–22 Uhr",
+ "1 Uhr AM – 10 Uhr PM",
+ "1 Uhr AM – 10 Uhr PM",
+ "01–22 Uhr",
+ "01 Uhr – 22 Uhr",
+ ],
+ },
+ // Morning to midnight.
+ {
+ start: 1,
+ end: 24,
+ data: [
+ "1.1.1970, 01 Uhr – 2.1.1970, 00 Uhr",
+ "1.1.1970, 1 Uhr AM – 2.1.1970, 0 Uhr AM",
+ "1.1.1970, 1 Uhr AM – 2.1.1970, 12 Uhr AM",
+ "1.1.1970, 01 Uhr – 2.1.1970, 00 Uhr",
+ "1.1.1970, 01 Uhr – 2.1.1970, 24 Uhr",
+ ],
+ },
+
+ // Noon to morning.
+ {
+ start: 12,
+ end: 24 + 1,
+ data: [
+ "1.1.1970, 12 Uhr – 2.1.1970, 01 Uhr",
+ "1.1.1970, 0 Uhr PM – 2.1.1970, 1 Uhr AM",
+ "1.1.1970, 12 Uhr PM – 2.1.1970, 1 Uhr AM",
+ "1.1.1970, 12 Uhr – 2.1.1970, 01 Uhr",
+ "1.1.1970, 12 Uhr – 2.1.1970, 01 Uhr",
+ ],
+ },
+ // Noon to noon.
+ {
+ start: 12,
+ end: 24 + 12,
+ data: [
+ "1.1.1970, 12 Uhr – 2.1.1970, 12 Uhr",
+ "1.1.1970, 0 Uhr PM – 2.1.1970, 0 Uhr PM",
+ "1.1.1970, 12 Uhr PM – 2.1.1970, 12 Uhr PM",
+ "1.1.1970, 12 Uhr – 2.1.1970, 12 Uhr",
+ "1.1.1970, 12 Uhr – 2.1.1970, 12 Uhr",
+ ],
+ },
+ // Noon to evening.
+ {
+ start: 12,
+ end: 22,
+ data: [
+ "12–22 Uhr",
+ "0 Uhr PM – 10 Uhr PM",
+ "12 – 10 Uhr PM",
+ "12–22 Uhr",
+ "12 Uhr – 22 Uhr",
+ ],
+ },
+ // Noon to midnight.
+ {
+ start: 12,
+ end: 24,
+ data: [
+ "1.1.1970, 12 Uhr – 2.1.1970, 00 Uhr",
+ "1.1.1970, 0 Uhr PM – 2.1.1970, 0 Uhr AM",
+ "1.1.1970, 12 Uhr PM – 2.1.1970, 12 Uhr AM",
+ "1.1.1970, 12 Uhr – 2.1.1970, 00 Uhr",
+ "1.1.1970, 12 Uhr – 2.1.1970, 24 Uhr",
+ ],
+ },
+
+ // Evening to morning.
+ {
+ start: 22,
+ end: 24 + 1,
+ data: [
+ "1.1.1970, 22 Uhr – 2.1.1970, 01 Uhr",
+ "1.1.1970, 10 Uhr PM – 2.1.1970, 1 Uhr AM",
+ "1.1.1970, 10 Uhr PM – 2.1.1970, 1 Uhr AM",
+ "1.1.1970, 22 Uhr – 2.1.1970, 01 Uhr",
+ "1.1.1970, 22 Uhr – 2.1.1970, 01 Uhr",
+ ],
+ },
+ // Evening to noon.
+ {
+ start: 22,
+ end: 24 + 12,
+ data: [
+ "1.1.1970, 22 Uhr – 2.1.1970, 12 Uhr",
+ "1.1.1970, 10 Uhr PM – 2.1.1970, 0 Uhr PM",
+ "1.1.1970, 10 Uhr PM – 2.1.1970, 12 Uhr PM",
+ "1.1.1970, 22 Uhr – 2.1.1970, 12 Uhr",
+ "1.1.1970, 22 Uhr – 2.1.1970, 12 Uhr",
+ ],
+ },
+ // Evening to evening.
+ {
+ start: 22,
+ end: 23,
+ data: [
+ "22–23 Uhr",
+ "10 Uhr PM – 11 Uhr PM",
+ "10 – 11 Uhr PM",
+ "22–23 Uhr",
+ "22 Uhr – 23 Uhr",
+ ],
+ },
+ // Evening to midnight.
+ {
+ start: 22,
+ end: 24,
+ data: [
+ "1.1.1970, 22 Uhr – 2.1.1970, 00 Uhr",
+ "1.1.1970, 10 Uhr PM – 2.1.1970, 0 Uhr AM",
+ "1.1.1970, 10 Uhr PM – 2.1.1970, 12 Uhr AM",
+ "1.1.1970, 22 Uhr – 2.1.1970, 00 Uhr",
+ "1.1.1970, 22 Uhr – 2.1.1970, 24 Uhr",
+ ],
+ },
+ ],
+};
+
+const hourCycles = [undefined, "h11", "h12", "h23", "h24"];
+const hour12Values = [true, false];
+const options = {hour: "2-digit", timeZone: "UTC"};
+const hourToMillis = 60 * 60 * 1000;
+
+for (let [locale, test] of Object.entries(tests)) {
+ // Find the matching hourCycle for each hour12 value.
+ let hour12Cycles = hour12Values.map(hour12 => {
+ let {hourCycle} = new Intl.DateTimeFormat(locale, {...options, hour12}).resolvedOptions();
+ return hourCycles.indexOf(hourCycle);
+ });
+
+ for (let {start, end, data} of Object.values(test)) {
+ assertEq(data.length, hourCycles.length);
+
+ // Test all possible "hourCycle" values, including |undefined|.
+ for (let i = 0; i < hourCycles.length; i++) {
+ let hourCycle = hourCycles[i];
+ let expected = data[i];
+ let dtf = new Intl.DateTimeFormat(locale, {...options, hourCycle});
+
+ assertEq(dtf.formatRange(start * hourToMillis, end * hourToMillis), expected,
+ `hourCycle: ${hourCycle}`);
+ }
+
+ // Test all possible "hour12" values.
+ for (let i = 0; i < hour12Values.length; i++) {
+ let hour12 = hour12Values[i];
+ let dtf = new Intl.DateTimeFormat(locale, {...options, hour12});
+ let expected = data[hour12Cycles[i]];
+
+ assertEq(dtf.formatRange(start * hourToMillis, end * hourToMillis), expected,
+ `hour12: ${hour12}`);
+ }
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/formatRange-matches-format-output.js b/js/src/tests/non262/Intl/DateTimeFormat/formatRange-matches-format-output.js
new file mode 100644
index 0000000000..713ba39813
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/formatRange-matches-format-output.js
@@ -0,0 +1,58 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||release_or_beta)
+
+// formatRange() returns the same output as format() when the date-time difference between
+// the start and end date is too small.
+
+// Test case when skeleton can't be retrieved from resolved pattern (bug 1298794).
+// - Best-fit pattern for the skeleton "MMMMMdd" is "M月dd日".
+// - Best-fit pattern for the skeleton "Mdd" is "M/dd".
+//
+// So in both cases the skeleton characters in the pattern are "Mdd", which means we can't
+// retrieve the original input skeleton by simply inspecting the resolved pattern.
+//
+// Also see: https://unicode-org.atlassian.net/browse/ICU-13518
+{
+ let dtf = new Intl.DateTimeFormat("zh", {month: "narrow", day: "2-digit", timeZone: "UTC"});
+ assertEq(dtf.formatRange(0, 0), dtf.format(0));
+}
+
+// Test that date-/time-style leads to the same output.
+// ICU bug: https://unicode-org.atlassian.net/browse/ICU-20710
+{
+ let dtf = new Intl.DateTimeFormat("en", {dateStyle: "full", timeStyle: "full"});
+ assertEq(dtf.formatRange(0, 0), dtf.format(0));
+}
+
+// Test that the hourCycle option is correctly processed (test with h24).
+// ICU bug: https://unicode-org.atlassian.net/browse/ICU-20707
+{
+ let dtf = new Intl.DateTimeFormat("en-u-hc-h24", {hour: "2-digit", timeZone:"UTC"});
+ assertEq(dtf.formatRange(0, 0), dtf.format(0));
+}
+{
+ let dtf = new Intl.DateTimeFormat("en", {hourCycle: "h24", hour: "2-digit", timeZone:"UTC"});
+ assertEq(dtf.formatRange(0, 0), dtf.format(0));
+}
+
+// Test that the hourCycle option is correctly processed (test with h11).
+// ICU bug: https://unicode-org.atlassian.net/browse/ICU-20707
+{
+ let dt = 60 * 60 * 1000; // one hour
+ let dtf = new Intl.DateTimeFormat("en-u-hc-h11", {hour: "2-digit", timeZone:"UTC"});
+ assertEq(dtf.formatRange(dt, dt), dtf.format(dt));
+}
+{
+ let dt = 60 * 60 * 1000; // one hour
+ let dtf = new Intl.DateTimeFormat("en", {hourCycle: "h11", hour: "2-digit", timeZone:"UTC"});
+ assertEq(dtf.formatRange(dt, dt), dtf.format(dt));
+}
+
+// Test that non-default calendars work correctly.
+// ICU bug: https://unicode-org.atlassian.net/browse/ICU-20706
+{
+ let dtf = new Intl.DateTimeFormat("en-001-u-ca-hebrew");
+ assertEq(dtf.formatRange(0, 0), dtf.format(0));
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/formatToParts.js b/js/src/tests/non262/Intl/DateTimeFormat/formatToParts.js
new file mode 100644
index 0000000000..62f0d69406
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/formatToParts.js
@@ -0,0 +1,96 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+// Tests the format function with a diverse set of locales and options.
+// Always use UTC to avoid dependencies on test environment.
+
+const {
+ Era, Year, Month, Weekday, Day, DayPeriod, Hour, Minute, Second, Literal
+} = DateTimeFormatParts
+
+var format;
+var date = Date.UTC(2012, 11, 17, 3, 0, 42);
+
+// Locale en-US; default options.
+format = new Intl.DateTimeFormat("en-us", {timeZone: "UTC"});
+assertParts(format, date, [
+ Month("12"), Literal("/"), Day("17"), Literal("/"), Year("2012"),
+]);
+
+// Just date
+format = new Intl.DateTimeFormat("en-us", {
+ year: 'numeric',
+ month: 'numeric',
+ day: 'numeric',
+ timeZone: "UTC"});
+assertParts(format, date, [
+ Month("12"), Literal("/"), Day("17"), Literal("/"), Year("2012"),
+]);
+
+// Just time in hour24
+format = new Intl.DateTimeFormat("en-us", {
+ hour: 'numeric',
+ minute: 'numeric',
+ second: 'numeric',
+ hour12: false,
+ timeZone: "UTC"});
+assertParts(format, date, [
+ Hour("03"), Literal(":"), Minute("00"), Literal(":"), Second("42"),
+]);
+
+// Just time in hour12
+format = new Intl.DateTimeFormat("en-us", {
+ hour: 'numeric',
+ minute: 'numeric',
+ second: 'numeric',
+ hour12: true,
+ timeZone: "UTC"});
+assertParts(format, date, [
+ Hour("3"), Literal(":"), Minute("00"), Literal(":"), Second("42"), Literal(" "), DayPeriod("AM"),
+]);
+
+// Just month.
+format = new Intl.DateTimeFormat("en-us", {
+ month: "narrow",
+ timeZone: "UTC"});
+assertParts(format, date, [
+ Month("D"),
+]);
+
+// Just weekday.
+format = new Intl.DateTimeFormat("en-us", {
+ weekday: "narrow",
+ timeZone: "UTC"});
+assertParts(format, date, [
+ Weekday("M"),
+]);
+
+// Year and era.
+format = new Intl.DateTimeFormat("en-us", {
+ year: "numeric",
+ era: "short",
+ timeZone: "UTC"});
+assertParts(format, date, [
+ Year("2012"), Literal(" "), Era("AD"),
+]);
+
+// Time and date
+format = new Intl.DateTimeFormat("en-us", {
+ weekday: 'long',
+ year: 'numeric',
+ month: 'numeric',
+ day: 'numeric',
+ hour: 'numeric',
+ minute: 'numeric',
+ second: 'numeric',
+ hour12: true,
+ timeZone: "UTC"});
+assertParts(format, date, [
+ Weekday("Monday"), Literal(", "), Month("12"), Literal("/"), Day("17"), Literal("/"), Year("2012"),
+ Literal(", "),
+ Hour("3"), Literal(":"), Minute("00"), Literal(":"), Second("42"), Literal(" "), DayPeriod("AM"),
+]);
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, 'ok');
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/format_timeZone-non-meta.js b/js/src/tests/non262/Intl/DateTimeFormat/format_timeZone-non-meta.js
new file mode 100644
index 0000000000..f3de06bb97
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/format_timeZone-non-meta.js
@@ -0,0 +1,62 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Ensure we provide 'long' and 'short' descriptive names for non-meta time
+// zones. (CLDR stores names for Etc/UTC, Europe/Dublin, and Europe/London as
+// non-meta zones.)
+
+const date = new Date(Date.UTC(2018, 7-1, 24));
+const tests = [
+ {
+ locale: "en-US",
+ timeZoneName: "long",
+ zones: {
+ "Etc/UTC": "7/24/2018, Coordinated Universal Time",
+ "Europe/Dublin": "7/24/2018, Irish Standard Time",
+ "Europe/London": "7/24/2018, British Summer Time",
+ }
+ },
+ {
+ locale: "de",
+ timeZoneName: "long",
+ zones: {
+ "Etc/UTC": "24.7.2018, Koordinierte Weltzeit",
+ "Europe/Dublin": "24.7.2018, Irische Sommerzeit",
+ "Europe/London": "24.7.2018, Britische Sommerzeit",
+ }
+ },
+
+ {
+ locale: "en-US",
+ timeZoneName: "short",
+ zones: {
+ "Europe/Dublin": "7/24/2018, GMT+1",
+ "Europe/London": "7/24/2018, GMT+1",
+ }
+ },
+ {
+ locale: "en-GB",
+ timeZoneName: "short",
+ zones: {
+ "Europe/Dublin": "24/07/2018, GMT+1",
+ "Europe/London": "24/07/2018, BST",
+ }
+ },
+ {
+ locale: "en-IE",
+ timeZoneName: "short",
+ zones: {
+ "Europe/Dublin": "24/7/2018, IST",
+ "Europe/London": "24/7/2018, GMT+1",
+ }
+ },
+];
+
+for (let {locale, timeZoneName, zones} of tests) {
+ for (let [timeZone, expected] of Object.entries(zones)) {
+ assertEq(new Intl.DateTimeFormat(locale, {timeZone, timeZoneName}).format(date),
+ expected);
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/format_timeZone.js b/js/src/tests/non262/Intl/DateTimeFormat/format_timeZone.js
new file mode 100644
index 0000000000..8b9f5b0c94
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/format_timeZone.js
@@ -0,0 +1,104 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const defaultLocale = "en-US";
+const defaultDate = Date.UTC(2012, 12-1, 6, 12, 0, 0);
+const defaultOptions = {
+ year: "numeric", month: "numeric", day: "numeric",
+ hour: "numeric", minute: "numeric", second: "numeric",
+};
+const longFormatOptions = Object.assign({}, defaultOptions, {
+ month: "long"
+});
+const tzNameFormatOptions = Object.assign({}, defaultOptions, {
+ timeZoneName: "short"
+});
+
+const tzMapper = [
+ x => x,
+ x => x.toUpperCase(),
+ x => x.toLowerCase(),
+];
+
+const tests = [
+ {
+ timeZone: "UTC",
+ result: "12/6/2012, 12:00:00 PM",
+ },
+ {
+ timeZone: "America/Los_Angeles",
+ result: "12/6/2012, 4:00:00 AM",
+ },
+ {
+ timeZone: "America/New_York",
+ options: tzNameFormatOptions,
+ result: "12/6/2012, 7:00:00 AM EST",
+ },
+ {
+ timeZone: "America/Caracas",
+ result: "12/6/2012, 7:30:00 AM",
+ },
+ {
+ timeZone: "Europe/London",
+ result: "12/6/2012, 12:00:00 PM",
+ },
+ {
+ timeZone: "Africa/Casablanca",
+ locale: "ar-MA-u-ca-islamicc", options: longFormatOptions,
+ result: "22 محرم 1434 هـ 12:00:00",
+ },
+ {
+ timeZone: "Europe/Berlin",
+ locale: "de-DE", options: tzNameFormatOptions,
+ result: "6.12.2012, 13:00:00 MEZ",
+ },
+ {
+ timeZone: "Asia/Kathmandu",
+ result: "12/6/2012, 5:45:00 PM",
+ },
+ {
+ timeZone: "Asia/Bangkok",
+ locale: "th-th-u-nu-thai", options: longFormatOptions,
+ result: "๖ ธันวาคม ๒๕๕๕ ๑๙:๐๐:๐๐",
+ },
+ {
+ timeZone: "Asia/Tokyo",
+ locale: "ja-JP", options: longFormatOptions,
+ result: "2012年12月6日 21:00:00",
+ },
+ {
+ timeZone: "Australia/Lord_Howe",
+ result: "12/6/2012, 11:00:00 PM",
+ },
+ {
+ timeZone: "Australia/Lord_Howe",
+ date: Date.UTC(2012, 7-1, 6, 12, 0, 0),
+ result: "7/6/2012, 10:30:00 PM",
+ },
+ {
+ timeZone: "Pacific/Kiritimati",
+ date: Date.UTC(1978, 12-1, 6, 12, 0, 0),
+ result: "12/6/1978, 1:20:00 AM",
+ },
+ {
+ timeZone: "Africa/Monrovia",
+ date: Date.UTC(1971, 12-1, 6, 12, 0, 0),
+ result: "12/6/1971, 11:15:30 AM",
+ },
+ {
+ timeZone: "Asia/Riyadh",
+ date: Date.UTC(1946, 12-1, 6, 12, 0, 0),
+ result: "12/6/1946, 3:06:52 PM",
+ },
+];
+
+for (let {timeZone, result, locale = defaultLocale, date = defaultDate, options = defaultOptions} of tests) {
+ for (let map of tzMapper) {
+ let dtf = new Intl.DateTimeFormat(locale, Object.assign({timeZone: map(timeZone)}, options));
+ assertEq(dtf.format(date), result);
+ assertEq(dtf.resolvedOptions().timeZone, timeZone);
+ }
+}
+
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/fractional-second-digits-append-item.js b/js/src/tests/non262/Intl/DateTimeFormat/fractional-second-digits-append-item.js
new file mode 100644
index 0000000000..c85b310e56
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/fractional-second-digits-append-item.js
@@ -0,0 +1,67 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+if (typeof getBuildConfiguration === "undefined") {
+ var getBuildConfiguration = SpecialPowers.Cu.getJSTestingFunctions().getBuildConfiguration;
+}
+
+var isNightly = !getBuildConfiguration().release_or_beta;
+
+const {
+ DayPeriod, Hour, Minute, Second, FractionalSecond, Literal
+} = DateTimeFormatParts
+
+const tests = [
+ // https://unicode-org.atlassian.net/browse/CLDR-13184
+ // https://unicode-org.atlassian.net/browse/CLDR-13623
+ {
+ locale: "en",
+ date: new Date("2020-01-01T00:00:00.123"),
+ options: {hour: "numeric", fractionalSecondDigits: 3},
+ parts: [
+ Hour("12"),
+ Literal(" "),
+ DayPeriod("AM"),
+ Literal(" (Fractional Second: "),
+ FractionalSecond("123"),
+ Literal(")")
+ ]
+ },
+
+ // https://unicode-org.atlassian.net/browse/ICU-20992
+ {
+ locale: "ckb-IR",
+ date: new Date("2020-01-01T00:00:00.123"),
+ options: {minute: "2-digit", fractionalSecondDigits: 3},
+ parts: [
+ Second("٠"),
+ Literal("٫"),
+ FractionalSecond("١٢٣"),
+ Literal(" (Minute: "),
+ Minute("٠"),
+ Literal(")")
+ ]
+ },
+];
+
+if (isNightly) {
+ // https://unicode-org.atlassian.net/browse/ICU-20992
+ tests.push({
+ locale: "ckb-IR",
+ date: new Date("2020-01-01T00:00:00.123"),
+ options: {dayPeriod: "short", fractionalSecondDigits: 3},
+ parts: [
+ FractionalSecond("١٢٣"),
+ Literal(" (Dayperiod: "),
+ DayPeriod("ب.ن"),
+ Literal(")")
+ ]
+ });
+}
+
+for (let {locale, date, options, parts} of tests) {
+ let dtf = new Intl.DateTimeFormat(locale, options);
+ assertParts(dtf, date, parts);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/fractional-second-digits.js b/js/src/tests/non262/Intl/DateTimeFormat/fractional-second-digits.js
new file mode 100644
index 0000000000..c6c29be604
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/fractional-second-digits.js
@@ -0,0 +1,43 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const {
+ Second, FractionalSecond, Literal
+} = DateTimeFormatParts
+
+const tests = [
+ {
+ date: new Date("2019-01-01T00:00:00.123"),
+ digits: {
+ 1: [Second("0"), Literal("."), FractionalSecond("1")],
+ 2: [Second("0"), Literal("."), FractionalSecond("12")],
+ 3: [Second("0"), Literal("."), FractionalSecond("123")],
+ }
+ },
+ {
+ date: new Date("2019-01-01T00:00:00.023"),
+ digits: {
+ 1: [Second("0"), Literal("."), FractionalSecond("0")],
+ 2: [Second("0"), Literal("."), FractionalSecond("02")],
+ 3: [Second("0"), Literal("."), FractionalSecond("023")],
+ }
+ },
+ {
+ date: new Date("2019-01-01T00:00:00.003"),
+ digits: {
+ 1: [Second("0"), Literal("."), FractionalSecond("0")],
+ 2: [Second("0"), Literal("."), FractionalSecond("00")],
+ 3: [Second("0"), Literal("."), FractionalSecond("003")],
+ }
+ },
+];
+
+for (let {date, digits} of tests) {
+ for (let [fractionalSecondDigits, parts] of Object.entries(digits)) {
+ let dtf = new Intl.DateTimeFormat("en", {second: "numeric", fractionalSecondDigits});
+
+ assertParts(dtf, date, parts);
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/hourCycle.js b/js/src/tests/non262/Intl/DateTimeFormat/hourCycle.js
new file mode 100644
index 0000000000..21414c72cf
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/hourCycle.js
@@ -0,0 +1,142 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const hourCycleToH12Map = {
+ "h11": true,
+ "h12": true,
+ "h23": false,
+ "h24": false,
+};
+
+for (const key of Object.keys(hourCycleToH12Map)) {
+ const langTag = "en-US";
+ const loc = `${langTag}-u-hc-${key}`;
+
+ const dtf = new Intl.DateTimeFormat(loc, {hour: "numeric"});
+ const dtf2 = new Intl.DateTimeFormat(langTag, {hour: "numeric", hourCycle: key});
+ assertEq(dtf.resolvedOptions().hourCycle, dtf2.resolvedOptions().hourCycle);
+}
+
+
+/* Legacy hour12 compatibility */
+
+// When constructed with hourCycle option, resolvedOptions' hour12 is correct.
+for (const key of Object.keys(hourCycleToH12Map)) {
+ const dtf = new Intl.DateTimeFormat("en-US", {hour: "numeric", hourCycle: key});
+ assertEq(dtf.resolvedOptions().hour12, hourCycleToH12Map[key]);
+}
+
+// When constructed with hour12 option, resolvedOptions' hourCycle is correct
+for (const [key, value] of Object.entries(hourCycleToH12Map)) {
+ const dtf = new Intl.DateTimeFormat("en-US", {hour: "numeric", hour12: value});
+ assertEq(hourCycleToH12Map[dtf.resolvedOptions().hourCycle], value);
+}
+
+// When constructed with both hour12 and hourCycle options that don't match
+// hour12 takes a precedence.
+for (const [key, value] of Object.entries(hourCycleToH12Map)) {
+ const dtf = new Intl.DateTimeFormat("en-US", {
+ hour: "numeric",
+ hourCycle: key,
+ hour12: !value
+ });
+ assertEq(hourCycleToH12Map[dtf.resolvedOptions().hourCycle], !value);
+ assertEq(dtf.resolvedOptions().hour12, !value);
+}
+
+// When constructed with hourCycle as extkey, resolvedOptions' hour12 is correct.
+for (const [key, value] of Object.entries(hourCycleToH12Map)) {
+ const langTag = "en-US";
+ const loc = `${langTag}-u-hc-${key}`;
+
+ const dtf = new Intl.DateTimeFormat(loc, {hour: "numeric"});
+ assertEq(dtf.resolvedOptions().hour12, value);
+}
+
+const expectedValuesENUS = {
+ h11: "0 AM",
+ h12: "12 AM",
+ h23: "00",
+ h24: "24"
+};
+
+const exampleDate = new Date(2017, 10-1, 10, 0);
+for (const [key, val] of Object.entries(expectedValuesENUS)) {
+ assertEq(
+ Intl.DateTimeFormat("en-US", {hour: "numeric", hourCycle: key}).format(exampleDate),
+ val
+ );
+}
+
+const invalidHourCycleValues = [
+ "h28",
+ "f28",
+];
+
+for (const key of invalidHourCycleValues) {
+ const langTag = "en-US";
+ const loc = `${langTag}-u-hc-${key}`;
+
+ const dtf = new Intl.DateTimeFormat(loc, {hour: "numeric"});
+ assertEq(dtf.resolvedOptions().hour12, true); // default value for en-US
+ assertEq(dtf.resolvedOptions().hourCycle, "h12"); //default value for en-US
+}
+
+{
+ // hourCycle is not present in resolvedOptions when the formatter has no hour field
+ const options = Intl.DateTimeFormat("en-US", {hourCycle:"h11"}).resolvedOptions();
+ assertEq("hourCycle" in options, false);
+ assertEq("hour12" in options, false);
+}
+
+{
+ // Make sure that hourCycle option overrides the unicode extension
+ let dtf = Intl.DateTimeFormat("en-US-u-hc-h23", {hourCycle: "h24", hour: "numeric"});
+ assertEq(
+ dtf.resolvedOptions().hourCycle,
+ "h24"
+ );
+}
+
+{
+ // Make sure that hour12 option overrides the unicode extension
+ let dtf = Intl.DateTimeFormat("en-US-u-hc-h23", {hour12: true, hour: "numeric"});
+ assertEq(
+ dtf.resolvedOptions().hourCycle,
+ "h12"
+ );
+}
+
+{
+ // Make sure that hour12 option overrides hourCycle options
+ let dtf = Intl.DateTimeFormat("en-US",
+ {hourCycle: "h12", hour12: false, hour: "numeric"});
+ assertEq(
+ dtf.resolvedOptions().hourCycle,
+ "h23"
+ );
+}
+
+{
+ // Make sure that hour12 option overrides hourCycle options
+ let dtf = Intl.DateTimeFormat("en-u-hc-h11", {hour: "numeric"});
+ assertEq(
+ dtf.resolvedOptions().locale,
+ "en-u-hc-h11"
+ );
+}
+
+{
+ // Make sure that hour12 option overrides unicode extension
+ let dtf = Intl.DateTimeFormat("en-u-hc-h11", {hour: "numeric", hourCycle: "h24"});
+ assertEq(
+ dtf.resolvedOptions().locale,
+ "en"
+ );
+ assertEq(
+ dtf.resolvedOptions().hourCycle,
+ "h24"
+ );
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/islamic.js b/js/src/tests/non262/Intl/DateTimeFormat/islamic.js
new file mode 100644
index 0000000000..3a88590ea5
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/islamic.js
@@ -0,0 +1,89 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+function civilDate(options, date) {
+ var opts = Object.assign({timeZone: "Asia/Riyadh"}, options);
+ return new Intl.DateTimeFormat("ar-SA-u-ca-islamic-civil-nu-latn", opts).format(date);
+}
+
+function tabularDate(options, date) {
+ var opts = Object.assign({timeZone: "Asia/Riyadh"}, options);
+ return new Intl.DateTimeFormat("ar-SA-u-ca-islamic-tbla-nu-latn", opts).format(date);
+}
+
+function sightingDate(options, date) {
+ var opts = Object.assign({timeZone: "Asia/Riyadh"}, options);
+ return new Intl.DateTimeFormat("ar-SA-u-ca-islamic-rgsa-nu-latn", opts).format(date);
+}
+
+function ummAlQuraDate(options, date) {
+ var opts = Object.assign({timeZone: "Asia/Riyadh"}, options);
+ return new Intl.DateTimeFormat("ar-SA-u-ca-umalqura-nu-latn", opts).format(date);
+}
+
+// Test islamic-tbla (Tabular / Thursday epoch).
+// Compare with islamic-civil (Tabular / Friday epoch).
+function testIslamicTbla() {
+ var date = new Date(Date.UTC(2015, 1 - 1, 1));
+
+ // Month and year are the same.
+ var monthYear = {year: "numeric", month: "numeric"};
+ assertEq(civilDate(monthYear, date), tabularDate(monthYear, date));
+
+ // Day is different by one.
+ var day = {day: "numeric"};
+ assertEq(Number(civilDate(day, date)) - Number(tabularDate(day, date)), -1);
+}
+
+// Test islamic-rgsa (Saudi Arabia sighting).
+// Sighting of the hilal (crescent moon) in Saudi Arabia.
+function testIslamicRgsa() {
+ var date1 = new Date(Date.UTC(1975, 5 - 1, 6));
+ var date2 = new Date(Date.UTC(2015, 1 - 1, 1));
+ var dayMonthYear = {year: "numeric", month: "numeric", day: "numeric"};
+
+ assertEq(sightingDate(dayMonthYear, date1), tabularDate(dayMonthYear, date1));
+ assertEq(sightingDate(dayMonthYear, date2), civilDate(dayMonthYear, date2));
+}
+
+// Test islamic-umalqura (Umm al-Qura).
+function testIslamicUmalqura() {
+ var year = {year: "numeric"};
+ var month = {month: "numeric"};
+ var day = {day: "numeric"};
+
+ // From ICU test files, which in turn was generated from:
+ // Official Umm-al-Qura calendar of SA:
+ // home, http://www.ummulqura.org.sa/default.aspx
+ // converter, http://www.ummulqura.org.sa/Index.aspx
+ var dates = [
+ [ {year: 2016, month: 1, day: 11}, {year: 1437, month: 4, day: 1} ],
+ [ {year: 2016, month: 2, day: 10}, {year: 1437, month: 5, day: 1} ],
+ [ {year: 2016, month: 3, day: 10}, {year: 1437, month: 6, day: 1} ],
+ [ {year: 2016, month: 4, day: 8}, {year: 1437, month: 7, day: 1} ],
+ [ {year: 2016, month: 5, day: 8}, {year: 1437, month: 8, day: 1} ],
+ [ {year: 2016, month: 6, day: 6}, {year: 1437, month: 9, day: 1} ],
+ [ {year: 2016, month: 7, day: 6}, {year: 1437, month: 10, day: 1} ],
+ [ {year: 2016, month: 8, day: 4}, {year: 1437, month: 11, day: 1} ],
+ [ {year: 2016, month: 9, day: 2}, {year: 1437, month: 12, day: 1} ],
+ [ {year: 2016, month: 10, day: 2}, {year: 1438, month: 1, day: 1} ],
+ [ {year: 2016, month: 11, day: 1}, {year: 1438, month: 2, day: 1} ],
+ [ {year: 2016, month: 11, day: 30}, {year: 1438, month: 3, day: 1} ],
+ [ {year: 2016, month: 12, day: 30}, {year: 1438, month: 4, day: 1} ],
+ ];
+
+ for (var [gregorian, ummAlQura] of dates) {
+ var date = new Date(Date.UTC(gregorian.year, gregorian.month - 1, gregorian.day));
+
+ // Use parseInt() to remove the trailing era indicator.
+ assertEq(parseInt(ummAlQuraDate(year, date), 10), ummAlQura.year);
+ assertEq(Number(ummAlQuraDate(month, date)), ummAlQura.month);
+ assertEq(Number(ummAlQuraDate(day, date)), ummAlQura.day);
+ }
+}
+
+testIslamicTbla();
+testIslamicRgsa();
+testIslamicUmalqura();
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/japanese-gannen-year.js b/js/src/tests/non262/Intl/DateTimeFormat/japanese-gannen-year.js
new file mode 100644
index 0000000000..81ee054b0a
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/japanese-gannen-year.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+var dtf = new Intl.DateTimeFormat("ja-u-ca-japanese", {
+ era: "short",
+ timeZone: "Asia/Tokyo",
+});
+
+var endShowa = new Date("1989-01-07T00:00:00.000Z");
+var startHeisei = new Date("1989-01-08T00:00:00.000Z");
+
+assertEq(dtf.format(endShowa), "昭和64年1月7日");
+assertEq(dtf.format(startHeisei), "平成元年1月8日");
+
+var parts = dtf.formatToParts(startHeisei);
+assertEq(parts.filter(p => p.type === "era")[0].value, "平成");
+assertEq(parts.filter(p => p.type === "year")[0].value, "元");
+
+// ICU returns mixed numbers when an explicit numbering system is present.
+
+var dtf = new Intl.DateTimeFormat("ja-u-ca-japanese-nu-arab", {
+ era: "short",
+ timeZone: "Asia/Tokyo",
+});
+
+assertEq(dtf.format(endShowa), "昭和64年١月٧日");
+assertEq(dtf.format(startHeisei), "平成元年١月٨日");
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/mozExtensions.js b/js/src/tests/non262/Intl/DateTimeFormat/mozExtensions.js
new file mode 100644
index 0000000000..7db314d413
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/mozExtensions.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||!this.hasOwnProperty("addIntlExtras"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let mozIntl = {};
+addIntlExtras(mozIntl);
+
+// Pattern
+
+var dtf = new Intl.DateTimeFormat("en-US", {pattern: "HH:mm MM/dd/YYYY"});
+var mozDtf = new mozIntl.DateTimeFormat("en-US", {pattern: "HH:mm MM/dd/YYYY"});
+
+assertEq(dtf.resolvedOptions().hasOwnProperty('pattern'), false);
+assertEq(mozDtf.resolvedOptions().pattern, "HH:mm MM/dd/YYYY");
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, 'ok');
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/numberingSystem-option.js b/js/src/tests/non262/Intl/DateTimeFormat/numberingSystem-option.js
new file mode 100644
index 0000000000..474910da47
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/numberingSystem-option.js
@@ -0,0 +1,60 @@
+const defaultLocale = "en";
+const defaultNumberingSystem = new Intl.DateTimeFormat(defaultLocale).resolvedOptions().numberingSystem;
+
+function createWithLocale(locale, numberingSystem) {
+ return new Intl.DateTimeFormat(locale, {numberingSystem});
+}
+
+function create(numberingSystem) {
+ return createWithLocale(defaultLocale, numberingSystem);
+}
+
+// Empty string should throw.
+assertThrowsInstanceOf(() => create(""), RangeError);
+
+// Trailing \0 should throw.
+assertThrowsInstanceOf(() => create("latn\0"), RangeError);
+
+// Too short or too long strings should throw.
+assertThrowsInstanceOf(() => create("a"), RangeError);
+assertThrowsInstanceOf(() => create("toolongstring"), RangeError);
+
+// Throw even when prefix is valid.
+assertThrowsInstanceOf(() => create("latn-toolongstring"), RangeError);
+
+// |numberingSystem| can be set to |undefined|.
+let dtf = create(undefined);
+assertEq(dtf.resolvedOptions().numberingSystem, defaultNumberingSystem);
+
+// Unsupported numbering systems are ignored.
+dtf = create("xxxxxxxx");
+assertEq(dtf.resolvedOptions().numberingSystem, defaultNumberingSystem);
+
+// Numbering system in options overwrite Unicode extension keyword.
+dtf = createWithLocale(`${defaultLocale}-u-nu-thai`, "arab");
+assertEq(dtf.resolvedOptions().locale, defaultLocale);
+assertEq(dtf.resolvedOptions().numberingSystem, "arab");
+
+// |numberingSystem| option ignores case.
+dtf = create("ARAB");
+assertEq(dtf.resolvedOptions().locale, defaultLocale);
+assertEq(dtf.resolvedOptions().numberingSystem, "arab");
+
+for (let [numberingSystem, {algorithmic}] of Object.entries(numberingSystems)) {
+ let dtf1 = new Intl.DateTimeFormat(`${defaultLocale}-u-nu-${numberingSystem}`);
+ let dtf2 = new Intl.DateTimeFormat(defaultLocale, {numberingSystem});
+
+ if (!algorithmic) {
+ assertEq(dtf1.resolvedOptions().numberingSystem, numberingSystem);
+ assertEq(dtf2.resolvedOptions().numberingSystem, numberingSystem);
+ } else {
+ // We don't yet support algorithmic numbering systems.
+ assertEq(dtf1.resolvedOptions().numberingSystem, defaultNumberingSystem);
+ assertEq(dtf2.resolvedOptions().numberingSystem, defaultNumberingSystem);
+ }
+
+ assertEq(dtf2.format(0), dtf1.format(0));
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/options-property-accesses.js b/js/src/tests/non262/Intl/DateTimeFormat/options-property-accesses.js
new file mode 100644
index 0000000000..72e896fcae
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/options-property-accesses.js
@@ -0,0 +1,85 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+if (typeof getBuildConfiguration === "undefined") {
+ var getBuildConfiguration = SpecialPowers.Cu.getJSTestingFunctions().getBuildConfiguration;
+}
+
+var isNightly = !getBuildConfiguration().release_or_beta;
+
+var log;
+var proxy = new Proxy({
+ year: "numeric",
+ hour: "numeric",
+}, new Proxy({
+ get(t, pk, r) {
+ log.push(pk);
+ return Reflect.get(t, pk, r);
+ }
+}, {
+ get(t, pk, r) {
+ assertEq(pk, "get");
+ return Reflect.get(t, pk, r);
+ }
+}));
+
+var dayPeriod = isNightly ? ["dayPeriod"] : [];
+
+var constructorAccesses = [
+ // ToDateTimeOptions(options, "any", "date").
+ "weekday", "year", "month", "day",
+ ...dayPeriod, "hour", "minute", "second", "fractionalSecondDigits",
+ "dateStyle", "timeStyle",
+
+ // InitializeDateTimeFormat
+ "localeMatcher", "calendar", "numberingSystem", "hour12", "hourCycle", "timeZone",
+
+ // Table 5: Components of date and time formats
+ "weekday", "era", "year", "month", "day", ...dayPeriod, "hour", "minute", "second",
+ "fractionalSecondDigits", "timeZoneName",
+
+ // InitializeDateTimeFormat
+ "formatMatcher",
+ "dateStyle", "timeStyle",
+];
+
+log = [];
+new Intl.DateTimeFormat(undefined, proxy);
+
+assertEqArray(log, constructorAccesses);
+
+log = [];
+new Date().toLocaleString(undefined, proxy);
+
+assertEqArray(log, [
+ // ToDateTimeOptions(options, "any", "all").
+ "weekday", "year", "month", "day",
+ ...dayPeriod, "hour", "minute", "second", "fractionalSecondDigits",
+ "dateStyle", "timeStyle",
+
+ ...constructorAccesses
+]);
+
+log = [];
+new Date().toLocaleDateString(undefined, proxy);
+
+assertEqArray(log, [
+ // ToDateTimeOptions(options, "date", "date").
+ "weekday", "year", "month", "day",
+ "dateStyle", "timeStyle",
+
+ ...constructorAccesses
+]);
+
+log = [];
+new Date().toLocaleTimeString(undefined, proxy);
+
+assertEqArray(log, [
+ // ToDateTimeOptions(options, "time", "time").
+ ...dayPeriod, "hour", "minute", "second", "fractionalSecondDigits",
+ "dateStyle", "timeStyle",
+
+ ...constructorAccesses
+]);
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/related-year.js b/js/src/tests/non262/Intl/DateTimeFormat/related-year.js
new file mode 100644
index 0000000000..2c2e111d0a
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/related-year.js
@@ -0,0 +1,185 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const {
+ Era, Year, YearName, RelatedYear, Month, Day, Literal
+} = DateTimeFormatParts
+
+const tests = [
+ // Test with non-leap month.
+ {
+ date: new Date("2020-04-23T00:00:00Z"),
+ options: {},
+ calendar: "chinese",
+ locales: {
+ "en": [Month("4"), Literal("/"), Day("1"), Literal("/"), RelatedYear("2020")],
+ "de": [Day("1"), Literal("."), Month("4"), Literal("."), Year("37")],
+ "ja": [YearName("庚子"), Literal("年"), Month("4"), Literal("月"), Day("1"), Literal("日")],
+ "zh": [RelatedYear("2020"), Literal("年"), Month("四月"), Day("1")],
+ "ar": [RelatedYear("٢٠٢٠"), Literal("-"), Month("٠٤"), Literal("-"), Day("٠١")],
+ }
+ },
+
+ // Test with leap month.
+ {
+ date: new Date("2020-05-23T00:00:00Z"),
+ options: {},
+ calendar: "chinese",
+ locales: {
+ "en": [Month("4bis"), Literal("/"), Day("1"), Literal("/"), RelatedYear("2020")],
+ "de": [Day("1"), Literal("."), Month("4bis"), Literal("."), Year("37")],
+ "ja": [YearName("庚子"), Literal("年"), Month("閏4"), Literal("月"), Day("1"), Literal("日")],
+ "zh": [RelatedYear("2020"), Literal("年"), Month("闰四月"), Day("1")],
+ "ar": [RelatedYear("٢٠٢٠"), Literal("-"), Month("٠٤bis"), Literal("-"), Day("٠١")],
+ }
+ },
+
+ // Display only "year" field.
+ {
+ date: new Date("2020-04-23T00:00:00Z"),
+ options: {year: "numeric"},
+ calendar: "chinese",
+ locales: {
+ "en": [RelatedYear("2020"), Literal("("), YearName("geng-zi"), Literal(")")],
+ "de": [YearName("geng-zi")],
+ "ja": [YearName("庚子"), Literal("年")],
+ "zh": [RelatedYear("2020"), YearName("庚子"), Literal("年")],
+ "ar": [Year("٣٧")],
+ }
+ },
+
+ // Display only "month" field.
+ {
+ date: new Date("2020-04-23T00:00:00Z"),
+ options: {month: "long"},
+ calendar: "chinese",
+ locales: {
+ "en": [Month("Fourth Month")],
+ "de": [Month("M04")],
+ "ja": [Month("四月")],
+ "zh": [Month("四月")],
+ "ar": [Month("M04")],
+ }
+ },
+
+ // Display only "month" field. (Leap month)
+ {
+ date: new Date("2020-05-23T00:00:00Z"),
+ options: {month: "long"},
+ calendar: "chinese",
+ locales: {
+ "en": [Month("Fourth Monthbis")],
+ "de": [Month("M04bis")],
+ "ja": [Month("閏四月")],
+ "zh": [Month("闰四月")],
+ "ar": [Month("M04bis")],
+ }
+ },
+
+ // Display "year" and "month" fields.
+ {
+ date: new Date("2020-04-23T00:00:00Z"),
+ options: {year: "numeric", month: "long"},
+ calendar: "chinese",
+ locales: {
+ "en": [Month("Fourth Month"), Literal(" "), RelatedYear("2020"), Literal("("), YearName("geng-zi"), Literal(")")],
+ "de": [Month("M04"), Literal(" "), YearName("geng-zi")],
+ "ja": [YearName("庚子"), Literal("年"), Month("四月")],
+ "zh": [RelatedYear("2020"), YearName("庚子"), Literal("年"), Month("四月")],
+ "ar": [RelatedYear("٢٠٢٠"), Literal("("), YearName("geng-zi"), Literal(") "), Month("M04")],
+ }
+ },
+
+ // Display "year" and "month" fields. (Leap month)
+ {
+ date: new Date("2020-05-23T00:00:00Z"),
+ options: {year: "numeric", month: "long"},
+ calendar: "chinese",
+ locales: {
+ "en": [Month("Fourth Monthbis"), Literal(" "), RelatedYear("2020"), Literal("("), YearName("geng-zi"), Literal(")")],
+ "de": [Month("M04bis"), Literal(" "), YearName("geng-zi")],
+ "ja": [YearName("庚子"), Literal("年"), Month("閏四月")],
+ "zh": [RelatedYear("2020"), YearName("庚子"), Literal("年"), Month("闰四月")],
+ "ar": [RelatedYear("٢٠٢٠"), Literal("("), YearName("geng-zi"), Literal(") "), Month("M04bis")],
+ }
+ },
+
+ // Related year in traditional Korean calendar.
+ {
+ date: new Date("2019-01-01T00:00:00Z"),
+ options: {},
+ calendar: "dangi",
+ locales: {
+ "en": [Month("11"), Literal("/"), Day("26"), Literal("/"), RelatedYear("2018")],
+ "ko": [RelatedYear("2018"), Literal(". "), Month("11"), Literal(". "), Day("26"), Literal(".")],
+ }
+ },
+
+ // Allowing the calendar to modify the pattern selection choice can result in falling back to
+ // the root locale patterns in more cases. That can result in displaying the era field by
+ // default, among other things.
+ {
+ date: new Date("2019-01-01T00:00:00Z"),
+ options: {},
+ calendar: "buddhist",
+ locales: {
+ "en": [Month("1"), Literal("/"), Day("1"), Literal("/"), Year("2562"), Literal(" "), Era("BE")],
+ "th": [Day("1"), Literal("/"), Month("1"), Literal("/"), Year("2562")],
+ }
+ },
+ {
+ date: new Date("2019-01-01T00:00:00Z"),
+ options: {},
+ calendar: "hebrew",
+ locales: {
+ "en": [Day("24"), Literal(" "), Month("Tevet"), Literal(" "), Year("5779")],
+ "he": [Day("24"), Literal(" ב"), Month("טבת"), Literal(" "), Year("5779")],
+ "fr": [Day("24"), Literal("/"), Month("4"), Literal("/"), Year("5779"), Literal(" "), Era("A. M.")],
+ }
+ },
+ {
+ date: new Date("2019-01-01T00:00:00Z"),
+ options: {},
+ calendar: "islamic",
+ locales: {
+ "en": [Month("4"), Literal("/"), Day("25"), Literal("/"), Year("1440"), Literal(" "), Era("AH")],
+ "ar": [Day("٢٥"), Literal("\u200F/"), Month("٤"), Literal("\u200F/"), Year("١٤٤٠"), Literal(" "), Era("هـ")],
+ }
+ },
+ {
+ date: new Date("2019-01-01T00:00:00Z"),
+ options: {},
+ calendar: "japanese",
+ locales: {
+ "en": [Month("1"), Literal("/"), Day("1"), Literal("/"), Year("31"), Literal(" "), Era("H")],
+ "ja": [Era("H"), Year("31"), Literal("/"), Month("1"), Literal("/"), Day("1")],
+ }
+ },
+ {
+ date: new Date("2019-01-01T00:00:00Z"),
+ options: {},
+ calendar: "persian",
+ locales: {
+ "en": [Month("10"), Literal("/"), Day("11"), Literal("/"), Year("1397"), Literal(" "), Era("AP")],
+ "fa": [Year("۱۳۹۷"), Literal("/"), Month("۱۰"), Literal("/"), Day("۱۱")],
+ }
+ },
+ {
+ date: new Date("2019-01-01T00:00:00Z"),
+ options: {},
+ calendar: "roc",
+ locales: {
+ "en": [Month("1"), Literal("/"), Day("1"), Literal("/"), Year("108"), Literal(" "), Era("Minguo")],
+ "zh-Hant-TW": [Era("民國"), Year("108"), Literal("/"), Month("1"), Literal("/"), Day("1")],
+ }
+ },
+];
+
+for (let {date, options, calendar, locales} of tests) {
+ for (let [locale, result] of Object.entries(locales)) {
+ let df = new Intl.DateTimeFormat(`${locale}-u-ca-${calendar}`, {timeZone: "UTC", ...options});
+ assertParts(df, date, result);
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/shell.js b/js/src/tests/non262/Intl/DateTimeFormat/shell.js
new file mode 100644
index 0000000000..c2f9e7138b
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/shell.js
@@ -0,0 +1,34 @@
+function GenericPartCreator(type) {
+ return str => ({ type, value: str });
+}
+
+const DateTimeFormatParts = {
+ Weekday: GenericPartCreator("weekday"),
+ Era: GenericPartCreator("era"),
+ Year: GenericPartCreator("year"),
+ YearName: GenericPartCreator("yearName"),
+ RelatedYear: GenericPartCreator("relatedYear"),
+ Month: GenericPartCreator("month"),
+ Day: GenericPartCreator("day"),
+ DayPeriod: GenericPartCreator("dayPeriod"),
+ Hour: GenericPartCreator("hour"),
+ Minute: GenericPartCreator("minute"),
+ Second: GenericPartCreator("second"),
+ FractionalSecond: GenericPartCreator("fractionalSecond"),
+ TimeZoneName: GenericPartCreator("timeZoneName"),
+ Unknown: GenericPartCreator("unknown"),
+ Literal: GenericPartCreator("literal"),
+};
+
+function assertParts(df, x, expected) {
+ var parts = df.formatToParts(x);
+ assertEq(parts.map(part => part.value).join(""), df.format(x),
+ "formatToParts and format must agree");
+
+ var len = parts.length;
+ assertEq(len, expected.length, "parts count mismatch");
+ for (var i = 0; i < len; i++) {
+ assertEq(parts[i].type, expected[i].type, "type mismatch at " + i);
+ assertEq(parts[i].value, expected[i].value, "value mismatch at " + i);
+ }
+}
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/standalone-month.js b/js/src/tests/non262/Intl/DateTimeFormat/standalone-month.js
new file mode 100644
index 0000000000..c8aab5888e
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/standalone-month.js
@@ -0,0 +1,11 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+for (let weekday of ["long", "short", "narrow"]) {
+ let dtf = new Intl.DateTimeFormat("en", {weekday});
+ let options = dtf.resolvedOptions();
+
+ assertEq(options.weekday, weekday);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/supportedLocalesOf.js b/js/src/tests/non262/Intl/DateTimeFormat/supportedLocalesOf.js
new file mode 100644
index 0000000000..b957f9cd60
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/supportedLocalesOf.js
@@ -0,0 +1,371 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||xulRuntime.shell)
+// -- test in browser only that ICU has locale data for all Mozilla languages
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This array contains the locales that ICU supports in
+// date and time formatting whose languages Mozilla localizes Firefox into.
+// Current as of ICU 50.1.2 and Firefox March 2013.
+var locales = [
+ "af",
+ "af-NA",
+ "af-ZA",
+ "ar",
+ "ar-001",
+ "ar-AE",
+ "ar-BH",
+ "ar-DJ",
+ "ar-DZ",
+ "ar-EG",
+ "ar-EH",
+ "ar-ER",
+ "ar-IL",
+ "ar-IQ",
+ "ar-JO",
+ "ar-KM",
+ "ar-KW",
+ "ar-LB",
+ "ar-LY",
+ "ar-MA",
+ "ar-MR",
+ "ar-OM",
+ "ar-PS",
+ "ar-QA",
+ "ar-SA",
+ "ar-SD",
+ "ar-SO",
+ "ar-SY",
+ "ar-TD",
+ "ar-TN",
+ "ar-YE",
+ "as",
+ "as-IN",
+ "be",
+ "be-BY",
+ "bg",
+ "bg-BG",
+ "bn",
+ "bn-BD",
+ "bn-IN",
+ "br",
+ "br-FR",
+ "bs",
+ "bs-Cyrl",
+ "bs-Cyrl-BA",
+ "bs-Latn",
+ "bs-Latn-BA",
+ "ca",
+ "ca-AD",
+ "ca-ES",
+ "cs",
+ "cs-CZ",
+ "cy",
+ "cy-GB",
+ "da",
+ "da-DK",
+ "de",
+ "de-AT",
+ "de-BE",
+ "de-CH",
+ "de-DE",
+ "de-LI",
+ "de-LU",
+ "el",
+ "el-CY",
+ "el-GR",
+ "en",
+ "en-150",
+ "en-AG",
+ "en-AS",
+ "en-AU",
+ "en-BB",
+ "en-BE",
+ "en-BM",
+ "en-BS",
+ "en-BW",
+ "en-BZ",
+ "en-CA",
+ "en-CM",
+ "en-DM",
+ "en-FJ",
+ "en-FM",
+ "en-GB",
+ "en-GD",
+ "en-GG",
+ "en-GH",
+ "en-GI",
+ "en-GM",
+ "en-GU",
+ "en-GY",
+ "en-HK",
+ "en-IE",
+ "en-IM",
+ "en-IN",
+ "en-JE",
+ "en-JM",
+ "en-KE",
+ "en-KI",
+ "en-KN",
+ "en-KY",
+ "en-LC",
+ "en-LR",
+ "en-LS",
+ "en-MG",
+ "en-MH",
+ "en-MP",
+ "en-MT",
+ "en-MU",
+ "en-MW",
+ "en-NA",
+ "en-NG",
+ "en-NZ",
+ "en-PG",
+ "en-PH",
+ "en-PK",
+ "en-PR",
+ "en-PW",
+ "en-SB",
+ "en-SC",
+ "en-SG",
+ "en-SL",
+ "en-SS",
+ "en-SZ",
+ "en-TC",
+ "en-TO",
+ "en-TT",
+ "en-TZ",
+ "en-UG",
+ "en-UM",
+ "en-US",
+ "en-US-POSIX",
+ "en-VC",
+ "en-VG",
+ "en-VI",
+ "en-VU",
+ "en-WS",
+ "en-ZA",
+ "en-ZM",
+ "en-ZW",
+ "eo",
+ "es",
+ "es-419",
+ "es-AR",
+ "es-BO",
+ "es-CL",
+ "es-CO",
+ "es-CR",
+ "es-CU",
+ "es-DO",
+ "es-EA",
+ "es-EC",
+ "es-ES",
+ "es-GQ",
+ "es-GT",
+ "es-HN",
+ "es-IC",
+ "es-MX",
+ "es-NI",
+ "es-PA",
+ "es-PE",
+ "es-PH",
+ "es-PR",
+ "es-PY",
+ "es-SV",
+ "es-US",
+ "es-UY",
+ "es-VE",
+ "et",
+ "et-EE",
+ "eu",
+ "eu-ES",
+ "fa",
+ "fa-AF",
+ "fa-IR",
+ "ff",
+ "ff-SN",
+ "fi",
+ "fi-FI",
+ "fr",
+ "fr-BE",
+ "fr-BF",
+ "fr-BI",
+ "fr-BJ",
+ "fr-BL",
+ "fr-CA",
+ "fr-CD",
+ "fr-CF",
+ "fr-CG",
+ "fr-CH",
+ "fr-CI",
+ "fr-CM",
+ "fr-DJ",
+ "fr-DZ",
+ "fr-FR",
+ "fr-GA",
+ "fr-GF",
+ "fr-GN",
+ "fr-GP",
+ "fr-GQ",
+ "fr-HT",
+ "fr-KM",
+ "fr-LU",
+ "fr-MA",
+ "fr-MC",
+ "fr-MF",
+ "fr-MG",
+ "fr-ML",
+ "fr-MQ",
+ "fr-MR",
+ "fr-MU",
+ "fr-NC",
+ "fr-NE",
+ "fr-PF",
+ "fr-RE",
+ "fr-RW",
+ "fr-SC",
+ "fr-SN",
+ "fr-SY",
+ "fr-TD",
+ "fr-TG",
+ "fr-TN",
+ "fr-VU",
+ "fr-YT",
+ "ga",
+ "ga-IE",
+ "gl",
+ "gl-ES",
+ "gu",
+ "gu-IN",
+ "he",
+ "he-IL",
+ "hi",
+ "hi-IN",
+ "hr",
+ "hr-BA",
+ "hr-HR",
+ "hu",
+ "hu-HU",
+ "hy",
+ "hy-AM",
+ "id",
+ "id-ID",
+ "is",
+ "is-IS",
+ "it",
+ "it-CH",
+ "it-IT",
+ "it-SM",
+ "ja",
+ "ja-JP",
+ "kk",
+ "kk-Cyrl",
+ "kk-Cyrl-KZ",
+ "km",
+ "km-KH",
+ "kn",
+ "kn-IN",
+ "ko",
+ "ko-KP",
+ "ko-KR",
+ "lt",
+ "lt-LT",
+ "lv",
+ "lv-LV",
+ "mk",
+ "mk-MK",
+ "ml",
+ "ml-IN",
+ "mr",
+ "mr-IN",
+ "nb",
+ "nb-NO",
+ "nl",
+ "nl-AW",
+ "nl-BE",
+ "nl-CW",
+ "nl-NL",
+ "nl-SR",
+ "nl-SX",
+ "nn",
+ "nn-NO",
+ "or",
+ "or-IN",
+ "pa",
+ "pa-Arab",
+ "pa-Arab-PK",
+ "pa-Guru",
+ "pa-Guru-IN",
+ "pl",
+ "pl-PL",
+ "pt",
+ "pt-AO",
+ "pt-BR",
+ "pt-CV",
+ "pt-GW",
+ "pt-MO",
+ "pt-MZ",
+ "pt-PT",
+ "pt-ST",
+ "pt-TL",
+ "rm",
+ "rm-CH",
+ "ro",
+ "ro-MD",
+ "ro-RO",
+ "ru",
+ "ru-BY",
+ "ru-KG",
+ "ru-KZ",
+ "ru-MD",
+ "ru-RU",
+ "ru-UA",
+ "si",
+ "si-LK",
+ "sk",
+ "sk-SK",
+ "sl",
+ "sl-SI",
+ "sq",
+ "sq-AL",
+ "sq-MK",
+ "sr",
+ "sr-Cyrl",
+ "sr-Cyrl-BA",
+ "sr-Cyrl-ME",
+ "sr-Cyrl-RS",
+ "sr-Latn",
+ "sr-Latn-BA",
+ "sr-Latn-ME",
+ "sr-Latn-RS",
+ "sv",
+ "sv-AX",
+ "sv-FI",
+ "sv-SE",
+ "te",
+ "te-IN",
+ "th",
+ "th-TH",
+ "tr",
+ "tr-CY",
+ "tr-TR",
+ "uk",
+ "uk-UA",
+ "vi",
+ "vi-VN",
+ "zh",
+ "zh-Hans",
+ "zh-Hans-CN",
+ "zh-Hans-HK",
+ "zh-Hans-MO",
+ "zh-Hans-SG",
+ "zh-Hant",
+ "zh-Hant-HK",
+ "zh-Hant-MO",
+ "zh-Hant-TW",
+];
+
+var count = Intl.DateTimeFormat.supportedLocalesOf(locales).length;
+
+reportCompare(locales.length, count, "Number of supported locales in Intl.DateTimeFormat");
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/timeZone.js b/js/src/tests/non262/Intl/DateTimeFormat/timeZone.js
new file mode 100644
index 0000000000..eca6ed27ee
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/timeZone.js
@@ -0,0 +1,139 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const tzMapper = [
+ x => x,
+ x => x.toUpperCase(),
+ x => x.toLowerCase(),
+];
+
+
+const utcTimeZones = [
+ // Etc/UTC and Etc/GMT are normalized to UTC.
+ "Etc/UTC", "Etc/GMT",
+
+ // Links to Etc/GMT. (tzdata/etcetera)
+ "GMT", "Etc/Greenwich", "Etc/GMT-0", "Etc/GMT+0", "Etc/GMT0",
+
+ // Links to Etc/UTC. (tzdata/etcetera)
+ "Etc/Universal", "Etc/Zulu",
+
+ // Links to Etc/GMT. (tzdata/backward)
+ "GMT+0", "GMT-0", "GMT0", "Greenwich",
+
+ // Links to Etc/UTC. (tzdata/backward)
+ "UTC", "Universal", "Zulu", "Etc/UCT", "UCT",
+];
+
+for (let timeZone of utcTimeZones) {
+ for (let map of tzMapper) {
+ let dtf = new Intl.DateTimeFormat(undefined, {timeZone: map(timeZone)});
+ assertEq(dtf.resolvedOptions().timeZone, "UTC");
+ }
+}
+
+
+const invalidTimeZones = [
+ "", "null", "undefined", "UTC\0",
+
+ // ICU time zone name for invalid time zones.
+ "Etc/Unknown",
+
+ // ICU custom time zones.
+ "GMT-1", "GMT+1", "GMT-10", "GMT+10",
+ "GMT-10:00", "GMT+10:00",
+ "GMT-1000", "GMT+1000",
+
+ // Legacy ICU time zones.
+ "ACT", "AET", "AGT", "ART", "AST", "BET", "BST", "CAT", "CNT", "CST",
+ "CTT", "EAT", "ECT", "IET", "IST", "JST", "MIT", "NET", "NST", "PLT",
+ "PNT", "PRT", "PST", "SST", "VST",
+
+ // Deprecated IANA time zones.
+ "SystemV/AST4ADT", "SystemV/EST5EDT", "SystemV/CST6CDT", "SystemV/MST7MDT",
+ "SystemV/PST8PDT", "SystemV/YST9YDT", "SystemV/AST4", "SystemV/EST5",
+ "SystemV/CST6", "SystemV/MST7", "SystemV/PST8", "SystemV/YST9", "SystemV/HST10",
+];
+
+for (let timeZone of invalidTimeZones) {
+ for (let map of tzMapper) {
+ assertThrowsInstanceOf(() => {
+ new Intl.DateTimeFormat(undefined, {timeZone: map(timeZone)});
+ }, RangeError);
+ }
+}
+
+
+// GMT[+-]hh is invalid, but Etc/GMT[+-]hh is a valid IANA time zone.
+for (let gmtOffset = -14; gmtOffset <= 12; ++gmtOffset) {
+ // Skip Etc/GMT0.
+ if (gmtOffset === 0)
+ continue;
+
+ let timeZone = `Etc/GMT${gmtOffset > 0 ? "+" : ""}${gmtOffset}`;
+ for (let map of tzMapper) {
+ let dtf = new Intl.DateTimeFormat(undefined, {timeZone: map(timeZone)});
+ assertEq(dtf.resolvedOptions().timeZone, timeZone);
+ }
+}
+
+
+const invalidEtcGMTNames = [
+ // Out of bounds GMT offset.
+ "Etc/GMT-15", "Etc/GMT+13",
+
+ // Etc/GMT[+-]hh:mm isn't a IANA time zone name.
+ "Etc/GMT-10:00", "Etc/GMT+10:00",
+ "Etc/GMT-1000", "Etc/GMT+1000",
+];
+
+for (let timeZone of invalidEtcGMTNames) {
+ for (let map of tzMapper) {
+ assertThrowsInstanceOf(() => {
+ new Intl.DateTimeFormat(undefined, {timeZone: map(timeZone)});
+ }, RangeError);
+ }
+}
+
+
+// RangeError is thrown for primitive values, because ToString(<primitive>)
+// isn't a valid time zone name.
+for (let nonStrings of [null, 0, 0.5, true, false]) {
+ assertThrowsInstanceOf(() => {
+ new Intl.DateTimeFormat(undefined, {timeZone: nonStrings});
+ }, RangeError);
+}
+
+// ToString(<symbol>) throws TypeError.
+assertThrowsInstanceOf(() => {
+ new Intl.DateTimeFormat(undefined, {timeZone: Symbol()});
+}, TypeError);
+
+// |undefined| or absent "timeZone" option selects the default time zone.
+{
+ let {timeZone: tzAbsent} = new Intl.DateTimeFormat(undefined, {}).resolvedOptions();
+ let {timeZone: tzUndefined} = new Intl.DateTimeFormat(undefined, {timeZone: undefined}).resolvedOptions();
+
+ assertEq(typeof tzAbsent, "string");
+ assertEq(typeof tzUndefined, "string");
+ assertEq(tzUndefined, tzAbsent);
+
+ // The default time zone isn't a link name.
+ let {timeZone: tzDefault} = new Intl.DateTimeFormat(undefined, {timeZone: tzAbsent}).resolvedOptions();
+ assertEq(tzDefault, tzAbsent);
+}
+
+// Objects are converted through ToString().
+{
+ let timeZone = "Europe/Warsaw";
+ let obj = {
+ toString() {
+ return timeZone;
+ }
+ };
+ let dtf = new Intl.DateTimeFormat(undefined, {timeZone: obj});
+ assertEq(dtf.resolvedOptions().timeZone, timeZone);
+}
+
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/timeZone_backward_links.js b/js/src/tests/non262/Intl/DateTimeFormat/timeZone_backward_links.js
new file mode 100644
index 0000000000..2a0f982c5c
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/timeZone_backward_links.js
@@ -0,0 +1,136 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Generated by make_intl_data.py. DO NOT EDIT.
+// tzdata version = 2021a
+
+const tzMapper = [
+ x => x,
+ x => x.toUpperCase(),
+ x => x.toLowerCase(),
+];
+
+// Link names derived from IANA Time Zone Database, backward file.
+const links = {
+ "Africa/Asmera": "Africa/Asmara",
+ "America/Atka": "America/Adak",
+ "America/Buenos_Aires": "America/Argentina/Buenos_Aires",
+ "America/Catamarca": "America/Argentina/Catamarca",
+ "America/Cordoba": "America/Argentina/Cordoba",
+ "America/Fort_Wayne": "America/Indiana/Indianapolis",
+ "America/Godthab": "America/Nuuk",
+ "America/Indianapolis": "America/Indiana/Indianapolis",
+ "America/Jujuy": "America/Argentina/Jujuy",
+ "America/Knox_IN": "America/Indiana/Knox",
+ "America/Louisville": "America/Kentucky/Louisville",
+ "America/Mendoza": "America/Argentina/Mendoza",
+ "America/Porto_Acre": "America/Rio_Branco",
+ "America/Santa_Isabel": "America/Tijuana",
+ "America/Shiprock": "America/Denver",
+ "America/Virgin": "America/Port_of_Spain",
+ "Antarctica/South_Pole": "Antarctica/McMurdo",
+ "Asia/Ashkhabad": "Asia/Ashgabat",
+ "Asia/Calcutta": "Asia/Kolkata",
+ "Asia/Chungking": "Asia/Chongqing",
+ "Asia/Dacca": "Asia/Dhaka",
+ "Asia/Katmandu": "Asia/Kathmandu",
+ "Asia/Macao": "Asia/Macau",
+ "Asia/Rangoon": "Asia/Yangon",
+ "Asia/Saigon": "Asia/Ho_Chi_Minh",
+ "Asia/Thimbu": "Asia/Thimphu",
+ "Asia/Ujung_Pandang": "Asia/Makassar",
+ "Asia/Ulan_Bator": "Asia/Ulaanbaatar",
+ "Atlantic/Faeroe": "Atlantic/Faroe",
+ "Australia/ACT": "Australia/Sydney",
+ "Australia/Canberra": "Australia/Sydney",
+ "Australia/LHI": "Australia/Lord_Howe",
+ "Australia/NSW": "Australia/Sydney",
+ "Australia/North": "Australia/Darwin",
+ "Australia/Queensland": "Australia/Brisbane",
+ "Australia/South": "Australia/Adelaide",
+ "Australia/Tasmania": "Australia/Hobart",
+ "Australia/Victoria": "Australia/Melbourne",
+ "Australia/West": "Australia/Perth",
+ "Australia/Yancowinna": "Australia/Broken_Hill",
+ "Brazil/Acre": "America/Rio_Branco",
+ "Brazil/DeNoronha": "America/Noronha",
+ "Brazil/East": "America/Sao_Paulo",
+ "Brazil/West": "America/Manaus",
+ "Canada/Atlantic": "America/Halifax",
+ "Canada/Central": "America/Winnipeg",
+ "Canada/Eastern": "America/Toronto",
+ "Canada/Mountain": "America/Edmonton",
+ "Canada/Newfoundland": "America/St_Johns",
+ "Canada/Pacific": "America/Vancouver",
+ "Canada/Saskatchewan": "America/Regina",
+ "Canada/Yukon": "America/Whitehorse",
+ "Chile/Continental": "America/Santiago",
+ "Chile/EasterIsland": "Pacific/Easter",
+ "Cuba": "America/Havana",
+ "Egypt": "Africa/Cairo",
+ "Eire": "Europe/Dublin",
+ "Etc/UCT": "Etc/UTC",
+ "GB": "Europe/London",
+ "GB-Eire": "Europe/London",
+ "GMT+0": "Etc/GMT",
+ "GMT-0": "Etc/GMT",
+ "GMT0": "Etc/GMT",
+ "Greenwich": "Etc/GMT",
+ "Hongkong": "Asia/Hong_Kong",
+ "Iceland": "Atlantic/Reykjavik",
+ "Iran": "Asia/Tehran",
+ "Israel": "Asia/Jerusalem",
+ "Jamaica": "America/Jamaica",
+ "Japan": "Asia/Tokyo",
+ "Kwajalein": "Pacific/Kwajalein",
+ "Libya": "Africa/Tripoli",
+ "Mexico/BajaNorte": "America/Tijuana",
+ "Mexico/BajaSur": "America/Mazatlan",
+ "Mexico/General": "America/Mexico_City",
+ "NZ": "Pacific/Auckland",
+ "NZ-CHAT": "Pacific/Chatham",
+ "Navajo": "America/Denver",
+ "PRC": "Asia/Shanghai",
+ "Pacific/Ponape": "Pacific/Pohnpei",
+ "Pacific/Samoa": "Pacific/Pago_Pago",
+ "Pacific/Truk": "Pacific/Chuuk",
+ "Pacific/Yap": "Pacific/Chuuk",
+ "Poland": "Europe/Warsaw",
+ "Portugal": "Europe/Lisbon",
+ "ROC": "Asia/Taipei",
+ "ROK": "Asia/Seoul",
+ "Singapore": "Asia/Singapore",
+ "Turkey": "Europe/Istanbul",
+ "UCT": "Etc/UTC",
+ "US/Alaska": "America/Anchorage",
+ "US/Aleutian": "America/Adak",
+ "US/Arizona": "America/Phoenix",
+ "US/Central": "America/Chicago",
+ "US/East-Indiana": "America/Indiana/Indianapolis",
+ "US/Eastern": "America/New_York",
+ "US/Hawaii": "Pacific/Honolulu",
+ "US/Indiana-Starke": "America/Indiana/Knox",
+ "US/Michigan": "America/Detroit",
+ "US/Mountain": "America/Denver",
+ "US/Pacific": "America/Los_Angeles",
+ "US/Samoa": "Pacific/Pago_Pago",
+ "UTC": "Etc/UTC",
+ "Universal": "Etc/UTC",
+ "W-SU": "Europe/Moscow",
+ "Zulu": "Etc/UTC",
+};
+
+for (let [linkName, target] of Object.entries(links)) {
+ if (target === "Etc/UTC" || target === "Etc/GMT")
+ target = "UTC";
+
+ for (let map of tzMapper) {
+ let dtf = new Intl.DateTimeFormat(undefined, {timeZone: map(linkName)});
+ let resolvedTimeZone = dtf.resolvedOptions().timeZone;
+ assertEq(resolvedTimeZone, target, `${linkName} -> ${target}`);
+ }
+}
+
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
+
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/timeZone_backzone.js b/js/src/tests/non262/Intl/DateTimeFormat/timeZone_backzone.js
new file mode 100644
index 0000000000..ad5c0d1d4f
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/timeZone_backzone.js
@@ -0,0 +1,115 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Generated by make_intl_data.py. DO NOT EDIT.
+// tzdata version = 2021a
+
+const tzMapper = [
+ x => x,
+ x => x.toUpperCase(),
+ x => x.toLowerCase(),
+];
+
+// This file was generated with historical, pre-1970 backzone information
+// respected. Therefore, every zone key listed below is its own Zone, not
+// a Link to a modern-day target as IANA ignoring backzones would say.
+
+// Backzone zones derived from IANA Time Zone Database.
+const links = {
+ "Africa/Addis_Ababa": "Africa/Addis_Ababa",
+ "Africa/Asmara": "Africa/Asmara",
+ "Africa/Bamako": "Africa/Bamako",
+ "Africa/Bangui": "Africa/Bangui",
+ "Africa/Banjul": "Africa/Banjul",
+ "Africa/Blantyre": "Africa/Blantyre",
+ "Africa/Brazzaville": "Africa/Brazzaville",
+ "Africa/Bujumbura": "Africa/Bujumbura",
+ "Africa/Conakry": "Africa/Conakry",
+ "Africa/Dakar": "Africa/Dakar",
+ "Africa/Dar_es_Salaam": "Africa/Dar_es_Salaam",
+ "Africa/Djibouti": "Africa/Djibouti",
+ "Africa/Douala": "Africa/Douala",
+ "Africa/Freetown": "Africa/Freetown",
+ "Africa/Gaborone": "Africa/Gaborone",
+ "Africa/Harare": "Africa/Harare",
+ "Africa/Kampala": "Africa/Kampala",
+ "Africa/Kigali": "Africa/Kigali",
+ "Africa/Kinshasa": "Africa/Kinshasa",
+ "Africa/Libreville": "Africa/Libreville",
+ "Africa/Lome": "Africa/Lome",
+ "Africa/Luanda": "Africa/Luanda",
+ "Africa/Lubumbashi": "Africa/Lubumbashi",
+ "Africa/Lusaka": "Africa/Lusaka",
+ "Africa/Malabo": "Africa/Malabo",
+ "Africa/Maseru": "Africa/Maseru",
+ "Africa/Mbabane": "Africa/Mbabane",
+ "Africa/Mogadishu": "Africa/Mogadishu",
+ "Africa/Niamey": "Africa/Niamey",
+ "Africa/Nouakchott": "Africa/Nouakchott",
+ "Africa/Ouagadougou": "Africa/Ouagadougou",
+ "Africa/Porto-Novo": "Africa/Porto-Novo",
+ "Africa/Timbuktu": "Africa/Timbuktu",
+ "America/Anguilla": "America/Anguilla",
+ "America/Antigua": "America/Antigua",
+ "America/Argentina/ComodRivadavia": "America/Argentina/ComodRivadavia",
+ "America/Aruba": "America/Aruba",
+ "America/Cayman": "America/Cayman",
+ "America/Coral_Harbour": "America/Coral_Harbour",
+ "America/Dominica": "America/Dominica",
+ "America/Ensenada": "America/Ensenada",
+ "America/Grenada": "America/Grenada",
+ "America/Guadeloupe": "America/Guadeloupe",
+ "America/Montreal": "America/Montreal",
+ "America/Montserrat": "America/Montserrat",
+ "America/Rosario": "America/Rosario",
+ "America/St_Kitts": "America/St_Kitts",
+ "America/St_Lucia": "America/St_Lucia",
+ "America/St_Thomas": "America/St_Thomas",
+ "America/St_Vincent": "America/St_Vincent",
+ "America/Tortola": "America/Tortola",
+ "Antarctica/McMurdo": "Antarctica/McMurdo",
+ "Asia/Aden": "Asia/Aden",
+ "Asia/Bahrain": "Asia/Bahrain",
+ "Asia/Chongqing": "Asia/Chongqing",
+ "Asia/Harbin": "Asia/Harbin",
+ "Asia/Kashgar": "Asia/Kashgar",
+ "Asia/Kuwait": "Asia/Kuwait",
+ "Asia/Muscat": "Asia/Muscat",
+ "Asia/Phnom_Penh": "Asia/Phnom_Penh",
+ "Asia/Tel_Aviv": "Asia/Tel_Aviv",
+ "Asia/Vientiane": "Asia/Vientiane",
+ "Atlantic/Jan_Mayen": "Atlantic/Jan_Mayen",
+ "Atlantic/St_Helena": "Atlantic/St_Helena",
+ "Australia/Currie": "Australia/Currie",
+ "Europe/Belfast": "Europe/Belfast",
+ "Europe/Guernsey": "Europe/Guernsey",
+ "Europe/Isle_of_Man": "Europe/Isle_of_Man",
+ "Europe/Jersey": "Europe/Jersey",
+ "Europe/Ljubljana": "Europe/Ljubljana",
+ "Europe/Sarajevo": "Europe/Sarajevo",
+ "Europe/Skopje": "Europe/Skopje",
+ "Europe/Tiraspol": "Europe/Tiraspol",
+ "Europe/Vaduz": "Europe/Vaduz",
+ "Europe/Zagreb": "Europe/Zagreb",
+ "Indian/Antananarivo": "Indian/Antananarivo",
+ "Indian/Comoro": "Indian/Comoro",
+ "Indian/Mayotte": "Indian/Mayotte",
+ "Pacific/Johnston": "Pacific/Johnston",
+ "Pacific/Midway": "Pacific/Midway",
+ "Pacific/Saipan": "Pacific/Saipan",
+};
+
+for (let [linkName, target] of Object.entries(links)) {
+ if (target === "Etc/UTC" || target === "Etc/GMT")
+ target = "UTC";
+
+ for (let map of tzMapper) {
+ let dtf = new Intl.DateTimeFormat(undefined, {timeZone: map(linkName)});
+ let resolvedTimeZone = dtf.resolvedOptions().timeZone;
+ assertEq(resolvedTimeZone, target, `${linkName} -> ${target}`);
+ }
+}
+
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
+
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/timeZone_backzone_links.js b/js/src/tests/non262/Intl/DateTimeFormat/timeZone_backzone_links.js
new file mode 100644
index 0000000000..98357774dc
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/timeZone_backzone_links.js
@@ -0,0 +1,38 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Generated by make_intl_data.py. DO NOT EDIT.
+// tzdata version = 2021a
+
+const tzMapper = [
+ x => x,
+ x => x.toUpperCase(),
+ x => x.toLowerCase(),
+];
+
+// This file was generated with historical, pre-1970 backzone information
+// respected. Therefore, every zone key listed below points to a target
+// in the backzone file and not to its modern-day target as IANA ignoring
+// backzones would say.
+
+// Backzone links derived from IANA Time Zone Database.
+const links = {
+ "Africa/Asmera": "Africa/Asmara",
+ "Antarctica/South_Pole": "Antarctica/McMurdo",
+ "Asia/Chungking": "Asia/Chongqing",
+};
+
+for (let [linkName, target] of Object.entries(links)) {
+ if (target === "Etc/UTC" || target === "Etc/GMT")
+ target = "UTC";
+
+ for (let map of tzMapper) {
+ let dtf = new Intl.DateTimeFormat(undefined, {timeZone: map(linkName)});
+ let resolvedTimeZone = dtf.resolvedOptions().timeZone;
+ assertEq(resolvedTimeZone, target, `${linkName} -> ${target}`);
+ }
+}
+
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
+
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/timeZone_notbackward_links.js b/js/src/tests/non262/Intl/DateTimeFormat/timeZone_notbackward_links.js
new file mode 100644
index 0000000000..01f9ce719a
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/timeZone_notbackward_links.js
@@ -0,0 +1,50 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Generated by make_intl_data.py. DO NOT EDIT.
+// tzdata version = 2021a
+
+const tzMapper = [
+ x => x,
+ x => x.toUpperCase(),
+ x => x.toLowerCase(),
+];
+
+// Link names derived from IANA Time Zone Database, excluding backward file.
+const links = {
+ "America/Kralendijk": "America/Curacao",
+ "America/Lower_Princes": "America/Curacao",
+ "America/Marigot": "America/Port_of_Spain",
+ "America/St_Barthelemy": "America/Port_of_Spain",
+ "Arctic/Longyearbyen": "Europe/Oslo",
+ "Asia/Istanbul": "Europe/Istanbul",
+ "Etc/GMT+0": "Etc/GMT",
+ "Etc/GMT-0": "Etc/GMT",
+ "Etc/GMT0": "Etc/GMT",
+ "Etc/Greenwich": "Etc/GMT",
+ "Etc/Universal": "Etc/UTC",
+ "Etc/Zulu": "Etc/UTC",
+ "Europe/Bratislava": "Europe/Prague",
+ "Europe/Busingen": "Europe/Zurich",
+ "Europe/Mariehamn": "Europe/Helsinki",
+ "Europe/Nicosia": "Asia/Nicosia",
+ "Europe/Podgorica": "Europe/Belgrade",
+ "Europe/San_Marino": "Europe/Rome",
+ "Europe/Vatican": "Europe/Rome",
+ "GMT": "Etc/GMT",
+};
+
+for (let [linkName, target] of Object.entries(links)) {
+ if (target === "Etc/UTC" || target === "Etc/GMT")
+ target = "UTC";
+
+ for (let map of tzMapper) {
+ let dtf = new Intl.DateTimeFormat(undefined, {timeZone: map(linkName)});
+ let resolvedTimeZone = dtf.resolvedOptions().timeZone;
+ assertEq(resolvedTimeZone, target, `${linkName} -> ${target}`);
+ }
+}
+
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
+
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/timeZone_version.js b/js/src/tests/non262/Intl/DateTimeFormat/timeZone_version.js
new file mode 100644
index 0000000000..6b6b71e04b
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/timeZone_version.js
@@ -0,0 +1,17 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Generated by make_intl_data.py. DO NOT EDIT.
+// tzdata version = 2021a
+const tzdata = "2021a";
+
+if (typeof getICUOptions === "undefined") {
+ var getICUOptions = SpecialPowers.Cu.getJSTestingFunctions().getICUOptions;
+}
+
+var options = getICUOptions();
+
+assertEq(options.tzdata, tzdata);
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
+
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/toStringTag.js b/js/src/tests/non262/Intl/DateTimeFormat/toStringTag.js
new file mode 100644
index 0000000000..136632f71f
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/toStringTag.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var desc = Object.getOwnPropertyDescriptor(Intl.DateTimeFormat.prototype, Symbol.toStringTag);
+
+assertEq(desc !== undefined, true);
+assertEq(desc.value, "Intl.DateTimeFormat");
+assertEq(desc.writable, false);
+assertEq(desc.enumerable, false);
+assertEq(desc.configurable, true);
+
+assertEq(Object.prototype.toString.call(Intl.DateTimeFormat.prototype), "[object Intl.DateTimeFormat]");
+assertEq(Object.prototype.toString.call(new Intl.DateTimeFormat), "[object Intl.DateTimeFormat]");
+
+Object.defineProperty(Intl.DateTimeFormat.prototype, Symbol.toStringTag, {value: "DateTimeFormat"});
+
+assertEq(Object.prototype.toString.call(Intl.DateTimeFormat.prototype), "[object DateTimeFormat]");
+assertEq(Object.prototype.toString.call(new Intl.DateTimeFormat), "[object DateTimeFormat]");
+
+delete Intl.DateTimeFormat.prototype[Symbol.toStringTag];
+
+assertEq(Object.prototype.toString.call(Intl.DateTimeFormat.prototype), "[object Object]");
+assertEq(Object.prototype.toString.call(new Intl.DateTimeFormat), "[object Object]");
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/tz-environment-variable.js b/js/src/tests/non262/Intl/DateTimeFormat/tz-environment-variable.js
new file mode 100644
index 0000000000..6128abb9d1
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/tz-environment-variable.js
@@ -0,0 +1,67 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||(xulRuntime.OS=="WINNT"&&!xulRuntime.shell)) -- Windows browser in automation doesn't pick up new time zones correctly
+
+// From bug 1330149:
+//
+// Windows only supports a very limited set of IANA time zone names for the TZ
+// environment variable.
+//
+// TZ format supported by Windows: "TZ=tzn[+|-]hh[:mm[:ss]][dzn]".
+//
+// Complete list of all IANA time zone ids matching that format.
+//
+// From tzdata's "northamerica" file:
+// EST5EDT
+// CST6CDT
+// MST7MDT
+// PST8PDT
+//
+// From tzdata's "backward" file:
+// GMT+0
+// GMT-0
+// GMT0
+//
+// Also supported on Windows even though they don't match the format listed
+// above.
+//
+// From tzdata's "backward" file:
+// UCT
+// UTC
+//
+// From tzdata's "etcetera" file:
+// GMT
+
+function inTimeZone(tzname, fn) {
+ setTimeZone(tzname);
+ try {
+ fn();
+ } finally {
+ setTimeZone("PST8PDT");
+ }
+}
+
+const timeZones = [
+ { id: "EST5EDT" },
+ { id: "CST6CDT" },
+ { id: "MST7MDT" },
+ { id: "PST8PDT" },
+ // ICU on non-Windows platforms doesn't accept these three time zone
+ // identifiers, cf. isValidOlsonID in $ICU/source/common/putil.cpp. We
+ // could add support for them, but it seems unlikely they're used in
+ // practice, so we just skip over them.
+ // { id: "GMT+0", normalized: "UTC" },
+ // { id: "GMT-0", normalized: "UTC" },
+ // { id: "GMT0", normalized: "UTC" },
+ { id: "UCT", normalized: "UTC" },
+ { id: "UTC", normalized: "UTC" },
+ { id: "GMT", normalized: "UTC" },
+];
+
+for (let {id, normalized = id} of timeZones) {
+ inTimeZone(id, () => {
+ let opts = new Intl.DateTimeFormat().resolvedOptions();
+ assertEq(opts.timeZone, normalized);
+ });
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/unwrapping.js b/js/src/tests/non262/Intl/DateTimeFormat/unwrapping.js
new file mode 100644
index 0000000000..579ba5bbcb
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/unwrapping.js
@@ -0,0 +1,247 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Test UnwrapDateTimeFormat operation.
+
+const dateTimeFormatFunctions = [];
+dateTimeFormatFunctions.push({
+ function: Intl.DateTimeFormat.prototype.resolvedOptions,
+ unwrap: true,
+});
+dateTimeFormatFunctions.push({
+ function: Object.getOwnPropertyDescriptor(Intl.DateTimeFormat.prototype, "format").get,
+ unwrap: true,
+});
+dateTimeFormatFunctions.push({
+ function: Intl.DateTimeFormat.prototype.formatToParts,
+ unwrap: false,
+});
+
+function IsIntlService(c) {
+ return typeof c === "function" &&
+ c.hasOwnProperty("prototype") &&
+ c.prototype.hasOwnProperty("resolvedOptions");
+}
+
+function IsObject(o) {
+ return Object(o) === o;
+}
+
+function IsPrimitive(o) {
+ return Object(o) !== o;
+}
+
+function intlObjects(ctor) {
+ let args = [];
+ if (ctor === Intl.DisplayNames) {
+ // Intl.DisplayNames can't be constructed without any arguments.
+ args = [undefined, {type: "language"}];
+ }
+
+ return [
+ // Instance of an Intl constructor.
+ new ctor(...args),
+
+ // Instance of a subclassed Intl constructor.
+ new class extends ctor {}(...args),
+
+ // Intl object not inheriting from its default prototype.
+ Object.setPrototypeOf(new ctor(...args), Object.prototype),
+ ];
+}
+
+function thisValues(C) {
+ const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsIntlService);
+
+ return [
+ // Primitive values.
+ ...[undefined, null, true, "abc", Symbol(), 123],
+
+ // Object values.
+ ...[{}, [], /(?:)/, function(){}, new Proxy({}, {})],
+
+ // Intl objects.
+ ...[].concat(...intlConstructors.filter(ctor => ctor !== C).map(intlObjects)),
+
+ // Object inheriting from an Intl constructor prototype.
+ ...intlConstructors.map(ctor => Object.create(ctor.prototype)),
+ ];
+}
+
+const intlFallbackSymbol = Object.getOwnPropertySymbols(Intl.DateTimeFormat.call(Object.create(Intl.DateTimeFormat.prototype)))[0];
+
+// Test Intl.DateTimeFormat.prototype methods.
+for (let {function: dateTimeFormatFunction, unwrap} of dateTimeFormatFunctions) {
+ // Test a TypeError is thrown when the this-value isn't an initialized
+ // Intl.DateTimeFormat instance.
+ for (let thisValue of thisValues(Intl.DateTimeFormat)) {
+ assertThrowsInstanceOf(() => dateTimeFormatFunction.call(thisValue), TypeError);
+ }
+
+ // And test no error is thrown for initialized Intl.DateTimeFormat instances.
+ for (let thisValue of intlObjects(Intl.DateTimeFormat)) {
+ dateTimeFormatFunction.call(thisValue);
+ }
+
+ // Manually add [[FallbackSymbol]] to objects and then repeat the tests from above.
+ for (let thisValue of thisValues(Intl.DateTimeFormat)) {
+ assertThrowsInstanceOf(() => dateTimeFormatFunction.call({
+ __proto__: Intl.DateTimeFormat.prototype,
+ [intlFallbackSymbol]: thisValue,
+ }), TypeError);
+ }
+
+ for (let thisValue of intlObjects(Intl.DateTimeFormat)) {
+ let obj = {
+ __proto__: Intl.DateTimeFormat.prototype,
+ [intlFallbackSymbol]: thisValue,
+ };
+ if (unwrap) {
+ dateTimeFormatFunction.call(obj);
+ } else {
+ assertThrowsInstanceOf(() => dateTimeFormatFunction.call(obj), TypeError);
+ }
+ }
+
+ // Ensure [[FallbackSymbol]] isn't retrieved for Intl.DateTimeFormat instances.
+ for (let thisValue of intlObjects(Intl.DateTimeFormat)) {
+ Object.defineProperty(thisValue, intlFallbackSymbol, {
+ get() { assertEq(false, true); }
+ });
+ dateTimeFormatFunction.call(thisValue);
+ }
+
+ // Ensure [[FallbackSymbol]] is only retrieved for objects inheriting from Intl.DateTimeFormat.prototype.
+ for (let thisValue of thisValues(Intl.DateTimeFormat).filter(IsObject)) {
+ if (Intl.DateTimeFormat.prototype.isPrototypeOf(thisValue))
+ continue;
+ Object.defineProperty(thisValue, intlFallbackSymbol, {
+ get() { assertEq(false, true); }
+ });
+ assertThrowsInstanceOf(() => dateTimeFormatFunction.call(thisValue), TypeError);
+ }
+
+ // Repeat the test from above, but also change Intl.DateTimeFormat[@@hasInstance]
+ // so it always returns |null|.
+ for (let thisValue of thisValues(Intl.DateTimeFormat).filter(IsObject)) {
+ let hasInstanceCalled = false, symbolGetterCalled = false;
+ Object.defineProperty(Intl.DateTimeFormat, Symbol.hasInstance, {
+ value() {
+ assertEq(hasInstanceCalled, false);
+ hasInstanceCalled = true;
+ return true;
+ }, configurable: true
+ });
+ Object.defineProperty(thisValue, intlFallbackSymbol, {
+ get() {
+ assertEq(symbolGetterCalled, false);
+ symbolGetterCalled = true;
+ return null;
+ }, configurable: true
+ });
+
+ assertThrowsInstanceOf(() => dateTimeFormatFunction.call(thisValue), TypeError);
+
+ delete Intl.DateTimeFormat[Symbol.hasInstance];
+
+ assertEq(hasInstanceCalled, unwrap);
+ assertEq(symbolGetterCalled, unwrap);
+ }
+
+ // Test with primitive values.
+ for (let thisValue of thisValues(Intl.DateTimeFormat).filter(IsPrimitive)) {
+ // Ensure @@hasInstance is not called.
+ Object.defineProperty(Intl.DateTimeFormat, Symbol.hasInstance, {
+ value() { assertEq(true, false); }, configurable: true
+ });
+ let isUndefinedOrNull = thisValue === undefined || thisValue === null;
+ let symbolHolder;
+ if (!isUndefinedOrNull) {
+ // Ensure the fallback symbol isn't retrieved from the primitive wrapper prototype.
+ symbolHolder = Object.getPrototypeOf(thisValue);
+ Object.defineProperty(symbolHolder, intlFallbackSymbol, {
+ get() { assertEq(true, false); }, configurable: true
+ });
+ }
+
+ assertThrowsInstanceOf(() => dateTimeFormatFunction.call(thisValue), TypeError);
+
+ delete Intl.DateTimeFormat[Symbol.hasInstance];
+ if (!isUndefinedOrNull)
+ delete symbolHolder[intlFallbackSymbol];
+ }
+}
+
+// Test format() returns the correct result for objects initialized as Intl.DateTimeFormat instances.
+{
+ // An actual Intl.DateTimeFormat instance.
+ let dateTimeFormat = new Intl.DateTimeFormat();
+
+ // An object initialized as a DateTimeFormat instance.
+ let thisValue = Object.create(Intl.DateTimeFormat.prototype);
+ Intl.DateTimeFormat.call(thisValue);
+
+ // Object with [[FallbackSymbol]] set to DateTimeFormat instance.
+ let fakeObj = {
+ __proto__: Intl.DateTimeFormat.prototype,
+ [intlFallbackSymbol]: dateTimeFormat,
+ };
+
+ for (let number of [0, Date.now(), -Date.now()]) {
+ let expected = dateTimeFormat.format(number);
+ assertEq(thisValue.format(number), expected);
+ assertEq(thisValue[intlFallbackSymbol].format(number), expected);
+ assertEq(fakeObj.format(number), expected);
+ }
+}
+
+// Ensure formatToParts() doesn't use the fallback semantics.
+{
+ let formatToParts = Intl.DateTimeFormat.prototype.formatToParts;
+
+ // An object initialized as a DateTimeFormat instance.
+ let thisValue = Object.create(Intl.DateTimeFormat.prototype);
+ Intl.DateTimeFormat.call(thisValue);
+ assertThrowsInstanceOf(() => formatToParts.call(thisValue), TypeError);
+
+ // Object with [[FallbackSymbol]] set to DateTimeFormat instance.
+ let fakeObj = {
+ __proto__: Intl.DateTimeFormat.prototype,
+ [intlFallbackSymbol]: new Intl.DateTimeFormat(),
+ };
+ assertThrowsInstanceOf(() => formatToParts.call(fakeObj), TypeError);
+}
+
+// Test resolvedOptions() returns the same results.
+{
+ // An actual Intl.DateTimeFormat instance.
+ let dateTimeFormat = new Intl.DateTimeFormat();
+
+ // An object initialized as a DateTimeFormat instance.
+ let thisValue = Object.create(Intl.DateTimeFormat.prototype);
+ Intl.DateTimeFormat.call(thisValue);
+
+ // Object with [[FallbackSymbol]] set to DateTimeFormat instance.
+ let fakeObj = {
+ __proto__: Intl.DateTimeFormat.prototype,
+ [intlFallbackSymbol]: dateTimeFormat,
+ };
+
+ function assertEqOptions(actual, expected) {
+ actual = Object.entries(actual);
+ expected = Object.entries(expected);
+
+ assertEq(actual.length, expected.length, "options count mismatch");
+ for (var i = 0; i < expected.length; i++) {
+ assertEq(actual[i][0], expected[i][0], "key mismatch at " + i);
+ assertEq(actual[i][1], expected[i][1], "value mismatch at " + i);
+ }
+ }
+
+ let expected = dateTimeFormat.resolvedOptions();
+ assertEqOptions(thisValue.resolvedOptions(), expected);
+ assertEqOptions(thisValue[intlFallbackSymbol].resolvedOptions(), expected);
+ assertEqOptions(fakeObj.resolvedOptions(), expected);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/DisplayNames/browser.js b/js/src/tests/non262/Intl/DisplayNames/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Intl/DisplayNames/browser.js
diff --git a/js/src/tests/non262/Intl/DisplayNames/calendar.js b/js/src/tests/non262/Intl/DisplayNames/calendar.js
new file mode 100644
index 0000000000..9a7d9abdaa
--- /dev/null
+++ b/js/src/tests/non262/Intl/DisplayNames/calendar.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl')||!this.hasOwnProperty('addIntlExtras'))
+
+addMozIntlDisplayNames(this);
+
+let dn1 = new Intl.DisplayNames("en", {type: "month", calendar: "gregory"});
+assertEq(dn1.of(1), "January");
+assertEq(dn1.resolvedOptions().calendar, "gregory");
+
+let dn2 = new Intl.DisplayNames("en", {type: "month", calendar: "hebrew"});
+assertEq(dn2.of(1), "Tishri");
+assertEq(dn2.resolvedOptions().calendar, "hebrew");
+
+let dn3 = new Intl.DisplayNames("en", {type: "month", calendar: "islamicc"});
+assertEq(dn3.of(1), "Muharram");
+assertEq(dn3.resolvedOptions().calendar, "islamic-civil");
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/DisplayNames/currency.js b/js/src/tests/non262/Intl/DisplayNames/currency.js
new file mode 100644
index 0000000000..1571249106
--- /dev/null
+++ b/js/src/tests/non262/Intl/DisplayNames/currency.js
@@ -0,0 +1,150 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl'))
+
+const tests = {
+ "en": {
+ long: {
+ "USD": "US Dollar",
+ "EUR": "Euro",
+ "FRF": "French Franc",
+ "CNY": "Chinese Yuan",
+ "XAU": "Gold",
+ },
+ short: {
+ "USD": "$",
+ "EUR": "€",
+ "FRF": "FRF",
+ "CNY": "CN¥",
+ "XAU": "XAU",
+ },
+ narrow: {
+ "USD": "USD",
+ "EUR": "EUR",
+ "CNY": "CNY",
+ },
+ },
+ "de": {
+ long: {
+ "USD": "US-Dollar",
+ "EUR": "Euro",
+ "FRF": "Französischer Franc",
+ "CNY": "Renminbi Yuan",
+ "XAU": "Unze Gold",
+ },
+ short: {
+ "USD": "$",
+ "EUR": "€",
+ "FRF": "FRF",
+ "CNY": "CN¥",
+ "XAU": "XAU",
+ },
+ narrow: {
+ "CNY": "¥",
+ },
+ },
+ "fr": {
+ long: {
+ "USD": "dollar des États-Unis",
+ "EUR": "euro",
+ "FRF": "franc français",
+ "CNY": "yuan renminbi chinois",
+ "XAU": "or",
+ },
+ short: {
+ "USD": "$US",
+ "EUR": "€",
+ "FRF": "F",
+ "CNY": "CNY",
+ "XAU": "XAU",
+ },
+ narrow: {
+ "USD": "$",
+ "CNY": "¥",
+ },
+ },
+ "zh": {
+ long: {
+ "USD": "美元",
+ "EUR": "欧元",
+ "FRF": "法国法郎",
+ "CNY": "人民币",
+ "XAU": "黄金",
+ },
+ short: {
+ "USD": "US$",
+ "EUR": "€",
+ "FRF": "FRF",
+ "CNY": "¥",
+ "XAU": "XAU",
+ },
+ narrow: {
+ "USD": "$",
+ },
+ },
+};
+
+for (let [locale, localeTests] of Object.entries(tests)) {
+ for (let [style, styleTests] of Object.entries(localeTests)) {
+ let dn = new Intl.DisplayNames(locale, {type: "currency", style});
+
+ let resolved = dn.resolvedOptions();
+ assertEq(resolved.locale, locale);
+ assertEq(resolved.style, style);
+ assertEq(resolved.type, "currency");
+ assertEq(resolved.fallback, "code");
+
+ let inheritedTests = {...localeTests.long, ...localeTests.short, ...localeTests.narrow};
+ for (let [currency, expected] of Object.entries({...inheritedTests, ...styleTests})) {
+ assertEq(dn.of(currency), expected);
+
+ // Also works with objects.
+ assertEq(dn.of(Object(currency)), expected);
+ }
+ }
+}
+
+{
+ let dn = new Intl.DisplayNames("en", {type: "currency"});
+
+ // Performs ToString on the input and then validates the stringified result.
+ assertThrowsInstanceOf(() => dn.of(), RangeError);
+ assertThrowsInstanceOf(() => dn.of(null), RangeError);
+ assertThrowsInstanceOf(() => dn.of(Symbol()), TypeError);
+ assertThrowsInstanceOf(() => dn.of(0), RangeError);
+
+ // Throws an error if |code| isn't a well-formed currency code.
+ assertThrowsInstanceOf(() => dn.of("us"), RangeError);
+ assertThrowsInstanceOf(() => dn.of("euro"), RangeError);
+ assertThrowsInstanceOf(() => dn.of("€uro"), RangeError);
+}
+
+// Test fallback behaviour.
+{
+ let dn1 = new Intl.DisplayNames("en", {type: "currency"});
+ let dn2 = new Intl.DisplayNames("en", {type: "currency", fallback: "code"});
+ let dn3 = new Intl.DisplayNames("en", {type: "currency", fallback: "none"});
+
+ assertEq(dn1.resolvedOptions().fallback, "code");
+ assertEq(dn2.resolvedOptions().fallback, "code");
+ assertEq(dn3.resolvedOptions().fallback, "none");
+
+ // "AAA" is not a registered currency code.
+ assertEq(dn1.of("AAA"), "AAA");
+ assertEq(dn2.of("AAA"), "AAA");
+ assertEq(dn3.of("AAA"), undefined);
+
+ // The returned fallback is in canonical case.
+ assertEq(dn1.of("aaa"), "AAA");
+ assertEq(dn2.of("aaa"), "AAA");
+ assertEq(dn3.of("aaa"), undefined);
+}
+
+// Test when case isn't canonical.
+{
+ let dn = new Intl.DisplayNames("en", {type: "currency", fallback: "none"});
+
+ assertEq(dn.of("USD"), "US Dollar");
+ assertEq(dn.of("usd"), "US Dollar");
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/DisplayNames/dateTimeField.js b/js/src/tests/non262/Intl/DisplayNames/dateTimeField.js
new file mode 100644
index 0000000000..f802b1b173
--- /dev/null
+++ b/js/src/tests/non262/Intl/DisplayNames/dateTimeField.js
@@ -0,0 +1,173 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl')||!this.hasOwnProperty('addIntlExtras'))
+
+addMozIntlDisplayNames(this);
+
+const tests = {
+ "en": {
+ long: {
+ "era": "era",
+ "year": "year",
+ "quarter": "quarter",
+ "month": "month",
+ "weekOfYear": "week",
+ "weekday": "day of the week",
+ "day": "day",
+ "dayPeriod": "AM/PM",
+ "hour": "hour",
+ "minute": "minute",
+ "second": "second",
+ "timeZoneName": "time zone",
+ },
+ short: {
+ "year": "yr.",
+ "quarter": "qtr.",
+ "month": "mo.",
+ "weekOfYear": "wk.",
+ "weekday": "day of wk.",
+ "dayPeriod": "AM/PM",
+ "hour": "hr.",
+ "minute": "min.",
+ "second": "sec.",
+ "timeZoneName": "zone",
+ },
+ narrow: {},
+ },
+ "de": {
+ long: {
+ "era": "Epoche",
+ "year": "Jahr",
+ "quarter": "Quartal",
+ "month": "Monat",
+ "weekOfYear": "Woche",
+ "weekday": "Wochentag",
+ "day": "Tag",
+ "dayPeriod": "Tageshälfte",
+ "hour": "Stunde",
+ "minute": "Minute",
+ "second": "Sekunde",
+ "timeZoneName": "Zeitzone",
+ },
+ short: {
+ "era": "Epoche",
+ "year": "Jahr",
+ "quarter": "Quart.",
+ "month": "Monat",
+ "weekOfYear": "Woche",
+ "weekday": "Wochentag",
+ "day": "Tag",
+ "dayPeriod": "Tageshälfte",
+ "hour": "Std.",
+ "minute": "Min.",
+ "second": "Sek.",
+ "timeZoneName": "Zeitzone",
+ },
+ narrow: {
+ "era": "E",
+ "year": "J",
+ "quarter": "Q",
+ "month": "M",
+ "weekOfYear": "W",
+ "weekday": "Wochent.",
+ "dayPeriod": "Tagesh.",
+ "timeZoneName": "Zeitz.",
+ },
+ },
+ "fr": {
+ long: {
+ "era": "ère",
+ "year": "année",
+ "quarter": "trimestre",
+ "month": "mois",
+ "weekOfYear": "semaine",
+ "weekday": "jour de la semaine",
+ "day": "jour",
+ "dayPeriod": "cadran",
+ "hour": "heure",
+ "minute": "minute",
+ "second": "seconde",
+ "timeZoneName": "fuseau horaire",
+ },
+ short: {
+ "year": "an",
+ "quarter": "trim.",
+ "month": "m.",
+ "weekOfYear": "sem.",
+ "weekday": "j (sem.)",
+ "day": "j",
+ "hour": "h",
+ "minute": "min",
+ "second": "s",
+ },
+ narrow: {
+ "year": "a",
+ },
+ },
+ "zh": {
+ long: {
+ "era": "纪元",
+ "year": "年",
+ "quarter": "季度",
+ "month": "月",
+ "weekOfYear": "周",
+ "weekday": "工作日",
+ "day": "日",
+ "dayPeriod": "上午/下午",
+ "hour": "小时",
+ "minute": "分钟",
+ "second": "秒",
+ "timeZoneName": "时区",
+ },
+ short: {
+ "quarter": "季",
+ "minute": "分",
+ },
+ narrow: {},
+ },
+};
+
+for (let [locale, localeTests] of Object.entries(tests)) {
+ let defaultCalendar = new Intl.DateTimeFormat(locale).resolvedOptions().calendar;
+
+ for (let [style, styleTests] of Object.entries(localeTests)) {
+ let dn = new Intl.DisplayNames(locale, {type: "dateTimeField", style});
+
+ let resolved = dn.resolvedOptions();
+ assertEq(resolved.locale, locale);
+ assertEq(resolved.calendar, defaultCalendar);
+ assertEq(resolved.style, style);
+ assertEq(resolved.type, "dateTimeField");
+ assertEq(resolved.fallback, "code");
+
+ let inheritedTests = {...localeTests.long, ...localeTests.short, ...localeTests.narrow};
+ for (let [field, expected] of Object.entries({...inheritedTests, ...styleTests})) {
+ assertEq(dn.of(field), expected);
+
+ // Also works with objects.
+ assertEq(dn.of(Object(field)), expected);
+ }
+ }
+}
+
+{
+ let dn = new Intl.DisplayNames("en", {type: "dayPeriod"});
+
+ // Performs ToString on the input and then validates the stringified result.
+ assertThrowsInstanceOf(() => dn.of(), RangeError);
+ assertThrowsInstanceOf(() => dn.of(null), RangeError);
+ assertThrowsInstanceOf(() => dn.of(Symbol()), TypeError);
+ assertThrowsInstanceOf(() => dn.of(0), RangeError);
+ assertThrowsInstanceOf(() => dn.of(1), RangeError);
+
+ // Throws an error if not one of ["era", "year", "quarter", "month", "weekOfYear", "weekday",
+ // "day", "dayPeriod", "hour", "minute", "second", "timeZoneName"].
+ assertThrowsInstanceOf(() => dn.of(""), RangeError);
+ assertThrowsInstanceOf(() => dn.of("ERA"), RangeError);
+ assertThrowsInstanceOf(() => dn.of("Era"), RangeError);
+ assertThrowsInstanceOf(() => dn.of("era\0"), RangeError);
+ assertThrowsInstanceOf(() => dn.of("dayperiod"), RangeError);
+ assertThrowsInstanceOf(() => dn.of("day-period"), RangeError);
+ assertThrowsInstanceOf(() => dn.of("timezoneName"), RangeError);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/DisplayNames/dayPeriod.js b/js/src/tests/non262/Intl/DisplayNames/dayPeriod.js
new file mode 100644
index 0000000000..cfeafdf5ee
--- /dev/null
+++ b/js/src/tests/non262/Intl/DisplayNames/dayPeriod.js
@@ -0,0 +1,80 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl')||!this.hasOwnProperty('addIntlExtras'))
+
+addMozIntlDisplayNames(this);
+
+const tests = {
+ "en": {
+ long: {
+ "am": "AM",
+ "pm": "PM",
+ },
+ short: {},
+ narrow: {},
+ },
+ "de": {
+ long: {
+ "am": "AM",
+ "pm": "PM",
+ },
+ short: {},
+ narrow: {},
+ },
+ "fr": {
+ long: {
+ "am": "AM",
+ "pm": "PM",
+ },
+ short: {},
+ narrow: {},
+ },
+ "zh": {
+ long: {
+ "am": "上午",
+ "pm": "下午",
+ },
+ short: {},
+ narrow: {},
+ },
+};
+
+for (let [locale, localeTests] of Object.entries(tests)) {
+ let defaultCalendar = new Intl.DateTimeFormat(locale).resolvedOptions().calendar;
+
+ for (let [style, styleTests] of Object.entries(localeTests)) {
+ let dn = new Intl.DisplayNames(locale, {type: "dayPeriod", style});
+
+ let resolved = dn.resolvedOptions();
+ assertEq(resolved.locale, locale);
+ assertEq(resolved.calendar, defaultCalendar);
+ assertEq(resolved.style, style);
+ assertEq(resolved.type, "dayPeriod");
+ assertEq(resolved.fallback, "code");
+
+ let inheritedTests = {...localeTests.long, ...localeTests.short, ...localeTests.narrow};
+ for (let [dayPeriod, expected] of Object.entries({...inheritedTests, ...styleTests})) {
+ assertEq(dn.of(dayPeriod), expected);
+
+ // Also works with objects.
+ assertEq(dn.of(Object(dayPeriod)), expected);
+ }
+ }
+}
+
+{
+ let dn = new Intl.DisplayNames("en", {type: "dayPeriod"});
+
+ // Performs ToString on the input and then validates the stringified result.
+ assertThrowsInstanceOf(() => dn.of(), RangeError);
+ assertThrowsInstanceOf(() => dn.of(null), RangeError);
+ assertThrowsInstanceOf(() => dn.of(Symbol()), TypeError);
+ assertThrowsInstanceOf(() => dn.of(0), RangeError);
+ assertThrowsInstanceOf(() => dn.of(1), RangeError);
+
+ // Throws an error if not one of ["am", "pm"].
+ assertThrowsInstanceOf(() => dn.of(""), RangeError);
+ assertThrowsInstanceOf(() => dn.of("AM"), RangeError);
+ assertThrowsInstanceOf(() => dn.of("PM"), RangeError);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/DisplayNames/language.js b/js/src/tests/non262/Intl/DisplayNames/language.js
new file mode 100644
index 0000000000..c8380a8184
--- /dev/null
+++ b/js/src/tests/non262/Intl/DisplayNames/language.js
@@ -0,0 +1,195 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl'))
+
+const tests = {
+ "en": {
+ long: {
+ "de": "German",
+ "de-AT": "German (Austria)",
+ "de-1996": "German (German orthography of 1996)",
+ "en": "English",
+ "en-Hant-GB": "English (Traditional, United Kingdom)",
+ "en-Hans-US": "English (Simplified, United States)",
+ "fr": "French",
+ "nl-BE": "Dutch (Belgium)",
+ "cr-Cans": "Cree (Unified Canadian Aboriginal Syllabics)",
+ },
+ short: {
+ "en-Hant-GB": "English (Traditional, UK)",
+ "en-Hans-US": "English (Simplified, US)",
+ "cr-Cans": "Cree (UCAS)",
+ },
+ narrow: {},
+ },
+ "de": {
+ long: {
+ "de": "Deutsch",
+ "de-AT": "Deutsch (Österreich)",
+ "de-1996": "Deutsch (Neue deutsche Rechtschreibung)",
+ "en": "Englisch",
+ "en-Hant-GB": "Englisch (Traditionell, Vereinigtes Königreich)",
+ "en-Hans-US": "Englisch (Vereinfacht, Vereinigte Staaten)",
+ "fr": "Französisch",
+ "nl-BE": "Niederländisch (Belgien)",
+ },
+ short: {
+ "en-Hant-GB": "Englisch (Traditionell, GB)",
+ "en-Hans-US": "Englisch (Vereinfacht, USA)",
+ },
+ narrow: {},
+ },
+ "fr": {
+ long: {
+ "de": "allemand",
+ "de-AT": "allemand (Autriche)",
+ "de-1996": "allemand (orthographe allemande de 1996)",
+ "en": "anglais",
+ "en-Hant-GB": "anglais (traditionnel, Royaume-Uni)",
+ "en-Hans-US": "anglais (simplifié, États-Unis)",
+ "fr": "français",
+ "nl-BE": "néerlandais (Belgique)",
+ },
+ short: {
+ "en-Hant-GB": "anglais (traditionnel, R.-U.)",
+ "en-Hans-US": "anglais (simplifié, É.-U.)",
+ },
+ narrow: {},
+ },
+ "zh": {
+ long: {
+ "zh": "中文",
+ "zh-Hant": "中文(繁体)",
+ "zh-Hant-CN": "中文(繁体,中国)",
+ "zh-Hans-HK": "中文(简体,中国香港特别行政区)",
+ },
+ short: {
+ "zh-Hans-HK": "中文(简体,香港)"
+ },
+ narrow: {},
+ },
+ "ar": {
+ long: {
+ "ar": "العربية",
+ "ar-SA": "العربية (المملكة العربية السعودية)",
+ "zh-MO": "الصينية (منطقة ماكاو الإدارية الخاصة)",
+ },
+ short: {
+ "zh-MO": "الصينية (مكاو)",
+ },
+ narrow: {},
+ },
+};
+
+for (let [locale, localeTests] of Object.entries(tests)) {
+ for (let [style, styleTests] of Object.entries(localeTests)) {
+ let dn = new Intl.DisplayNames(locale, {type: "language", style});
+
+ let resolved = dn.resolvedOptions();
+ assertEq(resolved.locale, locale);
+ assertEq(resolved.style, style);
+ assertEq(resolved.type, "language");
+ assertEq(resolved.fallback, "code");
+
+ let inheritedTests = {...localeTests.long, ...localeTests.short, ...localeTests.narrow};
+ for (let [language, expected] of Object.entries({...inheritedTests, ...styleTests})) {
+ assertEq(dn.of(language), expected);
+
+ // Also works with objects.
+ assertEq(dn.of(Object(language)), expected);
+ }
+ }
+}
+
+{
+ let dn = new Intl.DisplayNames("en", {type: "language"});
+
+ // Performs ToString on the input and then validates the stringified result.
+ assertThrowsInstanceOf(() => dn.of(), RangeError);
+ assertThrowsInstanceOf(() => dn.of(null), RangeError);
+ assertThrowsInstanceOf(() => dn.of(Symbol()), TypeError);
+ assertThrowsInstanceOf(() => dn.of(0), RangeError);
+
+ // Throws an error if |code| can't be parsed as a `unicode_language_id` production.
+ assertThrowsInstanceOf(() => dn.of("en-"), RangeError);
+ assertThrowsInstanceOf(() => dn.of("en-u-ca-gregory"), RangeError);
+ assertThrowsInstanceOf(() => dn.of("en-x-private"), RangeError);
+}
+
+// Test fallback behaviour.
+{
+ let dn1 = new Intl.DisplayNames("en", {type: "language"});
+ let dn2 = new Intl.DisplayNames("en", {type: "language", fallback: "code"});
+ let dn3 = new Intl.DisplayNames("en", {type: "language", fallback: "none"});
+
+ assertEq(dn1.resolvedOptions().fallback, "code");
+ assertEq(dn2.resolvedOptions().fallback, "code");
+ assertEq(dn3.resolvedOptions().fallback, "none");
+
+ // "aaa" is not a registered language code.
+ assertEq(dn1.of("aaa"), "aaa");
+ assertEq(dn2.of("aaa"), "aaa");
+ assertEq(dn3.of("aaa"), undefined);
+
+ // "aaa" is not a registered language code.
+ assertEq(dn1.of("aaa-Latn"), "aaa-Latn");
+ assertEq(dn2.of("aaa-Latn"), "aaa-Latn");
+ assertEq(dn3.of("aaa-Latn"), undefined);
+
+ // "Aaaa" is not a registered script code.
+ assertEq(dn1.of("en-Aaaa"), "en-Aaaa");
+ assertEq(dn2.of("en-Aaaa"), "en-Aaaa");
+ assertEq(dn3.of("en-Aaaa"), undefined);
+
+ // "AA" is not a registered region code.
+ assertEq(dn1.of("en-AA"), "en-AA");
+ assertEq(dn2.of("en-AA"), "en-AA");
+ assertEq(dn3.of("en-AA"), undefined);
+
+ // "XZ" doesn't have any localised names.
+ assertEq(dn1.of("en-XZ"), "en-XZ");
+ assertEq(dn2.of("en-XZ"), "en-XZ");
+ assertEq(dn3.of("en-XZ"), undefined);
+
+ // "998" is canonicalised to "XZ".
+ assertEq(dn1.of("en-998"), "en-XZ");
+ assertEq(dn2.of("en-998"), "en-XZ");
+ assertEq(dn3.of("en-998"), undefined);
+
+ // The returned fallback is in canonical case.
+ assertEq(dn1.of("AAA"), "aaa");
+ assertEq(dn2.of("AAA"), "aaa");
+ assertEq(dn3.of("AAA"), undefined);
+
+ assertEq(dn1.of("En-aaaa"), "en-Aaaa");
+ assertEq(dn2.of("En-aaaa"), "en-Aaaa");
+ assertEq(dn3.of("En-aaaa"), undefined);
+
+ assertEq(dn1.of("EN-aa"), "en-AA");
+ assertEq(dn2.of("EN-aa"), "en-AA");
+ assertEq(dn3.of("EN-aa"), undefined);
+}
+
+// Ensure language tag canonicalisation is performed.
+{
+ let dn = new Intl.DisplayNames("en", {type: "language", fallback: "none"});
+
+ assertEq(dn.of("ru-RU"), "Russian (Russia)");
+
+ // ICU's canonicalisation supports "SU" -> "RU".
+ assertEq(Intl.getCanonicalLocales("ru-SU")[0], "ru-RU");
+ assertEq(dn.of("ru-SU"), "Russian (Russia)");
+
+ // ICU's canonicalisation doesn't support "172" -> "RU".
+ assertEq(Intl.getCanonicalLocales("ru-172")[0], "ru-RU");
+ assertEq(dn.of("ru-172"), "Russian (Russia)");
+}
+
+// Test when case isn't canonical.
+{
+ let dn = new Intl.DisplayNames("en", {type: "language", fallback: "none"});
+
+ assertEq(dn.of("IT-LATN-IT"), "Italian (Latin, Italy)");
+ assertEq(dn.of("it-latn-it"), "Italian (Latin, Italy)");
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/DisplayNames/month.js b/js/src/tests/non262/Intl/DisplayNames/month.js
new file mode 100644
index 0000000000..39629b2a86
--- /dev/null
+++ b/js/src/tests/non262/Intl/DisplayNames/month.js
@@ -0,0 +1,109 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl')||!this.hasOwnProperty('addIntlExtras'))
+
+addMozIntlDisplayNames(this);
+
+const tests = {
+ "en": {
+ long: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", 13],
+ short: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 13],
+ narrow: ["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D", 13],
+ },
+ "de": {
+ long: ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", 13],
+ short: ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez", 13],
+ narrow: ["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D", 13],
+ },
+ "fr": {
+ long: ["janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre", 13],
+ short: ["janv.", "févr.", "mars", "avr.", "mai", "juin", "juil.", "août", "sept.", "oct.", "nov.", "déc.", 13],
+ narrow: ["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D", 13],
+ },
+ "zh": {
+ long: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月", 13],
+ short: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", 13],
+ narrow: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", 13],
+ },
+ "zh-u-ca-chinese": {
+ long: ["正月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "腊月", 13],
+ short: ["正月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "腊月", 13],
+ narrow: ["正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "冬", "腊", 13],
+ },
+ "en-u-ca-hebrew": {
+ long: ["Tishri", "Heshvan", "Kislev", "Tevet", "Shevat", "Adar I", "Adar", "Nisan", "Iyar", "Sivan", "Tamuz", "Av", "Elul"],
+ short: ["Tishri", "Heshvan", "Kislev", "Tevet", "Shevat", "Adar I", "Adar", "Nisan", "Iyar", "Sivan", "Tamuz", "Av", "Elul"],
+ narrow: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13"],
+ },
+};
+
+for (let [locale, localeTests] of Object.entries(tests)) {
+ let defaultCalendar = new Intl.DateTimeFormat(locale).resolvedOptions().calendar;
+
+ for (let [style, styleTests] of Object.entries(localeTests)) {
+ let dn = new Intl.DisplayNames(locale, {type: "month", style});
+
+ let resolved = dn.resolvedOptions();
+ assertEq(resolved.locale, locale);
+ assertEq(resolved.calendar, defaultCalendar);
+ assertEq(resolved.style, style);
+ assertEq(resolved.type, "month");
+ assertEq(resolved.fallback, "code");
+
+ for (let i = 0; i < 13; i++) {
+ assertEq(dn.of(i + 1), String(styleTests[i]));
+
+ // Also works with strings.
+ assertEq(dn.of(String(i + 1)), String(styleTests[i]));
+
+ // Also works with objects.
+ assertEq(dn.of(Object(i + 1)), String(styleTests[i]));
+ }
+ }
+}
+
+{
+ let dn = new Intl.DisplayNames("en", {type: "month"});
+
+ // Performs ToString on the input and then validates the stringified result.
+ assertThrowsInstanceOf(() => dn.of(), RangeError);
+ assertThrowsInstanceOf(() => dn.of(null), RangeError);
+ assertThrowsInstanceOf(() => dn.of(Symbol()), TypeError);
+
+ // Throws an error if |code| isn't an integer.
+ assertThrowsInstanceOf(() => dn.of(1.5), RangeError);
+ assertThrowsInstanceOf(() => dn.of(-Infinity), RangeError);
+ assertThrowsInstanceOf(() => dn.of(Infinity), RangeError);
+ assertThrowsInstanceOf(() => dn.of(NaN), RangeError);
+
+ // Throws an error if outside of [1, 13].
+ assertThrowsInstanceOf(() => dn.of(-1), RangeError);
+ assertThrowsInstanceOf(() => dn.of(0), RangeError);
+ assertThrowsInstanceOf(() => dn.of(14), RangeError);
+}
+
+// Test fallback behaviour.
+{
+ let dn1 = new Intl.DisplayNames("en", {type: "month"});
+ let dn2 = new Intl.DisplayNames("en", {type: "month", fallback: "code"});
+ let dn3 = new Intl.DisplayNames("en", {type: "month", fallback: "none"});
+
+ assertEq(dn1.resolvedOptions().fallback, "code");
+ assertEq(dn2.resolvedOptions().fallback, "code");
+ assertEq(dn3.resolvedOptions().fallback, "none");
+
+ assertEq(dn1.resolvedOptions().calendar, "gregory");
+ assertEq(dn2.resolvedOptions().calendar, "gregory");
+ assertEq(dn3.resolvedOptions().calendar, "gregory");
+
+ // The Gregorian calendar doesn't have a thirteenth month.
+ assertEq(dn1.of("13"), "13");
+ assertEq(dn2.of("13"), "13");
+ assertEq(dn3.of("13"), undefined);
+
+ // The returned fallback is in "canonical" case.
+ assertEq(dn1.of("13.0"), "13");
+ assertEq(dn2.of("13.0"), "13");
+ assertEq(dn3.of("13.0"), undefined);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/DisplayNames/quarter.js b/js/src/tests/non262/Intl/DisplayNames/quarter.js
new file mode 100644
index 0000000000..279d32d3c8
--- /dev/null
+++ b/js/src/tests/non262/Intl/DisplayNames/quarter.js
@@ -0,0 +1,74 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl')||!this.hasOwnProperty('addIntlExtras'))
+
+addMozIntlDisplayNames(this);
+
+const tests = {
+ "en": {
+ long: ["1st quarter", "2nd quarter", "3rd quarter", "4th quarter"],
+ short: ["Q1", "Q2", "Q3", "Q4"],
+ narrow: ["Q1", "Q2", "Q3", "Q4"],
+ },
+ "de": {
+ long: ["1. Quartal", "2. Quartal", "3. Quartal", "4. Quartal"],
+ short: ["Q1", "Q2", "Q3", "Q4"],
+ narrow: ["Q1", "Q2", "Q3", "Q4"],
+ },
+ "fr": {
+ long: ["1er trimestre", "2e trimestre", "3e trimestre", "4e trimestre"],
+ short: ["T1", "T2", "T3", "T4"],
+ narrow: ["T1", "T2", "T3", "T4"],
+ },
+ "zh": {
+ long: ["第一季度", "第二季度", "第三季度", "第四季度"],
+ short: ["1季度", "2季度", "3季度", "4季度"],
+ narrow: ["1季度", "2季度", "3季度", "4季度"],
+ },
+};
+
+for (let [locale, localeTests] of Object.entries(tests)) {
+ let defaultCalendar = new Intl.DateTimeFormat(locale).resolvedOptions().calendar;
+
+ for (let [style, styleTests] of Object.entries(localeTests)) {
+ let dn = new Intl.DisplayNames(locale, {type: "quarter", style});
+
+ let resolved = dn.resolvedOptions();
+ assertEq(resolved.locale, locale);
+ assertEq(resolved.calendar, defaultCalendar);
+ assertEq(resolved.style, style);
+ assertEq(resolved.type, "quarter");
+ assertEq(resolved.fallback, "code");
+
+ for (let i = 0; i < 4; i++) {
+ assertEq(dn.of(i + 1), styleTests[i]);
+
+ // Also works with strings.
+ assertEq(dn.of(String(i + 1)), styleTests[i]);
+
+ // Also works with objects.
+ assertEq(dn.of(Object(i + 1)), styleTests[i]);
+ }
+ }
+}
+
+{
+ let dn = new Intl.DisplayNames("en", {type: "quarter"});
+
+ // Performs ToString on the input and then validates the stringified result.
+ assertThrowsInstanceOf(() => dn.of(), RangeError);
+ assertThrowsInstanceOf(() => dn.of(null), RangeError);
+ assertThrowsInstanceOf(() => dn.of(Symbol()), TypeError);
+
+ // Throws an error if |code| isn't an integer.
+ assertThrowsInstanceOf(() => dn.of(1.5), RangeError);
+ assertThrowsInstanceOf(() => dn.of(-Infinity), RangeError);
+ assertThrowsInstanceOf(() => dn.of(Infinity), RangeError);
+ assertThrowsInstanceOf(() => dn.of(NaN), RangeError);
+
+ // Throws an error if outside of [1, 4].
+ assertThrowsInstanceOf(() => dn.of(-1), RangeError);
+ assertThrowsInstanceOf(() => dn.of(0), RangeError);
+ assertThrowsInstanceOf(() => dn.of(5), RangeError);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/DisplayNames/region.js b/js/src/tests/non262/Intl/DisplayNames/region.js
new file mode 100644
index 0000000000..94849d6e66
--- /dev/null
+++ b/js/src/tests/non262/Intl/DisplayNames/region.js
@@ -0,0 +1,157 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl'))
+
+const tests = {
+ "en": {
+ long: {
+ "DE": "Germany",
+ "GB": "United Kingdom",
+ "US": "United States",
+ "FR": "France",
+ },
+ short: {
+ "GB": "UK",
+ "US": "US",
+ },
+ narrow: {},
+ },
+ "de": {
+ long: {
+ "DE": "Deutschland",
+ "GB": "Vereinigtes Königreich",
+ "US": "Vereinigte Staaten",
+ "FR": "Frankreich",
+ },
+ short: {
+ "GB": "GB",
+ "US": "USA",
+ },
+ narrow: {},
+ },
+ "fr": {
+ long: {
+ "DE": "Allemagne",
+ "GB": "Royaume-Uni",
+ "US": "États-Unis",
+ "FR": "France",
+ },
+ short: {
+ "GB": "R.-U.",
+ "US": "É.-U.",
+ },
+ narrow: {},
+ },
+ "zh": {
+ long: {
+ "CN": "中国",
+ "HK": "中国香港特别行政区",
+ },
+ short: {
+ "HK": "香港"
+ },
+ narrow: {},
+ },
+ "ar": {
+ long: {
+ "SA": "المملكة العربية السعودية",
+ "MO": "منطقة ماكاو الإدارية الخاصة",
+ },
+ short: {
+ "MO": "مكاو",
+ },
+ narrow: {},
+ },
+};
+
+for (let [locale, localeTests] of Object.entries(tests)) {
+ for (let [style, styleTests] of Object.entries(localeTests)) {
+ let dn = new Intl.DisplayNames(locale, {type: "region", style});
+
+ let resolved = dn.resolvedOptions();
+ assertEq(resolved.locale, locale);
+ assertEq(resolved.style, style);
+ assertEq(resolved.type, "region");
+ assertEq(resolved.fallback, "code");
+
+ let inheritedTests = {...localeTests.long, ...localeTests.short, ...localeTests.narrow};
+ for (let [region, expected] of Object.entries({...inheritedTests, ...styleTests})) {
+ assertEq(dn.of(region), expected);
+
+ // Also works with objects.
+ assertEq(dn.of(Object(region)), expected);
+ }
+ }
+}
+
+{
+ let dn = new Intl.DisplayNames("en", {type: "region"});
+
+ // Performs ToString on the input and then validates the stringified result.
+ assertThrowsInstanceOf(() => dn.of(), RangeError);
+ assertThrowsInstanceOf(() => dn.of(null), RangeError);
+ assertThrowsInstanceOf(() => dn.of(Symbol()), TypeError);
+ assertThrowsInstanceOf(() => dn.of(0), RangeError);
+
+ // Throws an error if |code| can't be parsed as a `unicode_region_subtag` production.
+ assertThrowsInstanceOf(() => dn.of("CA-"), RangeError);
+ assertThrowsInstanceOf(() => dn.of("en-CA"), RangeError);
+}
+
+// Test fallback behaviour.
+{
+ let dn1 = new Intl.DisplayNames("en", {type: "region"});
+ let dn2 = new Intl.DisplayNames("en", {type: "region", fallback: "code"});
+ let dn3 = new Intl.DisplayNames("en", {type: "region", fallback: "none"});
+
+ assertEq(dn1.resolvedOptions().fallback, "code");
+ assertEq(dn2.resolvedOptions().fallback, "code");
+ assertEq(dn3.resolvedOptions().fallback, "none");
+
+ // "AA" is not a registered region code.
+ assertEq(dn1.of("AA"), "AA");
+ assertEq(dn2.of("AA"), "AA");
+ assertEq(dn3.of("AA"), undefined);
+
+ // The returned fallback is in canonical case.
+ assertEq(dn1.of("aa"), "AA");
+ assertEq(dn2.of("aa"), "AA");
+ assertEq(dn3.of("aa"), undefined);
+
+ // "998" is canonicalised to "XZ", but "XZ" has no localised names.
+ assertEq(new Intl.Locale("und-998").region, "XZ");
+
+ // Ensure we return the input and not the canonicalised input.
+ assertEq(dn1.of("998"), "998");
+ assertEq(dn2.of("998"), "998");
+ assertEq(dn3.of("998"), undefined);
+
+ // "XZ" should be consistent with "998".
+ assertEq(dn1.of("XZ"), "XZ");
+ assertEq(dn2.of("XZ"), "XZ");
+ assertEq(dn3.of("XZ"), undefined);
+}
+
+// Ensure language tag canonicalisation is performed.
+{
+ let dn = new Intl.DisplayNames("en", {type: "region", fallback: "none"});
+
+ assertEq(dn.of("RU"), "Russia");
+
+ // ICU's canonicalisation supports "SU" -> "RU".
+ assertEq(Intl.getCanonicalLocales("ru-SU")[0], "ru-RU");
+ assertEq(dn.of("SU"), "Russia");
+
+ // ICU's canonicalisation doesn't support "172" -> "RU".
+ assertEq(Intl.getCanonicalLocales("ru-172")[0], "ru-RU");
+ assertEq(dn.of("172"), "Russia");
+}
+
+// Test when case isn't canonical.
+{
+ let dn = new Intl.DisplayNames("en", {type: "region", fallback: "none"});
+
+ assertEq(dn.of("IT"), "Italy");
+ assertEq(dn.of("it"), "Italy");
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/DisplayNames/script.js b/js/src/tests/non262/Intl/DisplayNames/script.js
new file mode 100644
index 0000000000..43b3ad5308
--- /dev/null
+++ b/js/src/tests/non262/Intl/DisplayNames/script.js
@@ -0,0 +1,134 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl'))
+
+const tests = {
+ "en": {
+ long: {
+ "Latn": "Latin",
+ "Hant": "Traditional Han",
+ "Hans": "Simplified Han",
+ "Cans": "Unified Canadian Aboriginal Syllabics",
+ },
+ short: {
+ "Hant": "Traditional",
+ "Hans": "Simplified",
+ "Cans": "UCAS",
+ },
+ narrow: {},
+ },
+ "de": {
+ long: {
+ "Latn": "Lateinisch",
+ "Hant": "Traditionelles Chinesisch",
+ "Hans": "Vereinfachtes Chinesisch",
+ },
+ short: {
+ "Hant": "Traditionell",
+ "Hans": "Vereinfacht",
+ },
+ narrow: {},
+ },
+ "fr": {
+ long: {
+ "Latn": "latin",
+ "Hant": "sinogrammes traditionnels",
+ "Hans": "sinogrammes simplifiés",
+ },
+ short: {
+ "Hant": "traditionnel",
+ "Hans": "simplifié",
+ },
+ narrow: {},
+ },
+ "zh": {
+ long: {
+ "Latn": "拉丁文",
+ "Hant": "繁体中文",
+ "Hans": "简体中文",
+ },
+ short: {
+ "Hant": "繁体",
+ "Hans": "简体",
+ },
+ narrow: {},
+ },
+ "ar": {
+ long: {
+ "Latn": "اللاتينية",
+ "Arab": "العربية",
+ "Hant": "الهان التقليدية",
+ "Hans": "الهان المبسطة",
+ },
+ short: {
+ "Hant": "التقليدية",
+ "Hans": "المبسطة",
+ },
+ narrow: {},
+ },
+};
+
+for (let [locale, localeTests] of Object.entries(tests)) {
+ for (let [style, styleTests] of Object.entries(localeTests)) {
+ let dn = new Intl.DisplayNames(locale, {type: "script", style});
+
+ let resolved = dn.resolvedOptions();
+ assertEq(resolved.locale, locale);
+ assertEq(resolved.style, style);
+ assertEq(resolved.type, "script");
+ assertEq(resolved.fallback, "code");
+
+ let inheritedTests = {...localeTests.long, ...localeTests.short, ...localeTests.narrow};
+ for (let [script, expected] of Object.entries({...inheritedTests, ...styleTests})) {
+ assertEq(dn.of(script), expected);
+
+ // Also works with objects.
+ assertEq(dn.of(Object(script)), expected);
+ }
+ }
+}
+
+{
+ let dn = new Intl.DisplayNames("en", {type: "script"});
+
+ // Performs ToString on the input and then validates the stringified result.
+ assertThrowsInstanceOf(() => dn.of(), RangeError);
+ assertThrowsInstanceOf(() => dn.of(Symbol()), TypeError);
+ assertThrowsInstanceOf(() => dn.of(0), RangeError);
+
+ // ToString(null) = "null", which passes `unicode_script_subtag`.
+ dn.of(null); // no error
+
+ // Throws an error if |code| can't be parsed as a `unicode_script_subtag` production.
+ assertThrowsInstanceOf(() => dn.of("latn-"), RangeError);
+ assertThrowsInstanceOf(() => dn.of("en-latn"), RangeError);
+}
+
+// Test fallback behaviour.
+{
+ let dn1 = new Intl.DisplayNames("en", {type: "script"});
+ let dn2 = new Intl.DisplayNames("en", {type: "script", fallback: "code"});
+ let dn3 = new Intl.DisplayNames("en", {type: "script", fallback: "none"});
+
+ assertEq(dn1.resolvedOptions().fallback, "code");
+ assertEq(dn2.resolvedOptions().fallback, "code");
+ assertEq(dn3.resolvedOptions().fallback, "none");
+
+ // "Aaaa" is not a registered script code.
+ assertEq(dn1.of("Aaaa"), "Aaaa");
+ assertEq(dn2.of("Aaaa"), "Aaaa");
+ assertEq(dn3.of("Aaaa"), undefined);
+
+ // The returned fallback is in canonical case.
+ assertEq(dn1.of("aaaa"), "Aaaa");
+ assertEq(dn2.of("aaaa"), "Aaaa");
+ assertEq(dn3.of("aaaa"), undefined);
+}
+
+// Test when case isn't canonical.
+{
+ let dn = new Intl.DisplayNames("en", {type: "script", fallback: "none"});
+
+ assertEq(dn.of("LATN"), "Latin");
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/DisplayNames/shell.js b/js/src/tests/non262/Intl/DisplayNames/shell.js
new file mode 100644
index 0000000000..20326b0254
--- /dev/null
+++ b/js/src/tests/non262/Intl/DisplayNames/shell.js
@@ -0,0 +1,6 @@
+// Add |Intl.MozDisplayNames| to the Intl object.
+function addMozIntlDisplayNames(global) {
+ let obj = {};
+ global.addIntlExtras(obj);
+ global.Intl.DisplayNames = obj.DisplayNames;
+}
diff --git a/js/src/tests/non262/Intl/DisplayNames/weekday.js b/js/src/tests/non262/Intl/DisplayNames/weekday.js
new file mode 100644
index 0000000000..fd19f24bb1
--- /dev/null
+++ b/js/src/tests/non262/Intl/DisplayNames/weekday.js
@@ -0,0 +1,77 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl')||!this.hasOwnProperty('addIntlExtras'))
+
+addMozIntlDisplayNames(this);
+
+const tests = {
+ "en": {
+ long: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
+ // short: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
+ short: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
+ narrow: ["M", "T", "W", "T", "F", "S", "S"],
+ },
+ "de": {
+ long: ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"],
+ // short: ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"],
+ short: ["Mo.", "Di.", "Mi.", "Do.", "Fr.", "Sa.", "So."],
+ narrow: ["M", "D", "M", "D", "F", "S", "S"],
+ },
+ "fr": {
+ long: ["lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche"],
+ // short: ["lun.", "mar.", "mer.", "jeu.", "ven.", "sam.", "dim."],
+ short: ["lu", "ma", "me", "je", "ve", "sa", "di"],
+ narrow: ["L", "M", "M", "J", "V", "S", "D"],
+ },
+ "zh": {
+ long: ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"],
+ short: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"],
+ narrow: ["一", "二", "三", "四", "五", "六", "日"],
+ },
+};
+
+for (let [locale, localeTests] of Object.entries(tests)) {
+ let defaultCalendar = new Intl.DateTimeFormat(locale).resolvedOptions().calendar;
+
+ for (let [style, styleTests] of Object.entries(localeTests)) {
+ let dn = new Intl.DisplayNames(locale, {type: "weekday", style});
+
+ let resolved = dn.resolvedOptions();
+ assertEq(resolved.locale, locale);
+ assertEq(resolved.calendar, defaultCalendar);
+ assertEq(resolved.style, style);
+ assertEq(resolved.type, "weekday");
+ assertEq(resolved.fallback, "code");
+
+ for (let i = 0; i < 7; i++) {
+ assertEq(dn.of(i + 1), styleTests[i]);
+
+ // Also works with strings.
+ assertEq(dn.of(String(i + 1)), styleTests[i]);
+
+ // Also works with objects.
+ assertEq(dn.of(Object(i + 1)), styleTests[i]);
+ }
+ }
+}
+
+{
+ let dn = new Intl.DisplayNames("en", {type: "weekday"});
+
+ // Performs ToString on the input and then validates the stringified result.
+ assertThrowsInstanceOf(() => dn.of(), RangeError);
+ assertThrowsInstanceOf(() => dn.of(null), RangeError);
+ assertThrowsInstanceOf(() => dn.of(Symbol()), TypeError);
+
+ // Throws an error if |code| isn't an integer.
+ assertThrowsInstanceOf(() => dn.of(1.5), RangeError);
+ assertThrowsInstanceOf(() => dn.of(-Infinity), RangeError);
+ assertThrowsInstanceOf(() => dn.of(Infinity), RangeError);
+ assertThrowsInstanceOf(() => dn.of(NaN), RangeError);
+
+ // Throws an error if outside of [1, 7].
+ assertThrowsInstanceOf(() => dn.of(-1), RangeError);
+ assertThrowsInstanceOf(() => dn.of(0), RangeError);
+ assertThrowsInstanceOf(() => dn.of(8), RangeError);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/ListFormat/browser.js b/js/src/tests/non262/Intl/ListFormat/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Intl/ListFormat/browser.js
diff --git a/js/src/tests/non262/Intl/ListFormat/conjunction-type.js b/js/src/tests/non262/Intl/ListFormat/conjunction-type.js
new file mode 100644
index 0000000000..daf1234499
--- /dev/null
+++ b/js/src/tests/non262/Intl/ListFormat/conjunction-type.js
@@ -0,0 +1,112 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl'))
+
+// Note: Use the same test locales as used in unit-type.js
+
+const {Element, Literal} = ListFormatParts;
+const styles = ["long", "short", "narrow"];
+
+// Test with zero elements.
+{
+ const list = [];
+ const expected = [];
+ const locales = ["ar", "de", "en", "es", "ja", "nl", "th", "zh"];
+
+ for (let locale of locales) {
+ for (let style of styles) {
+ let lf = new Intl.ListFormat(locale, {type: "conjunction", style});
+ assertParts(lf, list, expected);
+ }
+ }
+}
+
+// Test with one element.
+{
+ const list = ["A"];
+ const expected = [Element(list[0])];
+ const locales = ["ar", "de", "en", "es", "ja", "nl", "th", "zh"];
+
+ for (let locale of locales) {
+ for (let style of styles) {
+ let lf = new Intl.ListFormat(locale, {type: "conjunction", style});
+ assertParts(lf, list, expected);
+ }
+ }
+}
+
+// Test with two elements to cover the [[Template2]] case.
+{
+ const list = ["A", "B"];
+
+ const testData = {
+ "ar": { long: [Element("A"), Literal(" و"), Element("B")] },
+ "de": { long: [Element("A"), Literal(" und "), Element("B")] },
+ "en": {
+ long: [Element("A"), Literal(" and "), Element("B")],
+ short: [Element("A"), Literal(" & "), Element("B")],
+ narrow: [Element("A"), Literal(", "), Element("B")],
+ },
+ "es": { long: [Element("A"), Literal(" y "), Element("B")] },
+ "ja": { long: [Element("A"), Literal("、"), Element("B")] },
+ "nl": { long: [Element("A"), Literal(" en "), Element("B")] },
+ "th": { long: [Element("A"), Literal("และ"), Element("B")] },
+ "zh": { long: [Element("A"), Literal("和"), Element("B")] },
+ };
+
+ for (let [locale, localeData] of Object.entries(testData)) {
+ for (let style of styles) {
+ let lf = new Intl.ListFormat(locale, {type: "conjunction", style});
+ let {[style]: expected = localeData.long} = localeData;
+ assertParts(lf, list, expected);
+ }
+ }
+}
+
+// Test with more than two elements.
+//
+// Use four elements to cover all template parts ([[TemplateStart]], [[TemplateMiddle]], and
+// [[TemplateEnd]]).
+{
+ const list = ["A", "B", "C", "D"];
+
+ const testData = {
+ "ar": {
+ long: [Element("A"), Literal(" و"), Element("B"), Literal(" و"), Element("C"), Literal(" و"), Element("D")],
+ short: [Element("A"), Literal(" و"), Element("B"), Literal(" و"), Element("C"), Literal(" و"), Element("D")],
+ narrow: [Element("A"), Literal(" و"), Element("B"), Literal(" و"), Element("C"), Literal(" و"), Element("D")],
+ },
+ "de": {
+ long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" und "), Element("D")],
+ },
+ "en": {
+ long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(", and "), Element("D")],
+ short: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(", & "), Element("D")],
+ narrow: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(", "), Element("D")],
+ },
+ "es": {
+ long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" y "), Element("D")],
+ },
+ "ja": {
+ long: [Element("A"), Literal("、"), Element("B"), Literal("、"), Element("C"), Literal("、"), Element("D")],
+ },
+ "nl": {
+ long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" en "), Element("D")],
+ },
+ "th": {
+ long: [Element("A"), Literal(" "), Element("B"), Literal(" "), Element("C"), Literal(" และ"), Element("D")],
+ },
+ "zh": {
+ long: [Element("A"), Literal("、"), Element("B"), Literal("、"), Element("C"), Literal("和"), Element("D")],
+ },
+ };
+
+ for (let [locale, localeData] of Object.entries(testData)) {
+ for (let style of styles) {
+ let lf = new Intl.ListFormat(locale, {type: "conjunction", style});
+ let {[style]: expected = localeData.long} = localeData;
+ assertParts(lf, list, expected);
+ }
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/ListFormat/cross-compartment.js b/js/src/tests/non262/Intl/ListFormat/cross-compartment.js
new file mode 100644
index 0000000000..37c45ef75c
--- /dev/null
+++ b/js/src/tests/non262/Intl/ListFormat/cross-compartment.js
@@ -0,0 +1,42 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl'))
+
+var g = newGlobal();
+
+var locale = "en";
+var list = ["a", "b", "c"];
+
+var listFormat = new Intl.ListFormat(locale);
+var ccwListFormat = new g.Intl.ListFormat(locale);
+
+// Intl.ListFormat.prototype.format
+{
+ var fn = Intl.ListFormat.prototype.format;
+
+ var expectedValue = fn.call(listFormat, list);
+ var actualValue = fn.call(ccwListFormat, list);
+
+ assertEq(actualValue, expectedValue);
+}
+
+// Intl.ListFormat.prototype.formatToParts
+{
+ var fn = Intl.ListFormat.prototype.formatToParts;
+
+ var expectedValue = fn.call(listFormat, list);
+ var actualValue = fn.call(ccwListFormat, list);
+
+ assertDeepEq(actualValue, expectedValue);
+}
+
+// Intl.ListFormat.prototype.resolvedOptions
+{
+ var fn = Intl.ListFormat.prototype.resolvedOptions;
+
+ var expectedValue = fn.call(listFormat);
+ var actualValue = fn.call(ccwListFormat);
+
+ assertDeepEq(actualValue, expectedValue);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/ListFormat/disjunction-type.js b/js/src/tests/non262/Intl/ListFormat/disjunction-type.js
new file mode 100644
index 0000000000..2713a8ae07
--- /dev/null
+++ b/js/src/tests/non262/Intl/ListFormat/disjunction-type.js
@@ -0,0 +1,108 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl'))
+
+// Note: Use the same test locales as used in unit-type.js
+
+const {Element, Literal} = ListFormatParts;
+const styles = ["long", "short", "narrow"];
+
+// Test with zero elements.
+{
+ const list = [];
+ const expected = [];
+ const locales = ["ar", "de", "en", "es", "ja", "nl", "th", "zh"];
+
+ for (let locale of locales) {
+ for (let style of styles) {
+ let lf = new Intl.ListFormat(locale, {type: "disjunction", style});
+ assertParts(lf, list, expected);
+ }
+ }
+}
+
+// Test with one element.
+{
+ const list = ["A"];
+ const expected = [Element(list[0])];
+ const locales = ["ar", "de", "en", "es", "ja", "nl", "th", "zh"];
+
+ for (let locale of locales) {
+ for (let style of styles) {
+ let lf = new Intl.ListFormat(locale, {type: "disjunction", style});
+ assertParts(lf, list, expected);
+ }
+ }
+}
+
+// Test with two elements to cover the [[Template2]] case.
+{
+ const list = ["A", "B"];
+
+ const testData = {
+ "ar": { long: [Element("A"), Literal(" أو "), Element("B")] },
+ "de": { long: [Element("A"), Literal(" oder "), Element("B")] },
+ "en": { long: [Element("A"), Literal(" or "), Element("B")] },
+ "es": { long: [Element("A"), Literal(" o "), Element("B")] },
+ "ja": { long: [Element("A"), Literal("または"), Element("B")] },
+ "nl": { long: [Element("A"), Literal(" of "), Element("B")] },
+ "th": {
+ long: [Element("A"), Literal(" หรือ "), Element("B")],
+ short: [Element("A"), Literal("หรือ"), Element("B")],
+ narrow: [Element("A"), Literal("หรือ"), Element("B")],
+ },
+ "zh": { long: [Element("A"), Literal("或"), Element("B")] },
+ };
+
+ for (let [locale, localeData] of Object.entries(testData)) {
+ for (let style of styles) {
+ let lf = new Intl.ListFormat(locale, {type: "disjunction", style});
+ let {[style]: expected = localeData.long} = localeData;
+ assertParts(lf, list, expected);
+ }
+ }
+}
+
+// Test with more than two elements.
+//
+// Use four elements to cover all template parts ([[TemplateStart]], [[TemplateMiddle]], and
+// [[TemplateEnd]]).
+{
+ const list = ["A", "B", "C", "D"];
+
+ const testData = {
+ "ar": {
+ long: [Element("A"), Literal(" أو "), Element("B"), Literal(" أو "), Element("C"), Literal(" أو "), Element("D")],
+ },
+ "de": {
+ long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" oder "), Element("D")],
+ },
+ "en": {
+ long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(", or "), Element("D")],
+ },
+ "es": {
+ long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" o "), Element("D")],
+ },
+ "ja": {
+ long: [Element("A"), Literal("、"), Element("B"), Literal("、"), Element("C"), Literal("、または"), Element("D")],
+ },
+ "nl": {
+ long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" of "), Element("D")],
+ },
+ "th": {
+ long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" หรือ "), Element("D")],
+ },
+ "zh": {
+ long: [Element("A"), Literal("、"), Element("B"), Literal("、"), Element("C"), Literal("或"), Element("D")],
+ },
+ };
+
+ for (let [locale, localeData] of Object.entries(testData)) {
+ for (let style of styles) {
+ let lf = new Intl.ListFormat(locale, {type: "disjunction", style});
+ let {[style]: expected = localeData.long} = localeData;
+ assertParts(lf, list, expected);
+ }
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/ListFormat/same-compartment.js b/js/src/tests/non262/Intl/ListFormat/same-compartment.js
new file mode 100644
index 0000000000..a51a041a08
--- /dev/null
+++ b/js/src/tests/non262/Intl/ListFormat/same-compartment.js
@@ -0,0 +1,40 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl')||!this.wrapWithProto)
+
+var locale = "en";
+var list = ["a", "b", "c"];
+
+var listFormat = new Intl.ListFormat(locale);
+var scwListFormat = wrapWithProto(listFormat, Intl.ListFormat.prototype);
+
+// Intl.ListFormat.prototype.format
+{
+ var fn = Intl.ListFormat.prototype.format;
+
+ var expectedValue = fn.call(listFormat, list);
+ var actualValue = fn.call(scwListFormat, list);
+
+ assertEq(actualValue, expectedValue);
+}
+
+// Intl.ListFormat.prototype.formatToParts
+{
+ var fn = Intl.ListFormat.prototype.formatToParts;
+
+ var expectedValue = fn.call(listFormat, list);
+ var actualValue = fn.call(scwListFormat, list);
+
+ assertDeepEq(actualValue, expectedValue);
+}
+
+// Intl.ListFormat.prototype.resolvedOptions
+{
+ var fn = Intl.ListFormat.prototype.resolvedOptions;
+
+ var expectedValue = fn.call(listFormat);
+ var actualValue = fn.call(scwListFormat);
+
+ assertDeepEq(actualValue, expectedValue);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/ListFormat/shell.js b/js/src/tests/non262/Intl/ListFormat/shell.js
new file mode 100644
index 0000000000..70056a579e
--- /dev/null
+++ b/js/src/tests/non262/Intl/ListFormat/shell.js
@@ -0,0 +1,21 @@
+function GenericPartCreator(type) {
+ return str => ({ type, value: str });
+}
+
+const ListFormatParts = {
+ Element: GenericPartCreator("element"),
+ Literal: GenericPartCreator("literal"),
+};
+
+function assertParts(lf, x, expected) {
+ var parts = lf.formatToParts(x);
+ assertEq(parts.map(part => part.value).join(""), lf.format(x),
+ "formatToParts and format must agree");
+
+ var len = parts.length;
+ assertEq(len, expected.length, "parts count mismatch");
+ for (var i = 0; i < len; i++) {
+ assertEq(parts[i].type, expected[i].type, "type mismatch at " + i);
+ assertEq(parts[i].value, expected[i].value, "value mismatch at " + i);
+ }
+}
diff --git a/js/src/tests/non262/Intl/ListFormat/supported-locales.js b/js/src/tests/non262/Intl/ListFormat/supported-locales.js
new file mode 100644
index 0000000000..f5d05e2da5
--- /dev/null
+++ b/js/src/tests/non262/Intl/ListFormat/supported-locales.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl'))
+
+// Intl.ListFormat.supportedLocalesOf returns an empty array for unsupported locales.
+assertEq(Intl.ListFormat.supportedLocalesOf("art-lobjan").length, 0);
+
+// And a non-empty array for supported locales.
+assertEq(Intl.ListFormat.supportedLocalesOf("en").length, 1);
+assertEq(Intl.ListFormat.supportedLocalesOf("en")[0], "en");
+
+// If the locale is supported per |Intl.ListFormat.supportedLocalesOf|, the resolved locale
+// should reflect this.
+for (let locale of Intl.ListFormat.supportedLocalesOf(["en", "de", "th", "ar"])) {
+ let lf = new Intl.ListFormat(locale);
+ assertEq(lf.resolvedOptions().locale, locale);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/ListFormat/unit-type.js b/js/src/tests/non262/Intl/ListFormat/unit-type.js
new file mode 100644
index 0000000000..8c76677865
--- /dev/null
+++ b/js/src/tests/non262/Intl/ListFormat/unit-type.js
@@ -0,0 +1,149 @@
+// |reftest| skip -- "unit" type currently not supported
+
+const {Element, Literal} = ListFormatParts;
+const styles = ["long", "short", "narrow"];
+
+// Test with zero elements.
+{
+ const list = [];
+ const expected = [];
+ const locales = ["ar", "de", "en", "es", "ja", "nl", "th", "zh"];
+
+ for (let locale of locales) {
+ for (let style of styles) {
+ let lf = new Intl.ListFormat(locale, {type: "unit", style});
+ assertParts(lf, list, expected);
+ }
+ }
+}
+
+// Test with one element.
+{
+ const list = ["A"];
+ const expected = [Element(list[0])];
+ const locales = ["ar", "de", "en", "es", "ja", "nl", "th", "zh"];
+
+ for (let locale of locales) {
+ for (let style of styles) {
+ let lf = new Intl.ListFormat(locale, {type: "unit", style});
+ assertParts(lf, list, expected);
+ }
+ }
+}
+
+// Test with two elements to cover the [[Template2]] case.
+{
+ const list = ["A", "B"];
+
+ const testData = {
+ "ar": {
+ long: [Element("A"), Literal(" و"), Element("B")],
+ narrow: [Element("A"), Literal("، "), Element("B")],
+ },
+ "de": {
+ long: [Element("A"), Literal(", "), Element("B")],
+ },
+ "en": {
+ long: [Element("A"), Literal(", "), Element("B")],
+ narrow: [Element("A"), Literal(" "), Element("B")],
+ },
+ "es": {
+ long: [Element("A"), Literal(" y "), Element("B")],
+ narrow: [Element("A"), Literal(" "), Element("B")],
+ },
+ "ja": {
+ long: [Element("A"), Literal(" "), Element("B")],
+ narrow: [Element("A"), Element("B")],
+ },
+ "nl": {
+ long: [Element("A"), Literal(" en "), Element("B")],
+ short: [Element("A"), Literal(", "), Element("B")],
+ narrow: [Element("A"), Literal(", "), Element("B")],
+ },
+ "th": {
+ long: [Element("A"), Literal(" และ "), Element("B")],
+ short: [Element("A"), Literal(" "), Element("B")],
+ narrow: [Element("A"), Literal(" "), Element("B")],
+ },
+ "zh": {
+ long: [Element("A"), Element("B")],
+ },
+ };
+
+ for (let [locale, localeData] of Object.entries(testData)) {
+ for (let style of styles) {
+ let lf = new Intl.ListFormat(locale, {type: "unit", style});
+ let {[style]: expected = localeData.long} = localeData;
+ assertParts(lf, list, expected);
+ }
+ }
+}
+
+// Test with more than two elements.
+//
+// Use four elements to cover all template parts ([[TemplateStart]], [[TemplateMiddle]], and
+// [[TemplateEnd]]).
+{
+ const list = ["A", "B", "C", "D"];
+
+ const testData = {
+ // non-ASCII case
+ "ar": {
+ long: [Element("A"), Literal("، و"), Element("B"), Literal("، و"), Element("C"), Literal("، و"), Element("D")],
+ narrow: [Element("A"), Literal("، "), Element("B"), Literal("، "), Element("C"), Literal("، "), Element("D")],
+ },
+
+ // all values are equal
+ "de": {
+ long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" und "), Element("D")],
+ },
+
+ // long and short values are equal
+ "en": {
+ long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(", "), Element("D")],
+ narrow: [Element("A"), Literal(" "), Element("B"), Literal(" "), Element("C"), Literal(" "), Element("D")],
+ },
+
+ // all values are different
+ "es": {
+ long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" y "), Element("D")],
+ short: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(", "), Element("D")],
+ narrow: [Element("A"), Literal(" "), Element("B"), Literal(" "), Element("C"), Literal(" "), Element("D")],
+ },
+
+ // no spacing for narrow case
+ "ja": {
+ long: [Element("A"), Literal(" "), Element("B"), Literal(" "), Element("C"), Literal(" "), Element("D")],
+ narrow: [Element("A"), Element("B"), Element("C"), Element("D")],
+ },
+
+ // short and narrow values are equal
+ "nl": {
+ long: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(" en "), Element("D")],
+ short: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(", "), Element("D")],
+ narrow: [Element("A"), Literal(", "), Element("B"), Literal(", "), Element("C"), Literal(", "), Element("D")],
+ },
+
+ // another non-ASCII case
+ "th": {
+ long: [Element("A"), Literal(" "), Element("B"), Literal(" "), Element("C"), Literal(" และ "), Element("D")],
+ narrow: [Element("A"), Literal(" "), Element("B"), Literal(" "), Element("C"), Literal(" "), Element("D")],
+ },
+
+ // no whitespace at all
+ "zh": {
+ long: [Element("A"), Element("B"), Element("C"), Element("D")],
+ },
+ };
+
+ for (let [locale, localeData] of Object.entries(testData)) {
+ for (let style of styles) {
+ let lf = new Intl.ListFormat(locale, {type: "unit", style});
+ let {[style]: expected = localeData.long} = localeData;
+ assertParts(lf, list, expected);
+ }
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/Locale/apply-options-to-tag-canonicalize-twice.js b/js/src/tests/non262/Intl/Locale/apply-options-to-tag-canonicalize-twice.js
new file mode 100644
index 0000000000..2a96200757
--- /dev/null
+++ b/js/src/tests/non262/Intl/Locale/apply-options-to-tag-canonicalize-twice.js
@@ -0,0 +1,11 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl'))
+
+// ApplyOptionsToTag canonicalises the locale identifier before applying the
+// options. That means "und-Armn-SU" is first canonicalised to "und-Armn-AM",
+// then the language is changed to "ru". If "ru" were applied first, the result
+// would be "ru-Armn-RU" instead.
+assertEq(new Intl.Locale("und-Armn-SU", {language:"ru"}).toString(),
+ "ru-Armn-AM");
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/Locale/browser.js b/js/src/tests/non262/Intl/Locale/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Intl/Locale/browser.js
diff --git a/js/src/tests/non262/Intl/Locale/coerce-options-before-validating-tag.js b/js/src/tests/non262/Intl/Locale/coerce-options-before-validating-tag.js
new file mode 100644
index 0000000000..7f57a61132
--- /dev/null
+++ b/js/src/tests/non262/Intl/Locale/coerce-options-before-validating-tag.js
@@ -0,0 +1,10 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl'))
+
+// Throw a TypeError when the |options| argument is null before validating the
+// language tag argument.
+assertThrowsInstanceOf(() => {
+ new Intl.Locale("invalid_tag", null);
+}, TypeError);
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/Locale/cross-compartment.js b/js/src/tests/non262/Intl/Locale/cross-compartment.js
new file mode 100644
index 0000000000..d3788e3eb7
--- /dev/null
+++ b/js/src/tests/non262/Intl/Locale/cross-compartment.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl'))
+
+var g = newGlobal();
+
+var tag = "de-Latn-AT-u-ca-gregory-nu-latn-co-phonebk-kf-false-kn-hc-h23";
+var locale = new Intl.Locale(tag);
+var ccwLocale = new g.Intl.Locale(tag);
+
+for (var [key, {get, value = get}] of Object.entries(Object.getOwnPropertyDescriptors(Intl.Locale.prototype))) {
+ if (typeof value === "function") {
+ if (key !== "constructor") {
+ var expectedValue = value.call(locale);
+
+ if (typeof expectedValue === "string" || typeof expectedValue === "boolean") {
+ assertEq(value.call(ccwLocale), expectedValue, key);
+ } else if (expectedValue instanceof Intl.Locale) {
+ assertEq(value.call(ccwLocale).toString(), expectedValue.toString(), key);
+ } else {
+ throw new Error("unexpected result value");
+ }
+ } else {
+ assertEq(new value(ccwLocale).toString(), new value(locale).toString(), key);
+ }
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/Locale/grandfathered.js b/js/src/tests/non262/Intl/Locale/grandfathered.js
new file mode 100644
index 0000000000..eb52bc62b4
--- /dev/null
+++ b/js/src/tests/non262/Intl/Locale/grandfathered.js
@@ -0,0 +1,75 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl'))
+
+var testData = [
+ {
+ tag: "cel-gaulish",
+ options: {
+ numberingSystem: "latn",
+ },
+ canonical: "xtg-u-nu-latn-x-cel-gaulish",
+ extensions: {
+ numberingSystem: "latn",
+ },
+ },
+
+ {
+ tag: "cel-gaulish",
+ options: {
+ region: "FR",
+ numberingSystem: "latn",
+ },
+ canonical: "xtg-FR-u-nu-latn-x-cel-gaulish",
+ extensions: {
+ numberingSystem: "latn",
+ },
+ },
+
+ {
+ tag: "art-lojban",
+ options: {
+ numberingSystem: "latn",
+ },
+ canonical: "jbo-u-nu-latn",
+ extensions: {
+ numberingSystem: "latn",
+ },
+ },
+
+ {
+ tag: "art-lojban",
+ options: {
+ region: "ZZ",
+ numberingSystem: "latn",
+ },
+ canonical: "jbo-ZZ-u-nu-latn",
+ extensions: {
+ numberingSystem: "latn",
+ },
+ },
+];
+
+for (var {tag, options, canonical, extensions} of testData) {
+ var loc = new Intl.Locale(tag, options);
+ assertEq(loc.toString(), canonical);
+
+ for (var [name, value] of Object.entries(extensions)) {
+ assertEq(loc[name], value);
+ }
+}
+
+var errorTestData = [
+ "en-gb-oed",
+ "i-default",
+ "sgn-ch-de",
+ "zh-min",
+ "zh-min-nan",
+ "zh-hakka-hakka",
+];
+
+for (var tag of errorTestData) {
+ assertThrowsInstanceOf(() => new Intl.Locale(tag), RangeError);
+ assertThrowsInstanceOf(() => new Intl.Locale(tag, {}), RangeError);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/Locale/likely-subtags-generated.js b/js/src/tests/non262/Intl/Locale/likely-subtags-generated.js
new file mode 100644
index 0000000000..ed9f9016d6
--- /dev/null
+++ b/js/src/tests/non262/Intl/Locale/likely-subtags-generated.js
@@ -0,0 +1,3465 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl'))
+// Generated by make_intl_data.py. DO NOT EDIT.
+
+// Extracted from likelySubtags.xml.
+// Derived from CLDR Supplemental Data, version 37.
+// https://unicode.org/Public/cldr/37/core.zip
+var maxLikelySubtags = {
+ "aa": "aa-Latn-ET",
+ "aai": "aai-Latn-ZZ",
+ "aak": "aak-Latn-ZZ",
+ "aau": "aau-Latn-ZZ",
+ "ab": "ab-Cyrl-GE",
+ "abi": "abi-Latn-ZZ",
+ "abq": "abq-Cyrl-ZZ",
+ "abr": "abr-Latn-GH",
+ "abt": "abt-Latn-ZZ",
+ "aby": "aby-Latn-ZZ",
+ "acd": "acd-Latn-ZZ",
+ "ace": "ace-Latn-ID",
+ "ach": "ach-Latn-UG",
+ "ada": "ada-Latn-GH",
+ "ade": "ade-Latn-ZZ",
+ "adj": "adj-Latn-ZZ",
+ "adp": "dz-Tibt-BT",
+ "ady": "ady-Cyrl-RU",
+ "adz": "adz-Latn-ZZ",
+ "ae": "ae-Avst-IR",
+ "aeb": "aeb-Arab-TN",
+ "aey": "aey-Latn-ZZ",
+ "af": "af-Latn-ZA",
+ "agc": "agc-Latn-ZZ",
+ "agd": "agd-Latn-ZZ",
+ "agg": "agg-Latn-ZZ",
+ "agm": "agm-Latn-ZZ",
+ "ago": "ago-Latn-ZZ",
+ "agq": "agq-Latn-CM",
+ "aha": "aha-Latn-ZZ",
+ "ahl": "ahl-Latn-ZZ",
+ "aho": "aho-Ahom-IN",
+ "ajg": "ajg-Latn-ZZ",
+ "ak": "ak-Latn-GH",
+ "akk": "akk-Xsux-IQ",
+ "ala": "ala-Latn-ZZ",
+ "ali": "ali-Latn-ZZ",
+ "aln": "aln-Latn-XK",
+ "alt": "alt-Cyrl-RU",
+ "am": "am-Ethi-ET",
+ "amm": "amm-Latn-ZZ",
+ "amn": "amn-Latn-ZZ",
+ "amo": "amo-Latn-NG",
+ "amp": "amp-Latn-ZZ",
+ "an": "an-Latn-ES",
+ "anc": "anc-Latn-ZZ",
+ "ank": "ank-Latn-ZZ",
+ "ann": "ann-Latn-ZZ",
+ "any": "any-Latn-ZZ",
+ "aoj": "aoj-Latn-ZZ",
+ "aom": "aom-Latn-ZZ",
+ "aoz": "aoz-Latn-ID",
+ "apc": "apc-Arab-ZZ",
+ "apd": "apd-Arab-TG",
+ "ape": "ape-Latn-ZZ",
+ "apr": "apr-Latn-ZZ",
+ "aps": "aps-Latn-ZZ",
+ "apz": "apz-Latn-ZZ",
+ "ar": "ar-Arab-EG",
+ "arc": "arc-Armi-IR",
+ "arc-Nbat": "arc-Nbat-JO",
+ "arc-Palm": "arc-Palm-SY",
+ "arh": "arh-Latn-ZZ",
+ "arn": "arn-Latn-CL",
+ "aro": "aro-Latn-BO",
+ "arq": "arq-Arab-DZ",
+ "ars": "ars-Arab-SA",
+ "ary": "ary-Arab-MA",
+ "arz": "arz-Arab-EG",
+ "as": "as-Beng-IN",
+ "asa": "asa-Latn-TZ",
+ "ase": "ase-Sgnw-US",
+ "asg": "asg-Latn-ZZ",
+ "aso": "aso-Latn-ZZ",
+ "ast": "ast-Latn-ES",
+ "ata": "ata-Latn-ZZ",
+ "atg": "atg-Latn-ZZ",
+ "atj": "atj-Latn-CA",
+ "auy": "auy-Latn-ZZ",
+ "av": "av-Cyrl-RU",
+ "avl": "avl-Arab-ZZ",
+ "avn": "avn-Latn-ZZ",
+ "avt": "avt-Latn-ZZ",
+ "avu": "avu-Latn-ZZ",
+ "awa": "awa-Deva-IN",
+ "awb": "awb-Latn-ZZ",
+ "awo": "awo-Latn-ZZ",
+ "awx": "awx-Latn-ZZ",
+ "ay": "ay-Latn-BO",
+ "ayb": "ayb-Latn-ZZ",
+ "az": "az-Latn-AZ",
+ "az-Arab": "az-Arab-IR",
+ "az-IQ": "az-Arab-IQ",
+ "az-IR": "az-Arab-IR",
+ "az-RU": "az-Cyrl-RU",
+ "ba": "ba-Cyrl-RU",
+ "bal": "bal-Arab-PK",
+ "ban": "ban-Latn-ID",
+ "bap": "bap-Deva-NP",
+ "bar": "bar-Latn-AT",
+ "bas": "bas-Latn-CM",
+ "bav": "bav-Latn-ZZ",
+ "bax": "bax-Bamu-CM",
+ "bba": "bba-Latn-ZZ",
+ "bbb": "bbb-Latn-ZZ",
+ "bbc": "bbc-Latn-ID",
+ "bbd": "bbd-Latn-ZZ",
+ "bbj": "bbj-Latn-CM",
+ "bbp": "bbp-Latn-ZZ",
+ "bbr": "bbr-Latn-ZZ",
+ "bcf": "bcf-Latn-ZZ",
+ "bch": "bch-Latn-ZZ",
+ "bci": "bci-Latn-CI",
+ "bcm": "bcm-Latn-ZZ",
+ "bcn": "bcn-Latn-ZZ",
+ "bco": "bco-Latn-ZZ",
+ "bcq": "bcq-Ethi-ZZ",
+ "bcu": "bcu-Latn-ZZ",
+ "bdd": "bdd-Latn-ZZ",
+ "be": "be-Cyrl-BY",
+ "bef": "bef-Latn-ZZ",
+ "beh": "beh-Latn-ZZ",
+ "bej": "bej-Arab-SD",
+ "bem": "bem-Latn-ZM",
+ "bet": "bet-Latn-ZZ",
+ "bew": "bew-Latn-ID",
+ "bex": "bex-Latn-ZZ",
+ "bez": "bez-Latn-TZ",
+ "bfd": "bfd-Latn-CM",
+ "bfq": "bfq-Taml-IN",
+ "bft": "bft-Arab-PK",
+ "bfy": "bfy-Deva-IN",
+ "bg": "bg-Cyrl-BG",
+ "bgc": "bgc-Deva-IN",
+ "bgn": "bgn-Arab-PK",
+ "bgx": "bgx-Grek-TR",
+ "bhb": "bhb-Deva-IN",
+ "bhg": "bhg-Latn-ZZ",
+ "bhi": "bhi-Deva-IN",
+ "bhl": "bhl-Latn-ZZ",
+ "bho": "bho-Deva-IN",
+ "bhy": "bhy-Latn-ZZ",
+ "bi": "bi-Latn-VU",
+ "bib": "bib-Latn-ZZ",
+ "big": "big-Latn-ZZ",
+ "bik": "bik-Latn-PH",
+ "bim": "bim-Latn-ZZ",
+ "bin": "bin-Latn-NG",
+ "bio": "bio-Latn-ZZ",
+ "biq": "biq-Latn-ZZ",
+ "bjh": "bjh-Latn-ZZ",
+ "bji": "bji-Ethi-ZZ",
+ "bjj": "bjj-Deva-IN",
+ "bjn": "bjn-Latn-ID",
+ "bjo": "bjo-Latn-ZZ",
+ "bjr": "bjr-Latn-ZZ",
+ "bjt": "bjt-Latn-SN",
+ "bjz": "bjz-Latn-ZZ",
+ "bkc": "bkc-Latn-ZZ",
+ "bkm": "bkm-Latn-CM",
+ "bkq": "bkq-Latn-ZZ",
+ "bku": "bku-Latn-PH",
+ "bkv": "bkv-Latn-ZZ",
+ "blt": "blt-Tavt-VN",
+ "bm": "bm-Latn-ML",
+ "bmh": "bmh-Latn-ZZ",
+ "bmk": "bmk-Latn-ZZ",
+ "bmq": "bmq-Latn-ML",
+ "bmu": "bmu-Latn-ZZ",
+ "bn": "bn-Beng-BD",
+ "bng": "bng-Latn-ZZ",
+ "bnm": "bnm-Latn-ZZ",
+ "bnp": "bnp-Latn-ZZ",
+ "bo": "bo-Tibt-CN",
+ "boj": "boj-Latn-ZZ",
+ "bom": "bom-Latn-ZZ",
+ "bon": "bon-Latn-ZZ",
+ "bpy": "bpy-Beng-IN",
+ "bqc": "bqc-Latn-ZZ",
+ "bqi": "bqi-Arab-IR",
+ "bqp": "bqp-Latn-ZZ",
+ "bqv": "bqv-Latn-CI",
+ "br": "br-Latn-FR",
+ "bra": "bra-Deva-IN",
+ "brh": "brh-Arab-PK",
+ "brx": "brx-Deva-IN",
+ "brz": "brz-Latn-ZZ",
+ "bs": "bs-Latn-BA",
+ "bsj": "bsj-Latn-ZZ",
+ "bsq": "bsq-Bass-LR",
+ "bss": "bss-Latn-CM",
+ "bst": "bst-Ethi-ZZ",
+ "bto": "bto-Latn-PH",
+ "btt": "btt-Latn-ZZ",
+ "btv": "btv-Deva-PK",
+ "bua": "bua-Cyrl-RU",
+ "buc": "buc-Latn-YT",
+ "bud": "bud-Latn-ZZ",
+ "bug": "bug-Latn-ID",
+ "buk": "buk-Latn-ZZ",
+ "bum": "bum-Latn-CM",
+ "buo": "buo-Latn-ZZ",
+ "bus": "bus-Latn-ZZ",
+ "buu": "buu-Latn-ZZ",
+ "bvb": "bvb-Latn-GQ",
+ "bwd": "bwd-Latn-ZZ",
+ "bwr": "bwr-Latn-ZZ",
+ "bxh": "bxh-Latn-ZZ",
+ "bye": "bye-Latn-ZZ",
+ "byn": "byn-Ethi-ER",
+ "byr": "byr-Latn-ZZ",
+ "bys": "bys-Latn-ZZ",
+ "byv": "byv-Latn-CM",
+ "byx": "byx-Latn-ZZ",
+ "bza": "bza-Latn-ZZ",
+ "bze": "bze-Latn-ML",
+ "bzf": "bzf-Latn-ZZ",
+ "bzh": "bzh-Latn-ZZ",
+ "bzw": "bzw-Latn-ZZ",
+ "ca": "ca-Latn-ES",
+ "cad": "cad-Latn-US",
+ "can": "can-Latn-ZZ",
+ "cbj": "cbj-Latn-ZZ",
+ "cch": "cch-Latn-NG",
+ "ccp": "ccp-Cakm-BD",
+ "ce": "ce-Cyrl-RU",
+ "ceb": "ceb-Latn-PH",
+ "cfa": "cfa-Latn-ZZ",
+ "cgg": "cgg-Latn-UG",
+ "ch": "ch-Latn-GU",
+ "chk": "chk-Latn-FM",
+ "chm": "chm-Cyrl-RU",
+ "cho": "cho-Latn-US",
+ "chp": "chp-Latn-CA",
+ "chr": "chr-Cher-US",
+ "cic": "cic-Latn-US",
+ "cja": "cja-Arab-KH",
+ "cjm": "cjm-Cham-VN",
+ "cjv": "cjv-Latn-ZZ",
+ "ckb": "ckb-Arab-IQ",
+ "ckl": "ckl-Latn-ZZ",
+ "cko": "cko-Latn-ZZ",
+ "cky": "cky-Latn-ZZ",
+ "cla": "cla-Latn-ZZ",
+ "cme": "cme-Latn-ZZ",
+ "cmg": "cmg-Soyo-MN",
+ "co": "co-Latn-FR",
+ "cop": "cop-Copt-EG",
+ "cps": "cps-Latn-PH",
+ "cr": "cr-Cans-CA",
+ "crh": "crh-Cyrl-UA",
+ "crj": "crj-Cans-CA",
+ "crk": "crk-Cans-CA",
+ "crl": "crl-Cans-CA",
+ "crm": "crm-Cans-CA",
+ "crs": "crs-Latn-SC",
+ "cs": "cs-Latn-CZ",
+ "csb": "csb-Latn-PL",
+ "csw": "csw-Cans-CA",
+ "ctd": "ctd-Pauc-MM",
+ "cu": "cu-Cyrl-RU",
+ "cu-Glag": "cu-Glag-BG",
+ "cv": "cv-Cyrl-RU",
+ "cy": "cy-Latn-GB",
+ "da": "da-Latn-DK",
+ "dad": "dad-Latn-ZZ",
+ "daf": "daf-Latn-ZZ",
+ "dag": "dag-Latn-ZZ",
+ "dah": "dah-Latn-ZZ",
+ "dak": "dak-Latn-US",
+ "dar": "dar-Cyrl-RU",
+ "dav": "dav-Latn-KE",
+ "dbd": "dbd-Latn-ZZ",
+ "dbq": "dbq-Latn-ZZ",
+ "dcc": "dcc-Arab-IN",
+ "ddn": "ddn-Latn-ZZ",
+ "de": "de-Latn-DE",
+ "ded": "ded-Latn-ZZ",
+ "den": "den-Latn-CA",
+ "dga": "dga-Latn-ZZ",
+ "dgh": "dgh-Latn-ZZ",
+ "dgi": "dgi-Latn-ZZ",
+ "dgl": "dgl-Arab-ZZ",
+ "dgr": "dgr-Latn-CA",
+ "dgz": "dgz-Latn-ZZ",
+ "dia": "dia-Latn-ZZ",
+ "dje": "dje-Latn-NE",
+ "dnj": "dnj-Latn-CI",
+ "dob": "dob-Latn-ZZ",
+ "doi": "doi-Arab-IN",
+ "dop": "dop-Latn-ZZ",
+ "dow": "dow-Latn-ZZ",
+ "drh": "mn-Cyrl-MN",
+ "dri": "dri-Latn-ZZ",
+ "drs": "drs-Ethi-ZZ",
+ "dsb": "dsb-Latn-DE",
+ "dtm": "dtm-Latn-ML",
+ "dtp": "dtp-Latn-MY",
+ "dts": "dts-Latn-ZZ",
+ "dty": "dty-Deva-NP",
+ "dua": "dua-Latn-CM",
+ "duc": "duc-Latn-ZZ",
+ "dud": "dud-Latn-ZZ",
+ "dug": "dug-Latn-ZZ",
+ "dv": "dv-Thaa-MV",
+ "dva": "dva-Latn-ZZ",
+ "dww": "dww-Latn-ZZ",
+ "dyo": "dyo-Latn-SN",
+ "dyu": "dyu-Latn-BF",
+ "dz": "dz-Tibt-BT",
+ "dzg": "dzg-Latn-ZZ",
+ "ebu": "ebu-Latn-KE",
+ "ee": "ee-Latn-GH",
+ "efi": "efi-Latn-NG",
+ "egl": "egl-Latn-IT",
+ "egy": "egy-Egyp-EG",
+ "eka": "eka-Latn-ZZ",
+ "eky": "eky-Kali-MM",
+ "el": "el-Grek-GR",
+ "ema": "ema-Latn-ZZ",
+ "emi": "emi-Latn-ZZ",
+ "en": "en-Latn-US",
+ "en-Shaw": "en-Shaw-GB",
+ "enn": "enn-Latn-ZZ",
+ "enq": "enq-Latn-ZZ",
+ "eo": "eo-Latn-001",
+ "eri": "eri-Latn-ZZ",
+ "es": "es-Latn-ES",
+ "esg": "esg-Gonm-IN",
+ "esu": "esu-Latn-US",
+ "et": "et-Latn-EE",
+ "etr": "etr-Latn-ZZ",
+ "ett": "ett-Ital-IT",
+ "etu": "etu-Latn-ZZ",
+ "etx": "etx-Latn-ZZ",
+ "eu": "eu-Latn-ES",
+ "ewo": "ewo-Latn-CM",
+ "ext": "ext-Latn-ES",
+ "fa": "fa-Arab-IR",
+ "faa": "faa-Latn-ZZ",
+ "fab": "fab-Latn-ZZ",
+ "fag": "fag-Latn-ZZ",
+ "fai": "fai-Latn-ZZ",
+ "fan": "fan-Latn-GQ",
+ "ff": "ff-Latn-SN",
+ "ff-Adlm": "ff-Adlm-GN",
+ "ffi": "ffi-Latn-ZZ",
+ "ffm": "ffm-Latn-ML",
+ "fi": "fi-Latn-FI",
+ "fia": "fia-Arab-SD",
+ "fil": "fil-Latn-PH",
+ "fit": "fit-Latn-SE",
+ "fj": "fj-Latn-FJ",
+ "flr": "flr-Latn-ZZ",
+ "fmp": "fmp-Latn-ZZ",
+ "fo": "fo-Latn-FO",
+ "fod": "fod-Latn-ZZ",
+ "fon": "fon-Latn-BJ",
+ "for": "for-Latn-ZZ",
+ "fpe": "fpe-Latn-ZZ",
+ "fqs": "fqs-Latn-ZZ",
+ "fr": "fr-Latn-FR",
+ "frc": "frc-Latn-US",
+ "frp": "frp-Latn-FR",
+ "frr": "frr-Latn-DE",
+ "frs": "frs-Latn-DE",
+ "fub": "fub-Arab-CM",
+ "fud": "fud-Latn-WF",
+ "fue": "fue-Latn-ZZ",
+ "fuf": "fuf-Latn-GN",
+ "fuh": "fuh-Latn-ZZ",
+ "fuq": "fuq-Latn-NE",
+ "fur": "fur-Latn-IT",
+ "fuv": "fuv-Latn-NG",
+ "fuy": "fuy-Latn-ZZ",
+ "fvr": "fvr-Latn-SD",
+ "fy": "fy-Latn-NL",
+ "ga": "ga-Latn-IE",
+ "gaa": "gaa-Latn-GH",
+ "gaf": "gaf-Latn-ZZ",
+ "gag": "gag-Latn-MD",
+ "gah": "gah-Latn-ZZ",
+ "gaj": "gaj-Latn-ZZ",
+ "gam": "gam-Latn-ZZ",
+ "gan": "gan-Hans-CN",
+ "gaw": "gaw-Latn-ZZ",
+ "gay": "gay-Latn-ID",
+ "gba": "gba-Latn-ZZ",
+ "gbf": "gbf-Latn-ZZ",
+ "gbm": "gbm-Deva-IN",
+ "gby": "gby-Latn-ZZ",
+ "gbz": "gbz-Arab-IR",
+ "gcr": "gcr-Latn-GF",
+ "gd": "gd-Latn-GB",
+ "gde": "gde-Latn-ZZ",
+ "gdn": "gdn-Latn-ZZ",
+ "gdr": "gdr-Latn-ZZ",
+ "geb": "geb-Latn-ZZ",
+ "gej": "gej-Latn-ZZ",
+ "gel": "gel-Latn-ZZ",
+ "gez": "gez-Ethi-ET",
+ "gfk": "gfk-Latn-ZZ",
+ "ggn": "gvr-Deva-NP",
+ "ghs": "ghs-Latn-ZZ",
+ "gil": "gil-Latn-KI",
+ "gim": "gim-Latn-ZZ",
+ "gjk": "gjk-Arab-PK",
+ "gjn": "gjn-Latn-ZZ",
+ "gju": "gju-Arab-PK",
+ "gkn": "gkn-Latn-ZZ",
+ "gkp": "gkp-Latn-ZZ",
+ "gl": "gl-Latn-ES",
+ "glk": "glk-Arab-IR",
+ "gmm": "gmm-Latn-ZZ",
+ "gmv": "gmv-Ethi-ZZ",
+ "gn": "gn-Latn-PY",
+ "gnd": "gnd-Latn-ZZ",
+ "gng": "gng-Latn-ZZ",
+ "god": "god-Latn-ZZ",
+ "gof": "gof-Ethi-ZZ",
+ "goi": "goi-Latn-ZZ",
+ "gom": "gom-Deva-IN",
+ "gon": "gon-Telu-IN",
+ "gor": "gor-Latn-ID",
+ "gos": "gos-Latn-NL",
+ "got": "got-Goth-UA",
+ "grb": "grb-Latn-ZZ",
+ "grc": "grc-Cprt-CY",
+ "grc-Linb": "grc-Linb-GR",
+ "grt": "grt-Beng-IN",
+ "grw": "grw-Latn-ZZ",
+ "gsw": "gsw-Latn-CH",
+ "gu": "gu-Gujr-IN",
+ "gub": "gub-Latn-BR",
+ "guc": "guc-Latn-CO",
+ "gud": "gud-Latn-ZZ",
+ "gur": "gur-Latn-GH",
+ "guw": "guw-Latn-ZZ",
+ "gux": "gux-Latn-ZZ",
+ "guz": "guz-Latn-KE",
+ "gv": "gv-Latn-IM",
+ "gvf": "gvf-Latn-ZZ",
+ "gvr": "gvr-Deva-NP",
+ "gvs": "gvs-Latn-ZZ",
+ "gwc": "gwc-Arab-ZZ",
+ "gwi": "gwi-Latn-CA",
+ "gwt": "gwt-Arab-ZZ",
+ "gyi": "gyi-Latn-ZZ",
+ "ha": "ha-Latn-NG",
+ "ha-CM": "ha-Arab-CM",
+ "ha-SD": "ha-Arab-SD",
+ "hag": "hag-Latn-ZZ",
+ "hak": "hak-Hans-CN",
+ "ham": "ham-Latn-ZZ",
+ "haw": "haw-Latn-US",
+ "haz": "haz-Arab-AF",
+ "hbb": "hbb-Latn-ZZ",
+ "hdy": "hdy-Ethi-ZZ",
+ "he": "he-Hebr-IL",
+ "hhy": "hhy-Latn-ZZ",
+ "hi": "hi-Deva-IN",
+ "hi-Latn": "hi-Latn-IN",
+ "hia": "hia-Latn-ZZ",
+ "hif": "hif-Latn-FJ",
+ "hig": "hig-Latn-ZZ",
+ "hih": "hih-Latn-ZZ",
+ "hil": "hil-Latn-PH",
+ "hla": "hla-Latn-ZZ",
+ "hlu": "hlu-Hluw-TR",
+ "hmd": "hmd-Plrd-CN",
+ "hmt": "hmt-Latn-ZZ",
+ "hnd": "hnd-Arab-PK",
+ "hne": "hne-Deva-IN",
+ "hnj": "hnj-Hmng-LA",
+ "hnn": "hnn-Latn-PH",
+ "hno": "hno-Arab-PK",
+ "ho": "ho-Latn-PG",
+ "hoc": "hoc-Deva-IN",
+ "hoj": "hoj-Deva-IN",
+ "hot": "hot-Latn-ZZ",
+ "hr": "hr-Latn-HR",
+ "hsb": "hsb-Latn-DE",
+ "hsn": "hsn-Hans-CN",
+ "ht": "ht-Latn-HT",
+ "hu": "hu-Latn-HU",
+ "hui": "hui-Latn-ZZ",
+ "hy": "hy-Armn-AM",
+ "hz": "hz-Latn-NA",
+ "ia": "ia-Latn-001",
+ "ian": "ian-Latn-ZZ",
+ "iar": "iar-Latn-ZZ",
+ "iba": "iba-Latn-MY",
+ "ibb": "ibb-Latn-NG",
+ "iby": "iby-Latn-ZZ",
+ "ica": "ica-Latn-ZZ",
+ "ich": "ich-Latn-ZZ",
+ "id": "id-Latn-ID",
+ "idd": "idd-Latn-ZZ",
+ "idi": "idi-Latn-ZZ",
+ "idu": "idu-Latn-ZZ",
+ "ife": "ife-Latn-TG",
+ "ig": "ig-Latn-NG",
+ "igb": "igb-Latn-ZZ",
+ "ige": "ige-Latn-ZZ",
+ "ii": "ii-Yiii-CN",
+ "ijj": "ijj-Latn-ZZ",
+ "ik": "ik-Latn-US",
+ "ikk": "ikk-Latn-ZZ",
+ "ikt": "ikt-Latn-CA",
+ "ikw": "ikw-Latn-ZZ",
+ "ikx": "ikx-Latn-ZZ",
+ "ilo": "ilo-Latn-PH",
+ "imo": "imo-Latn-ZZ",
+ "in": "id-Latn-ID",
+ "inh": "inh-Cyrl-RU",
+ "io": "io-Latn-001",
+ "iou": "iou-Latn-ZZ",
+ "iri": "iri-Latn-ZZ",
+ "is": "is-Latn-IS",
+ "it": "it-Latn-IT",
+ "iu": "iu-Cans-CA",
+ "iw": "he-Hebr-IL",
+ "iwm": "iwm-Latn-ZZ",
+ "iws": "iws-Latn-ZZ",
+ "izh": "izh-Latn-RU",
+ "izi": "izi-Latn-ZZ",
+ "ja": "ja-Jpan-JP",
+ "jab": "jab-Latn-ZZ",
+ "jam": "jam-Latn-JM",
+ "jbo": "jbo-Latn-001",
+ "jbu": "jbu-Latn-ZZ",
+ "jen": "jen-Latn-ZZ",
+ "jgk": "jgk-Latn-ZZ",
+ "jgo": "jgo-Latn-CM",
+ "ji": "yi-Hebr-001",
+ "jib": "jib-Latn-ZZ",
+ "jmc": "jmc-Latn-TZ",
+ "jml": "jml-Deva-NP",
+ "jra": "jra-Latn-ZZ",
+ "jut": "jut-Latn-DK",
+ "jv": "jv-Latn-ID",
+ "jw": "jv-Latn-ID",
+ "ka": "ka-Geor-GE",
+ "kaa": "kaa-Cyrl-UZ",
+ "kab": "kab-Latn-DZ",
+ "kac": "kac-Latn-MM",
+ "kad": "kad-Latn-ZZ",
+ "kai": "kai-Latn-ZZ",
+ "kaj": "kaj-Latn-NG",
+ "kam": "kam-Latn-KE",
+ "kao": "kao-Latn-ML",
+ "kbd": "kbd-Cyrl-RU",
+ "kbm": "kbm-Latn-ZZ",
+ "kbp": "kbp-Latn-ZZ",
+ "kbq": "kbq-Latn-ZZ",
+ "kbx": "kbx-Latn-ZZ",
+ "kby": "kby-Arab-NE",
+ "kcg": "kcg-Latn-NG",
+ "kck": "kck-Latn-ZW",
+ "kcl": "kcl-Latn-ZZ",
+ "kct": "kct-Latn-ZZ",
+ "kde": "kde-Latn-TZ",
+ "kdh": "kdh-Arab-TG",
+ "kdl": "kdl-Latn-ZZ",
+ "kdt": "kdt-Thai-TH",
+ "kea": "kea-Latn-CV",
+ "ken": "ken-Latn-CM",
+ "kez": "kez-Latn-ZZ",
+ "kfo": "kfo-Latn-CI",
+ "kfr": "kfr-Deva-IN",
+ "kfy": "kfy-Deva-IN",
+ "kg": "kg-Latn-CD",
+ "kge": "kge-Latn-ID",
+ "kgf": "kgf-Latn-ZZ",
+ "kgp": "kgp-Latn-BR",
+ "kha": "kha-Latn-IN",
+ "khb": "khb-Talu-CN",
+ "khn": "khn-Deva-IN",
+ "khq": "khq-Latn-ML",
+ "khs": "khs-Latn-ZZ",
+ "kht": "kht-Mymr-IN",
+ "khw": "khw-Arab-PK",
+ "khz": "khz-Latn-ZZ",
+ "ki": "ki-Latn-KE",
+ "kij": "kij-Latn-ZZ",
+ "kiu": "kiu-Latn-TR",
+ "kiw": "kiw-Latn-ZZ",
+ "kj": "kj-Latn-NA",
+ "kjd": "kjd-Latn-ZZ",
+ "kjg": "kjg-Laoo-LA",
+ "kjs": "kjs-Latn-ZZ",
+ "kjy": "kjy-Latn-ZZ",
+ "kk": "kk-Cyrl-KZ",
+ "kk-AF": "kk-Arab-AF",
+ "kk-Arab": "kk-Arab-CN",
+ "kk-CN": "kk-Arab-CN",
+ "kk-IR": "kk-Arab-IR",
+ "kk-MN": "kk-Arab-MN",
+ "kkc": "kkc-Latn-ZZ",
+ "kkj": "kkj-Latn-CM",
+ "kl": "kl-Latn-GL",
+ "kln": "kln-Latn-KE",
+ "klq": "klq-Latn-ZZ",
+ "klt": "klt-Latn-ZZ",
+ "klx": "klx-Latn-ZZ",
+ "km": "km-Khmr-KH",
+ "kmb": "kmb-Latn-AO",
+ "kmh": "kmh-Latn-ZZ",
+ "kmo": "kmo-Latn-ZZ",
+ "kms": "kms-Latn-ZZ",
+ "kmu": "kmu-Latn-ZZ",
+ "kmw": "kmw-Latn-ZZ",
+ "kn": "kn-Knda-IN",
+ "knf": "knf-Latn-GW",
+ "knp": "knp-Latn-ZZ",
+ "ko": "ko-Kore-KR",
+ "koi": "koi-Cyrl-RU",
+ "kok": "kok-Deva-IN",
+ "kol": "kol-Latn-ZZ",
+ "kos": "kos-Latn-FM",
+ "koz": "koz-Latn-ZZ",
+ "kpe": "kpe-Latn-LR",
+ "kpf": "kpf-Latn-ZZ",
+ "kpo": "kpo-Latn-ZZ",
+ "kpr": "kpr-Latn-ZZ",
+ "kpx": "kpx-Latn-ZZ",
+ "kqb": "kqb-Latn-ZZ",
+ "kqf": "kqf-Latn-ZZ",
+ "kqs": "kqs-Latn-ZZ",
+ "kqy": "kqy-Ethi-ZZ",
+ "kr": "kr-Latn-ZZ",
+ "krc": "krc-Cyrl-RU",
+ "kri": "kri-Latn-SL",
+ "krj": "krj-Latn-PH",
+ "krl": "krl-Latn-RU",
+ "krs": "krs-Latn-ZZ",
+ "kru": "kru-Deva-IN",
+ "ks": "ks-Arab-IN",
+ "ks-Deva": "ks-Deva-IN",
+ "ksb": "ksb-Latn-TZ",
+ "ksd": "ksd-Latn-ZZ",
+ "ksf": "ksf-Latn-CM",
+ "ksh": "ksh-Latn-DE",
+ "ksj": "ksj-Latn-ZZ",
+ "ksr": "ksr-Latn-ZZ",
+ "ktb": "ktb-Ethi-ZZ",
+ "ktm": "ktm-Latn-ZZ",
+ "kto": "kto-Latn-ZZ",
+ "ktr": "dtp-Latn-MY",
+ "ku": "ku-Latn-TR",
+ "ku-Arab": "ku-Arab-IQ",
+ "ku-LB": "ku-Arab-LB",
+ "ku-Yezi": "ku-Yezi-GE",
+ "kub": "kub-Latn-ZZ",
+ "kud": "kud-Latn-ZZ",
+ "kue": "kue-Latn-ZZ",
+ "kuj": "kuj-Latn-ZZ",
+ "kum": "kum-Cyrl-RU",
+ "kun": "kun-Latn-ZZ",
+ "kup": "kup-Latn-ZZ",
+ "kus": "kus-Latn-ZZ",
+ "kv": "kv-Cyrl-RU",
+ "kvg": "kvg-Latn-ZZ",
+ "kvr": "kvr-Latn-ID",
+ "kvx": "kvx-Arab-PK",
+ "kw": "kw-Latn-GB",
+ "kwj": "kwj-Latn-ZZ",
+ "kwo": "kwo-Latn-ZZ",
+ "kwq": "yam-Latn-ZZ",
+ "kxa": "kxa-Latn-ZZ",
+ "kxc": "kxc-Ethi-ZZ",
+ "kxe": "tvd-Latn-ZZ",
+ "kxm": "kxm-Thai-TH",
+ "kxp": "kxp-Arab-PK",
+ "kxw": "kxw-Latn-ZZ",
+ "kxz": "kxz-Latn-ZZ",
+ "ky": "ky-Cyrl-KG",
+ "ky-Arab": "ky-Arab-CN",
+ "ky-CN": "ky-Arab-CN",
+ "ky-Latn": "ky-Latn-TR",
+ "ky-TR": "ky-Latn-TR",
+ "kye": "kye-Latn-ZZ",
+ "kyx": "kyx-Latn-ZZ",
+ "kzj": "dtp-Latn-MY",
+ "kzr": "kzr-Latn-ZZ",
+ "kzt": "dtp-Latn-MY",
+ "la": "la-Latn-VA",
+ "lab": "lab-Lina-GR",
+ "lad": "lad-Hebr-IL",
+ "lag": "lag-Latn-TZ",
+ "lah": "lah-Arab-PK",
+ "laj": "laj-Latn-UG",
+ "las": "las-Latn-ZZ",
+ "lb": "lb-Latn-LU",
+ "lbe": "lbe-Cyrl-RU",
+ "lbu": "lbu-Latn-ZZ",
+ "lbw": "lbw-Latn-ID",
+ "lcm": "lcm-Latn-ZZ",
+ "lcp": "lcp-Thai-CN",
+ "ldb": "ldb-Latn-ZZ",
+ "led": "led-Latn-ZZ",
+ "lee": "lee-Latn-ZZ",
+ "lem": "lem-Latn-ZZ",
+ "lep": "lep-Lepc-IN",
+ "leq": "leq-Latn-ZZ",
+ "leu": "leu-Latn-ZZ",
+ "lez": "lez-Cyrl-RU",
+ "lg": "lg-Latn-UG",
+ "lgg": "lgg-Latn-ZZ",
+ "li": "li-Latn-NL",
+ "lia": "lia-Latn-ZZ",
+ "lid": "lid-Latn-ZZ",
+ "lif": "lif-Deva-NP",
+ "lif-Limb": "lif-Limb-IN",
+ "lig": "lig-Latn-ZZ",
+ "lih": "lih-Latn-ZZ",
+ "lij": "lij-Latn-IT",
+ "lis": "lis-Lisu-CN",
+ "ljp": "ljp-Latn-ID",
+ "lki": "lki-Arab-IR",
+ "lkt": "lkt-Latn-US",
+ "lle": "lle-Latn-ZZ",
+ "lln": "lln-Latn-ZZ",
+ "lmn": "lmn-Telu-IN",
+ "lmo": "lmo-Latn-IT",
+ "lmp": "lmp-Latn-ZZ",
+ "ln": "ln-Latn-CD",
+ "lns": "lns-Latn-ZZ",
+ "lnu": "lnu-Latn-ZZ",
+ "lo": "lo-Laoo-LA",
+ "loj": "loj-Latn-ZZ",
+ "lok": "lok-Latn-ZZ",
+ "lol": "lol-Latn-CD",
+ "lor": "lor-Latn-ZZ",
+ "los": "los-Latn-ZZ",
+ "loz": "loz-Latn-ZM",
+ "lrc": "lrc-Arab-IR",
+ "lt": "lt-Latn-LT",
+ "ltg": "ltg-Latn-LV",
+ "lu": "lu-Latn-CD",
+ "lua": "lua-Latn-CD",
+ "luo": "luo-Latn-KE",
+ "luy": "luy-Latn-KE",
+ "luz": "luz-Arab-IR",
+ "lv": "lv-Latn-LV",
+ "lwl": "lwl-Thai-TH",
+ "lzh": "lzh-Hans-CN",
+ "lzz": "lzz-Latn-TR",
+ "mad": "mad-Latn-ID",
+ "maf": "maf-Latn-CM",
+ "mag": "mag-Deva-IN",
+ "mai": "mai-Deva-IN",
+ "mak": "mak-Latn-ID",
+ "man": "man-Latn-GM",
+ "man-GN": "man-Nkoo-GN",
+ "man-Nkoo": "man-Nkoo-GN",
+ "mas": "mas-Latn-KE",
+ "maw": "maw-Latn-ZZ",
+ "maz": "maz-Latn-MX",
+ "mbh": "mbh-Latn-ZZ",
+ "mbo": "mbo-Latn-ZZ",
+ "mbq": "mbq-Latn-ZZ",
+ "mbu": "mbu-Latn-ZZ",
+ "mbw": "mbw-Latn-ZZ",
+ "mci": "mci-Latn-ZZ",
+ "mcp": "mcp-Latn-ZZ",
+ "mcq": "mcq-Latn-ZZ",
+ "mcr": "mcr-Latn-ZZ",
+ "mcu": "mcu-Latn-ZZ",
+ "mda": "mda-Latn-ZZ",
+ "mde": "mde-Arab-ZZ",
+ "mdf": "mdf-Cyrl-RU",
+ "mdh": "mdh-Latn-PH",
+ "mdj": "mdj-Latn-ZZ",
+ "mdr": "mdr-Latn-ID",
+ "mdx": "mdx-Ethi-ZZ",
+ "med": "med-Latn-ZZ",
+ "mee": "mee-Latn-ZZ",
+ "mek": "mek-Latn-ZZ",
+ "men": "men-Latn-SL",
+ "mer": "mer-Latn-KE",
+ "met": "met-Latn-ZZ",
+ "meu": "meu-Latn-ZZ",
+ "mfa": "mfa-Arab-TH",
+ "mfe": "mfe-Latn-MU",
+ "mfn": "mfn-Latn-ZZ",
+ "mfo": "mfo-Latn-ZZ",
+ "mfq": "mfq-Latn-ZZ",
+ "mg": "mg-Latn-MG",
+ "mgh": "mgh-Latn-MZ",
+ "mgl": "mgl-Latn-ZZ",
+ "mgo": "mgo-Latn-CM",
+ "mgp": "mgp-Deva-NP",
+ "mgy": "mgy-Latn-TZ",
+ "mh": "mh-Latn-MH",
+ "mhi": "mhi-Latn-ZZ",
+ "mhl": "mhl-Latn-ZZ",
+ "mi": "mi-Latn-NZ",
+ "mif": "mif-Latn-ZZ",
+ "min": "min-Latn-ID",
+ "mis": "mis-Hatr-IQ",
+ "mis-Medf": "mis-Medf-NG",
+ "miw": "miw-Latn-ZZ",
+ "mk": "mk-Cyrl-MK",
+ "mki": "mki-Arab-ZZ",
+ "mkl": "mkl-Latn-ZZ",
+ "mkp": "mkp-Latn-ZZ",
+ "mkw": "mkw-Latn-ZZ",
+ "ml": "ml-Mlym-IN",
+ "mle": "mle-Latn-ZZ",
+ "mlp": "mlp-Latn-ZZ",
+ "mls": "mls-Latn-SD",
+ "mmo": "mmo-Latn-ZZ",
+ "mmu": "mmu-Latn-ZZ",
+ "mmx": "mmx-Latn-ZZ",
+ "mn": "mn-Cyrl-MN",
+ "mn-CN": "mn-Mong-CN",
+ "mn-Mong": "mn-Mong-CN",
+ "mna": "mna-Latn-ZZ",
+ "mnf": "mnf-Latn-ZZ",
+ "mni": "mni-Beng-IN",
+ "mnw": "mnw-Mymr-MM",
+ "mo": "ro-Latn-RO",
+ "moa": "moa-Latn-ZZ",
+ "moe": "moe-Latn-CA",
+ "moh": "moh-Latn-CA",
+ "mos": "mos-Latn-BF",
+ "mox": "mox-Latn-ZZ",
+ "mpp": "mpp-Latn-ZZ",
+ "mps": "mps-Latn-ZZ",
+ "mpt": "mpt-Latn-ZZ",
+ "mpx": "mpx-Latn-ZZ",
+ "mql": "mql-Latn-ZZ",
+ "mr": "mr-Deva-IN",
+ "mrd": "mrd-Deva-NP",
+ "mrj": "mrj-Cyrl-RU",
+ "mro": "mro-Mroo-BD",
+ "ms": "ms-Latn-MY",
+ "ms-CC": "ms-Arab-CC",
+ "ms-ID": "ms-Latn-ID",
+ "mt": "mt-Latn-MT",
+ "mtc": "mtc-Latn-ZZ",
+ "mtf": "mtf-Latn-ZZ",
+ "mti": "mti-Latn-ZZ",
+ "mtr": "mtr-Deva-IN",
+ "mua": "mua-Latn-CM",
+ "mur": "mur-Latn-ZZ",
+ "mus": "mus-Latn-US",
+ "mva": "mva-Latn-ZZ",
+ "mvn": "mvn-Latn-ZZ",
+ "mvy": "mvy-Arab-PK",
+ "mwk": "mwk-Latn-ML",
+ "mwr": "mwr-Deva-IN",
+ "mwv": "mwv-Latn-ID",
+ "mww": "mww-Hmnp-US",
+ "mxc": "mxc-Latn-ZW",
+ "mxm": "mxm-Latn-ZZ",
+ "my": "my-Mymr-MM",
+ "myk": "myk-Latn-ZZ",
+ "mym": "mym-Ethi-ZZ",
+ "myv": "myv-Cyrl-RU",
+ "myw": "myw-Latn-ZZ",
+ "myx": "myx-Latn-UG",
+ "myz": "myz-Mand-IR",
+ "mzk": "mzk-Latn-ZZ",
+ "mzm": "mzm-Latn-ZZ",
+ "mzn": "mzn-Arab-IR",
+ "mzp": "mzp-Latn-ZZ",
+ "mzw": "mzw-Latn-ZZ",
+ "mzz": "mzz-Latn-ZZ",
+ "na": "na-Latn-NR",
+ "nac": "nac-Latn-ZZ",
+ "naf": "naf-Latn-ZZ",
+ "nak": "nak-Latn-ZZ",
+ "nan": "nan-Hans-CN",
+ "nap": "nap-Latn-IT",
+ "naq": "naq-Latn-NA",
+ "nas": "nas-Latn-ZZ",
+ "nb": "nb-Latn-NO",
+ "nca": "nca-Latn-ZZ",
+ "nce": "nce-Latn-ZZ",
+ "ncf": "ncf-Latn-ZZ",
+ "nch": "nch-Latn-MX",
+ "nco": "nco-Latn-ZZ",
+ "ncu": "ncu-Latn-ZZ",
+ "nd": "nd-Latn-ZW",
+ "ndc": "ndc-Latn-MZ",
+ "nds": "nds-Latn-DE",
+ "ne": "ne-Deva-NP",
+ "neb": "neb-Latn-ZZ",
+ "new": "new-Deva-NP",
+ "nex": "nex-Latn-ZZ",
+ "nfr": "nfr-Latn-ZZ",
+ "ng": "ng-Latn-NA",
+ "nga": "nga-Latn-ZZ",
+ "ngb": "ngb-Latn-ZZ",
+ "ngl": "ngl-Latn-MZ",
+ "nhb": "nhb-Latn-ZZ",
+ "nhe": "nhe-Latn-MX",
+ "nhw": "nhw-Latn-MX",
+ "nif": "nif-Latn-ZZ",
+ "nii": "nii-Latn-ZZ",
+ "nij": "nij-Latn-ID",
+ "nin": "nin-Latn-ZZ",
+ "niu": "niu-Latn-NU",
+ "niy": "niy-Latn-ZZ",
+ "niz": "niz-Latn-ZZ",
+ "njo": "njo-Latn-IN",
+ "nkg": "nkg-Latn-ZZ",
+ "nko": "nko-Latn-ZZ",
+ "nl": "nl-Latn-NL",
+ "nmg": "nmg-Latn-CM",
+ "nmz": "nmz-Latn-ZZ",
+ "nn": "nn-Latn-NO",
+ "nnf": "nnf-Latn-ZZ",
+ "nnh": "nnh-Latn-CM",
+ "nnk": "nnk-Latn-ZZ",
+ "nnm": "nnm-Latn-ZZ",
+ "nnp": "nnp-Wcho-IN",
+ "no": "nb-Latn-NO",
+ "nod": "nod-Lana-TH",
+ "noe": "noe-Deva-IN",
+ "non": "non-Runr-SE",
+ "nop": "nop-Latn-ZZ",
+ "nou": "nou-Latn-ZZ",
+ "nqo": "nqo-Nkoo-GN",
+ "nr": "nr-Latn-ZA",
+ "nrb": "nrb-Latn-ZZ",
+ "nsk": "nsk-Cans-CA",
+ "nsn": "nsn-Latn-ZZ",
+ "nso": "nso-Latn-ZA",
+ "nss": "nss-Latn-ZZ",
+ "ntm": "ntm-Latn-ZZ",
+ "ntr": "ntr-Latn-ZZ",
+ "nui": "nui-Latn-ZZ",
+ "nup": "nup-Latn-ZZ",
+ "nus": "nus-Latn-SS",
+ "nuv": "nuv-Latn-ZZ",
+ "nux": "nux-Latn-ZZ",
+ "nv": "nv-Latn-US",
+ "nwb": "nwb-Latn-ZZ",
+ "nxq": "nxq-Latn-CN",
+ "nxr": "nxr-Latn-ZZ",
+ "ny": "ny-Latn-MW",
+ "nym": "nym-Latn-TZ",
+ "nyn": "nyn-Latn-UG",
+ "nzi": "nzi-Latn-GH",
+ "oc": "oc-Latn-FR",
+ "ogc": "ogc-Latn-ZZ",
+ "okr": "okr-Latn-ZZ",
+ "okv": "okv-Latn-ZZ",
+ "om": "om-Latn-ET",
+ "ong": "ong-Latn-ZZ",
+ "onn": "onn-Latn-ZZ",
+ "ons": "ons-Latn-ZZ",
+ "opm": "opm-Latn-ZZ",
+ "or": "or-Orya-IN",
+ "oro": "oro-Latn-ZZ",
+ "oru": "oru-Arab-ZZ",
+ "os": "os-Cyrl-GE",
+ "osa": "osa-Osge-US",
+ "ota": "ota-Arab-ZZ",
+ "otk": "otk-Orkh-MN",
+ "ozm": "ozm-Latn-ZZ",
+ "pa": "pa-Guru-IN",
+ "pa-Arab": "pa-Arab-PK",
+ "pa-PK": "pa-Arab-PK",
+ "pag": "pag-Latn-PH",
+ "pal": "pal-Phli-IR",
+ "pal-Phlp": "pal-Phlp-CN",
+ "pam": "pam-Latn-PH",
+ "pap": "pap-Latn-AW",
+ "pau": "pau-Latn-PW",
+ "pbi": "pbi-Latn-ZZ",
+ "pcd": "pcd-Latn-FR",
+ "pcm": "pcm-Latn-NG",
+ "pdc": "pdc-Latn-US",
+ "pdt": "pdt-Latn-CA",
+ "ped": "ped-Latn-ZZ",
+ "peo": "peo-Xpeo-IR",
+ "pex": "pex-Latn-ZZ",
+ "pfl": "pfl-Latn-DE",
+ "phl": "phl-Arab-ZZ",
+ "phn": "phn-Phnx-LB",
+ "pil": "pil-Latn-ZZ",
+ "pip": "pip-Latn-ZZ",
+ "pka": "pka-Brah-IN",
+ "pko": "pko-Latn-KE",
+ "pl": "pl-Latn-PL",
+ "pla": "pla-Latn-ZZ",
+ "pms": "pms-Latn-IT",
+ "png": "png-Latn-ZZ",
+ "pnn": "pnn-Latn-ZZ",
+ "pnt": "pnt-Grek-GR",
+ "pon": "pon-Latn-FM",
+ "ppa": "bfy-Deva-IN",
+ "ppo": "ppo-Latn-ZZ",
+ "pra": "pra-Khar-PK",
+ "prd": "prd-Arab-IR",
+ "prg": "prg-Latn-001",
+ "ps": "ps-Arab-AF",
+ "pss": "pss-Latn-ZZ",
+ "pt": "pt-Latn-BR",
+ "ptp": "ptp-Latn-ZZ",
+ "puu": "puu-Latn-GA",
+ "pwa": "pwa-Latn-ZZ",
+ "qu": "qu-Latn-PE",
+ "quc": "quc-Latn-GT",
+ "qug": "qug-Latn-EC",
+ "rai": "rai-Latn-ZZ",
+ "raj": "raj-Deva-IN",
+ "rao": "rao-Latn-ZZ",
+ "rcf": "rcf-Latn-RE",
+ "rej": "rej-Latn-ID",
+ "rel": "rel-Latn-ZZ",
+ "res": "res-Latn-ZZ",
+ "rgn": "rgn-Latn-IT",
+ "rhg": "rhg-Arab-MM",
+ "ria": "ria-Latn-IN",
+ "rif": "rif-Tfng-MA",
+ "rif-NL": "rif-Latn-NL",
+ "rjs": "rjs-Deva-NP",
+ "rkt": "rkt-Beng-BD",
+ "rm": "rm-Latn-CH",
+ "rmf": "rmf-Latn-FI",
+ "rmo": "rmo-Latn-CH",
+ "rmt": "rmt-Arab-IR",
+ "rmu": "rmu-Latn-SE",
+ "rn": "rn-Latn-BI",
+ "rna": "rna-Latn-ZZ",
+ "rng": "rng-Latn-MZ",
+ "ro": "ro-Latn-RO",
+ "rob": "rob-Latn-ID",
+ "rof": "rof-Latn-TZ",
+ "roo": "roo-Latn-ZZ",
+ "rro": "rro-Latn-ZZ",
+ "rtm": "rtm-Latn-FJ",
+ "ru": "ru-Cyrl-RU",
+ "rue": "rue-Cyrl-UA",
+ "rug": "rug-Latn-SB",
+ "rw": "rw-Latn-RW",
+ "rwk": "rwk-Latn-TZ",
+ "rwo": "rwo-Latn-ZZ",
+ "ryu": "ryu-Kana-JP",
+ "sa": "sa-Deva-IN",
+ "saf": "saf-Latn-GH",
+ "sah": "sah-Cyrl-RU",
+ "saq": "saq-Latn-KE",
+ "sas": "sas-Latn-ID",
+ "sat": "sat-Olck-IN",
+ "sav": "sav-Latn-SN",
+ "saz": "saz-Saur-IN",
+ "sba": "sba-Latn-ZZ",
+ "sbe": "sbe-Latn-ZZ",
+ "sbp": "sbp-Latn-TZ",
+ "sc": "sc-Latn-IT",
+ "sck": "sck-Deva-IN",
+ "scl": "scl-Arab-ZZ",
+ "scn": "scn-Latn-IT",
+ "sco": "sco-Latn-GB",
+ "scs": "scs-Latn-CA",
+ "sd": "sd-Arab-PK",
+ "sd-Deva": "sd-Deva-IN",
+ "sd-Khoj": "sd-Khoj-IN",
+ "sd-Sind": "sd-Sind-IN",
+ "sdc": "sdc-Latn-IT",
+ "sdh": "sdh-Arab-IR",
+ "se": "se-Latn-NO",
+ "sef": "sef-Latn-CI",
+ "seh": "seh-Latn-MZ",
+ "sei": "sei-Latn-MX",
+ "ses": "ses-Latn-ML",
+ "sg": "sg-Latn-CF",
+ "sga": "sga-Ogam-IE",
+ "sgs": "sgs-Latn-LT",
+ "sgw": "sgw-Ethi-ZZ",
+ "sgz": "sgz-Latn-ZZ",
+ "shi": "shi-Tfng-MA",
+ "shk": "shk-Latn-ZZ",
+ "shn": "shn-Mymr-MM",
+ "shu": "shu-Arab-ZZ",
+ "si": "si-Sinh-LK",
+ "sid": "sid-Latn-ET",
+ "sig": "sig-Latn-ZZ",
+ "sil": "sil-Latn-ZZ",
+ "sim": "sim-Latn-ZZ",
+ "sjr": "sjr-Latn-ZZ",
+ "sk": "sk-Latn-SK",
+ "skc": "skc-Latn-ZZ",
+ "skr": "skr-Arab-PK",
+ "sks": "sks-Latn-ZZ",
+ "sl": "sl-Latn-SI",
+ "sld": "sld-Latn-ZZ",
+ "sli": "sli-Latn-PL",
+ "sll": "sll-Latn-ZZ",
+ "sly": "sly-Latn-ID",
+ "sm": "sm-Latn-WS",
+ "sma": "sma-Latn-SE",
+ "smj": "smj-Latn-SE",
+ "smn": "smn-Latn-FI",
+ "smp": "smp-Samr-IL",
+ "smq": "smq-Latn-ZZ",
+ "sms": "sms-Latn-FI",
+ "sn": "sn-Latn-ZW",
+ "snc": "snc-Latn-ZZ",
+ "snk": "snk-Latn-ML",
+ "snp": "snp-Latn-ZZ",
+ "snx": "snx-Latn-ZZ",
+ "sny": "sny-Latn-ZZ",
+ "so": "so-Latn-SO",
+ "sog": "sog-Sogd-UZ",
+ "sok": "sok-Latn-ZZ",
+ "soq": "soq-Latn-ZZ",
+ "sou": "sou-Thai-TH",
+ "soy": "soy-Latn-ZZ",
+ "spd": "spd-Latn-ZZ",
+ "spl": "spl-Latn-ZZ",
+ "sps": "sps-Latn-ZZ",
+ "sq": "sq-Latn-AL",
+ "sr": "sr-Cyrl-RS",
+ "sr-ME": "sr-Latn-ME",
+ "sr-RO": "sr-Latn-RO",
+ "sr-RU": "sr-Latn-RU",
+ "sr-TR": "sr-Latn-TR",
+ "srb": "srb-Sora-IN",
+ "srn": "srn-Latn-SR",
+ "srr": "srr-Latn-SN",
+ "srx": "srx-Deva-IN",
+ "ss": "ss-Latn-ZA",
+ "ssd": "ssd-Latn-ZZ",
+ "ssg": "ssg-Latn-ZZ",
+ "ssy": "ssy-Latn-ER",
+ "st": "st-Latn-ZA",
+ "stk": "stk-Latn-ZZ",
+ "stq": "stq-Latn-DE",
+ "su": "su-Latn-ID",
+ "sua": "sua-Latn-ZZ",
+ "sue": "sue-Latn-ZZ",
+ "suk": "suk-Latn-TZ",
+ "sur": "sur-Latn-ZZ",
+ "sus": "sus-Latn-GN",
+ "sv": "sv-Latn-SE",
+ "sw": "sw-Latn-TZ",
+ "swb": "swb-Arab-YT",
+ "swc": "sw-Latn-CD",
+ "swg": "swg-Latn-DE",
+ "swp": "swp-Latn-ZZ",
+ "swv": "swv-Deva-IN",
+ "sxn": "sxn-Latn-ID",
+ "sxw": "sxw-Latn-ZZ",
+ "syl": "syl-Beng-BD",
+ "syr": "syr-Syrc-IQ",
+ "szl": "szl-Latn-PL",
+ "ta": "ta-Taml-IN",
+ "taj": "taj-Deva-NP",
+ "tal": "tal-Latn-ZZ",
+ "tan": "tan-Latn-ZZ",
+ "taq": "taq-Latn-ZZ",
+ "tbc": "tbc-Latn-ZZ",
+ "tbd": "tbd-Latn-ZZ",
+ "tbf": "tbf-Latn-ZZ",
+ "tbg": "tbg-Latn-ZZ",
+ "tbo": "tbo-Latn-ZZ",
+ "tbw": "tbw-Latn-PH",
+ "tbz": "tbz-Latn-ZZ",
+ "tci": "tci-Latn-ZZ",
+ "tcy": "tcy-Knda-IN",
+ "tdd": "tdd-Tale-CN",
+ "tdg": "tdg-Deva-NP",
+ "tdh": "tdh-Deva-NP",
+ "tdu": "dtp-Latn-MY",
+ "te": "te-Telu-IN",
+ "ted": "ted-Latn-ZZ",
+ "tem": "tem-Latn-SL",
+ "teo": "teo-Latn-UG",
+ "tet": "tet-Latn-TL",
+ "tfi": "tfi-Latn-ZZ",
+ "tg": "tg-Cyrl-TJ",
+ "tg-Arab": "tg-Arab-PK",
+ "tg-PK": "tg-Arab-PK",
+ "tgc": "tgc-Latn-ZZ",
+ "tgo": "tgo-Latn-ZZ",
+ "tgu": "tgu-Latn-ZZ",
+ "th": "th-Thai-TH",
+ "thl": "thl-Deva-NP",
+ "thq": "thq-Deva-NP",
+ "thr": "thr-Deva-NP",
+ "ti": "ti-Ethi-ET",
+ "tif": "tif-Latn-ZZ",
+ "tig": "tig-Ethi-ER",
+ "tik": "tik-Latn-ZZ",
+ "tim": "tim-Latn-ZZ",
+ "tio": "tio-Latn-ZZ",
+ "tiv": "tiv-Latn-NG",
+ "tk": "tk-Latn-TM",
+ "tkl": "tkl-Latn-TK",
+ "tkr": "tkr-Latn-AZ",
+ "tkt": "tkt-Deva-NP",
+ "tl": "fil-Latn-PH",
+ "tlf": "tlf-Latn-ZZ",
+ "tlx": "tlx-Latn-ZZ",
+ "tly": "tly-Latn-AZ",
+ "tmh": "tmh-Latn-NE",
+ "tmy": "tmy-Latn-ZZ",
+ "tn": "tn-Latn-ZA",
+ "tnh": "tnh-Latn-ZZ",
+ "to": "to-Latn-TO",
+ "tof": "tof-Latn-ZZ",
+ "tog": "tog-Latn-MW",
+ "toq": "toq-Latn-ZZ",
+ "tpi": "tpi-Latn-PG",
+ "tpm": "tpm-Latn-ZZ",
+ "tpz": "tpz-Latn-ZZ",
+ "tqo": "tqo-Latn-ZZ",
+ "tr": "tr-Latn-TR",
+ "tru": "tru-Latn-TR",
+ "trv": "trv-Latn-TW",
+ "trw": "trw-Arab-ZZ",
+ "ts": "ts-Latn-ZA",
+ "tsd": "tsd-Grek-GR",
+ "tsf": "taj-Deva-NP",
+ "tsg": "tsg-Latn-PH",
+ "tsj": "tsj-Tibt-BT",
+ "tsw": "tsw-Latn-ZZ",
+ "tt": "tt-Cyrl-RU",
+ "ttd": "ttd-Latn-ZZ",
+ "tte": "tte-Latn-ZZ",
+ "ttj": "ttj-Latn-UG",
+ "ttr": "ttr-Latn-ZZ",
+ "tts": "tts-Thai-TH",
+ "ttt": "ttt-Latn-AZ",
+ "tuh": "tuh-Latn-ZZ",
+ "tul": "tul-Latn-ZZ",
+ "tum": "tum-Latn-MW",
+ "tuq": "tuq-Latn-ZZ",
+ "tvd": "tvd-Latn-ZZ",
+ "tvl": "tvl-Latn-TV",
+ "tvu": "tvu-Latn-ZZ",
+ "twh": "twh-Latn-ZZ",
+ "twq": "twq-Latn-NE",
+ "txg": "txg-Tang-CN",
+ "ty": "ty-Latn-PF",
+ "tya": "tya-Latn-ZZ",
+ "tyv": "tyv-Cyrl-RU",
+ "tzm": "tzm-Latn-MA",
+ "ubu": "ubu-Latn-ZZ",
+ "udm": "udm-Cyrl-RU",
+ "ug": "ug-Arab-CN",
+ "ug-Cyrl": "ug-Cyrl-KZ",
+ "ug-KZ": "ug-Cyrl-KZ",
+ "ug-MN": "ug-Cyrl-MN",
+ "uga": "uga-Ugar-SY",
+ "uk": "uk-Cyrl-UA",
+ "uli": "uli-Latn-FM",
+ "umb": "umb-Latn-AO",
+ "und": "en-Latn-US",
+ "und-002": "en-Latn-NG",
+ "und-003": "en-Latn-US",
+ "und-005": "pt-Latn-BR",
+ "und-009": "en-Latn-AU",
+ "und-011": "en-Latn-NG",
+ "und-013": "es-Latn-MX",
+ "und-014": "sw-Latn-TZ",
+ "und-015": "ar-Arab-EG",
+ "und-017": "sw-Latn-CD",
+ "und-018": "en-Latn-ZA",
+ "und-019": "en-Latn-US",
+ "und-021": "en-Latn-US",
+ "und-029": "es-Latn-CU",
+ "und-030": "zh-Hans-CN",
+ "und-034": "hi-Deva-IN",
+ "und-035": "id-Latn-ID",
+ "und-039": "it-Latn-IT",
+ "und-053": "en-Latn-AU",
+ "und-054": "en-Latn-PG",
+ "und-057": "en-Latn-GU",
+ "und-061": "sm-Latn-WS",
+ "und-142": "zh-Hans-CN",
+ "und-143": "uz-Latn-UZ",
+ "und-145": "ar-Arab-SA",
+ "und-150": "ru-Cyrl-RU",
+ "und-151": "ru-Cyrl-RU",
+ "und-154": "en-Latn-GB",
+ "und-155": "de-Latn-DE",
+ "und-202": "en-Latn-NG",
+ "und-419": "es-Latn-419",
+ "und-AD": "ca-Latn-AD",
+ "und-AE": "ar-Arab-AE",
+ "und-AF": "fa-Arab-AF",
+ "und-AL": "sq-Latn-AL",
+ "und-AM": "hy-Armn-AM",
+ "und-AO": "pt-Latn-AO",
+ "und-AQ": "und-Latn-AQ",
+ "und-AR": "es-Latn-AR",
+ "und-AS": "sm-Latn-AS",
+ "und-AT": "de-Latn-AT",
+ "und-AW": "nl-Latn-AW",
+ "und-AX": "sv-Latn-AX",
+ "und-AZ": "az-Latn-AZ",
+ "und-Adlm": "ff-Adlm-GN",
+ "und-Aghb": "lez-Aghb-RU",
+ "und-Ahom": "aho-Ahom-IN",
+ "und-Arab": "ar-Arab-EG",
+ "und-Arab-CC": "ms-Arab-CC",
+ "und-Arab-CN": "ug-Arab-CN",
+ "und-Arab-GB": "ks-Arab-GB",
+ "und-Arab-ID": "ms-Arab-ID",
+ "und-Arab-IN": "ur-Arab-IN",
+ "und-Arab-KH": "cja-Arab-KH",
+ "und-Arab-MM": "rhg-Arab-MM",
+ "und-Arab-MN": "kk-Arab-MN",
+ "und-Arab-MU": "ur-Arab-MU",
+ "und-Arab-NG": "ha-Arab-NG",
+ "und-Arab-PK": "ur-Arab-PK",
+ "und-Arab-TG": "apd-Arab-TG",
+ "und-Arab-TH": "mfa-Arab-TH",
+ "und-Arab-TJ": "fa-Arab-TJ",
+ "und-Arab-TR": "az-Arab-TR",
+ "und-Arab-YT": "swb-Arab-YT",
+ "und-Armi": "arc-Armi-IR",
+ "und-Armn": "hy-Armn-AM",
+ "und-Avst": "ae-Avst-IR",
+ "und-BA": "bs-Latn-BA",
+ "und-BD": "bn-Beng-BD",
+ "und-BE": "nl-Latn-BE",
+ "und-BF": "fr-Latn-BF",
+ "und-BG": "bg-Cyrl-BG",
+ "und-BH": "ar-Arab-BH",
+ "und-BI": "rn-Latn-BI",
+ "und-BJ": "fr-Latn-BJ",
+ "und-BL": "fr-Latn-BL",
+ "und-BN": "ms-Latn-BN",
+ "und-BO": "es-Latn-BO",
+ "und-BQ": "pap-Latn-BQ",
+ "und-BR": "pt-Latn-BR",
+ "und-BT": "dz-Tibt-BT",
+ "und-BV": "und-Latn-BV",
+ "und-BY": "be-Cyrl-BY",
+ "und-Bali": "ban-Bali-ID",
+ "und-Bamu": "bax-Bamu-CM",
+ "und-Bass": "bsq-Bass-LR",
+ "und-Batk": "bbc-Batk-ID",
+ "und-Beng": "bn-Beng-BD",
+ "und-Bhks": "sa-Bhks-IN",
+ "und-Bopo": "zh-Bopo-TW",
+ "und-Brah": "pka-Brah-IN",
+ "und-Brai": "fr-Brai-FR",
+ "und-Bugi": "bug-Bugi-ID",
+ "und-Buhd": "bku-Buhd-PH",
+ "und-CD": "sw-Latn-CD",
+ "und-CF": "fr-Latn-CF",
+ "und-CG": "fr-Latn-CG",
+ "und-CH": "de-Latn-CH",
+ "und-CI": "fr-Latn-CI",
+ "und-CL": "es-Latn-CL",
+ "und-CM": "fr-Latn-CM",
+ "und-CN": "zh-Hans-CN",
+ "und-CO": "es-Latn-CO",
+ "und-CP": "und-Latn-CP",
+ "und-CR": "es-Latn-CR",
+ "und-CU": "es-Latn-CU",
+ "und-CV": "pt-Latn-CV",
+ "und-CW": "pap-Latn-CW",
+ "und-CY": "el-Grek-CY",
+ "und-CZ": "cs-Latn-CZ",
+ "und-Cakm": "ccp-Cakm-BD",
+ "und-Cans": "cr-Cans-CA",
+ "und-Cari": "xcr-Cari-TR",
+ "und-Cham": "cjm-Cham-VN",
+ "und-Cher": "chr-Cher-US",
+ "und-Chrs": "xco-Chrs-UZ",
+ "und-Copt": "cop-Copt-EG",
+ "und-Cprt": "grc-Cprt-CY",
+ "und-Cyrl": "ru-Cyrl-RU",
+ "und-Cyrl-AL": "mk-Cyrl-AL",
+ "und-Cyrl-BA": "sr-Cyrl-BA",
+ "und-Cyrl-GE": "ab-Cyrl-GE",
+ "und-Cyrl-GR": "mk-Cyrl-GR",
+ "und-Cyrl-MD": "uk-Cyrl-MD",
+ "und-Cyrl-RO": "bg-Cyrl-RO",
+ "und-Cyrl-SK": "uk-Cyrl-SK",
+ "und-Cyrl-TR": "kbd-Cyrl-TR",
+ "und-Cyrl-XK": "sr-Cyrl-XK",
+ "und-DE": "de-Latn-DE",
+ "und-DJ": "aa-Latn-DJ",
+ "und-DK": "da-Latn-DK",
+ "und-DO": "es-Latn-DO",
+ "und-DZ": "ar-Arab-DZ",
+ "und-Deva": "hi-Deva-IN",
+ "und-Deva-BT": "ne-Deva-BT",
+ "und-Deva-FJ": "hif-Deva-FJ",
+ "und-Deva-MU": "bho-Deva-MU",
+ "und-Deva-PK": "btv-Deva-PK",
+ "und-Diak": "dv-Diak-MV",
+ "und-Dogr": "doi-Dogr-IN",
+ "und-Dupl": "fr-Dupl-FR",
+ "und-EA": "es-Latn-EA",
+ "und-EC": "es-Latn-EC",
+ "und-EE": "et-Latn-EE",
+ "und-EG": "ar-Arab-EG",
+ "und-EH": "ar-Arab-EH",
+ "und-ER": "ti-Ethi-ER",
+ "und-ES": "es-Latn-ES",
+ "und-ET": "am-Ethi-ET",
+ "und-EU": "en-Latn-GB",
+ "und-EZ": "de-Latn-EZ",
+ "und-Egyp": "egy-Egyp-EG",
+ "und-Elba": "sq-Elba-AL",
+ "und-Elym": "arc-Elym-IR",
+ "und-Ethi": "am-Ethi-ET",
+ "und-FI": "fi-Latn-FI",
+ "und-FO": "fo-Latn-FO",
+ "und-FR": "fr-Latn-FR",
+ "und-GA": "fr-Latn-GA",
+ "und-GE": "ka-Geor-GE",
+ "und-GF": "fr-Latn-GF",
+ "und-GH": "ak-Latn-GH",
+ "und-GL": "kl-Latn-GL",
+ "und-GN": "fr-Latn-GN",
+ "und-GP": "fr-Latn-GP",
+ "und-GQ": "es-Latn-GQ",
+ "und-GR": "el-Grek-GR",
+ "und-GS": "und-Latn-GS",
+ "und-GT": "es-Latn-GT",
+ "und-GW": "pt-Latn-GW",
+ "und-Geor": "ka-Geor-GE",
+ "und-Glag": "cu-Glag-BG",
+ "und-Gong": "wsg-Gong-IN",
+ "und-Gonm": "esg-Gonm-IN",
+ "und-Goth": "got-Goth-UA",
+ "und-Gran": "sa-Gran-IN",
+ "und-Grek": "el-Grek-GR",
+ "und-Grek-TR": "bgx-Grek-TR",
+ "und-Gujr": "gu-Gujr-IN",
+ "und-Guru": "pa-Guru-IN",
+ "und-HK": "zh-Hant-HK",
+ "und-HM": "und-Latn-HM",
+ "und-HN": "es-Latn-HN",
+ "und-HR": "hr-Latn-HR",
+ "und-HT": "ht-Latn-HT",
+ "und-HU": "hu-Latn-HU",
+ "und-Hanb": "zh-Hanb-TW",
+ "und-Hang": "ko-Hang-KR",
+ "und-Hani": "zh-Hani-CN",
+ "und-Hano": "hnn-Hano-PH",
+ "und-Hans": "zh-Hans-CN",
+ "und-Hant": "zh-Hant-TW",
+ "und-Hatr": "mis-Hatr-IQ",
+ "und-Hebr": "he-Hebr-IL",
+ "und-Hebr-CA": "yi-Hebr-CA",
+ "und-Hebr-GB": "yi-Hebr-GB",
+ "und-Hebr-SE": "yi-Hebr-SE",
+ "und-Hebr-UA": "yi-Hebr-UA",
+ "und-Hebr-US": "yi-Hebr-US",
+ "und-Hira": "ja-Hira-JP",
+ "und-Hluw": "hlu-Hluw-TR",
+ "und-Hmng": "hnj-Hmng-LA",
+ "und-Hmnp": "mww-Hmnp-US",
+ "und-Hung": "hu-Hung-HU",
+ "und-IC": "es-Latn-IC",
+ "und-ID": "id-Latn-ID",
+ "und-IL": "he-Hebr-IL",
+ "und-IN": "hi-Deva-IN",
+ "und-IQ": "ar-Arab-IQ",
+ "und-IR": "fa-Arab-IR",
+ "und-IS": "is-Latn-IS",
+ "und-IT": "it-Latn-IT",
+ "und-Ital": "ett-Ital-IT",
+ "und-JO": "ar-Arab-JO",
+ "und-JP": "ja-Jpan-JP",
+ "und-Jamo": "ko-Jamo-KR",
+ "und-Java": "jv-Java-ID",
+ "und-Jpan": "ja-Jpan-JP",
+ "und-KE": "sw-Latn-KE",
+ "und-KG": "ky-Cyrl-KG",
+ "und-KH": "km-Khmr-KH",
+ "und-KM": "ar-Arab-KM",
+ "und-KP": "ko-Kore-KP",
+ "und-KR": "ko-Kore-KR",
+ "und-KW": "ar-Arab-KW",
+ "und-KZ": "ru-Cyrl-KZ",
+ "und-Kali": "eky-Kali-MM",
+ "und-Kana": "ja-Kana-JP",
+ "und-Khar": "pra-Khar-PK",
+ "und-Khmr": "km-Khmr-KH",
+ "und-Khoj": "sd-Khoj-IN",
+ "und-Kits": "zkt-Kits-CN",
+ "und-Knda": "kn-Knda-IN",
+ "und-Kore": "ko-Kore-KR",
+ "und-Kthi": "bho-Kthi-IN",
+ "und-LA": "lo-Laoo-LA",
+ "und-LB": "ar-Arab-LB",
+ "und-LI": "de-Latn-LI",
+ "und-LK": "si-Sinh-LK",
+ "und-LS": "st-Latn-LS",
+ "und-LT": "lt-Latn-LT",
+ "und-LU": "fr-Latn-LU",
+ "und-LV": "lv-Latn-LV",
+ "und-LY": "ar-Arab-LY",
+ "und-Lana": "nod-Lana-TH",
+ "und-Laoo": "lo-Laoo-LA",
+ "und-Latn-AF": "tk-Latn-AF",
+ "und-Latn-AM": "ku-Latn-AM",
+ "und-Latn-CN": "za-Latn-CN",
+ "und-Latn-CY": "tr-Latn-CY",
+ "und-Latn-DZ": "fr-Latn-DZ",
+ "und-Latn-ET": "en-Latn-ET",
+ "und-Latn-GE": "ku-Latn-GE",
+ "und-Latn-IR": "tk-Latn-IR",
+ "und-Latn-KM": "fr-Latn-KM",
+ "und-Latn-MA": "fr-Latn-MA",
+ "und-Latn-MK": "sq-Latn-MK",
+ "und-Latn-MM": "kac-Latn-MM",
+ "und-Latn-MO": "pt-Latn-MO",
+ "und-Latn-MR": "fr-Latn-MR",
+ "und-Latn-RU": "krl-Latn-RU",
+ "und-Latn-SY": "fr-Latn-SY",
+ "und-Latn-TN": "fr-Latn-TN",
+ "und-Latn-TW": "trv-Latn-TW",
+ "und-Latn-UA": "pl-Latn-UA",
+ "und-Lepc": "lep-Lepc-IN",
+ "und-Limb": "lif-Limb-IN",
+ "und-Lina": "lab-Lina-GR",
+ "und-Linb": "grc-Linb-GR",
+ "und-Lisu": "lis-Lisu-CN",
+ "und-Lyci": "xlc-Lyci-TR",
+ "und-Lydi": "xld-Lydi-TR",
+ "und-MA": "ar-Arab-MA",
+ "und-MC": "fr-Latn-MC",
+ "und-MD": "ro-Latn-MD",
+ "und-ME": "sr-Latn-ME",
+ "und-MF": "fr-Latn-MF",
+ "und-MG": "mg-Latn-MG",
+ "und-MK": "mk-Cyrl-MK",
+ "und-ML": "bm-Latn-ML",
+ "und-MM": "my-Mymr-MM",
+ "und-MN": "mn-Cyrl-MN",
+ "und-MO": "zh-Hant-MO",
+ "und-MQ": "fr-Latn-MQ",
+ "und-MR": "ar-Arab-MR",
+ "und-MT": "mt-Latn-MT",
+ "und-MU": "mfe-Latn-MU",
+ "und-MV": "dv-Thaa-MV",
+ "und-MX": "es-Latn-MX",
+ "und-MY": "ms-Latn-MY",
+ "und-MZ": "pt-Latn-MZ",
+ "und-Mahj": "hi-Mahj-IN",
+ "und-Maka": "mak-Maka-ID",
+ "und-Mand": "myz-Mand-IR",
+ "und-Mani": "xmn-Mani-CN",
+ "und-Marc": "bo-Marc-CN",
+ "und-Medf": "mis-Medf-NG",
+ "und-Mend": "men-Mend-SL",
+ "und-Merc": "xmr-Merc-SD",
+ "und-Mero": "xmr-Mero-SD",
+ "und-Mlym": "ml-Mlym-IN",
+ "und-Modi": "mr-Modi-IN",
+ "und-Mong": "mn-Mong-CN",
+ "und-Mroo": "mro-Mroo-BD",
+ "und-Mtei": "mni-Mtei-IN",
+ "und-Mult": "skr-Mult-PK",
+ "und-Mymr": "my-Mymr-MM",
+ "und-Mymr-IN": "kht-Mymr-IN",
+ "und-Mymr-TH": "mnw-Mymr-TH",
+ "und-NA": "af-Latn-NA",
+ "und-NC": "fr-Latn-NC",
+ "und-NE": "ha-Latn-NE",
+ "und-NI": "es-Latn-NI",
+ "und-NL": "nl-Latn-NL",
+ "und-NO": "nb-Latn-NO",
+ "und-NP": "ne-Deva-NP",
+ "und-Nand": "sa-Nand-IN",
+ "und-Narb": "xna-Narb-SA",
+ "und-Nbat": "arc-Nbat-JO",
+ "und-Newa": "new-Newa-NP",
+ "und-Nkoo": "man-Nkoo-GN",
+ "und-Nshu": "zhx-Nshu-CN",
+ "und-OM": "ar-Arab-OM",
+ "und-Ogam": "sga-Ogam-IE",
+ "und-Olck": "sat-Olck-IN",
+ "und-Orkh": "otk-Orkh-MN",
+ "und-Orya": "or-Orya-IN",
+ "und-Osge": "osa-Osge-US",
+ "und-Osma": "so-Osma-SO",
+ "und-PA": "es-Latn-PA",
+ "und-PE": "es-Latn-PE",
+ "und-PF": "fr-Latn-PF",
+ "und-PG": "tpi-Latn-PG",
+ "und-PH": "fil-Latn-PH",
+ "und-PK": "ur-Arab-PK",
+ "und-PL": "pl-Latn-PL",
+ "und-PM": "fr-Latn-PM",
+ "und-PR": "es-Latn-PR",
+ "und-PS": "ar-Arab-PS",
+ "und-PT": "pt-Latn-PT",
+ "und-PW": "pau-Latn-PW",
+ "und-PY": "gn-Latn-PY",
+ "und-Palm": "arc-Palm-SY",
+ "und-Pauc": "ctd-Pauc-MM",
+ "und-Perm": "kv-Perm-RU",
+ "und-Phag": "lzh-Phag-CN",
+ "und-Phli": "pal-Phli-IR",
+ "und-Phlp": "pal-Phlp-CN",
+ "und-Phnx": "phn-Phnx-LB",
+ "und-Plrd": "hmd-Plrd-CN",
+ "und-Prti": "xpr-Prti-IR",
+ "und-QA": "ar-Arab-QA",
+ "und-QO": "en-Latn-DG",
+ "und-RE": "fr-Latn-RE",
+ "und-RO": "ro-Latn-RO",
+ "und-RS": "sr-Cyrl-RS",
+ "und-RU": "ru-Cyrl-RU",
+ "und-RW": "rw-Latn-RW",
+ "und-Rjng": "rej-Rjng-ID",
+ "und-Rohg": "rhg-Rohg-MM",
+ "und-Runr": "non-Runr-SE",
+ "und-SA": "ar-Arab-SA",
+ "und-SC": "fr-Latn-SC",
+ "und-SD": "ar-Arab-SD",
+ "und-SE": "sv-Latn-SE",
+ "und-SI": "sl-Latn-SI",
+ "und-SJ": "nb-Latn-SJ",
+ "und-SK": "sk-Latn-SK",
+ "und-SM": "it-Latn-SM",
+ "und-SN": "fr-Latn-SN",
+ "und-SO": "so-Latn-SO",
+ "und-SR": "nl-Latn-SR",
+ "und-ST": "pt-Latn-ST",
+ "und-SV": "es-Latn-SV",
+ "und-SY": "ar-Arab-SY",
+ "und-Samr": "smp-Samr-IL",
+ "und-Sarb": "xsa-Sarb-YE",
+ "und-Saur": "saz-Saur-IN",
+ "und-Sgnw": "ase-Sgnw-US",
+ "und-Shaw": "en-Shaw-GB",
+ "und-Shrd": "sa-Shrd-IN",
+ "und-Sidd": "sa-Sidd-IN",
+ "und-Sind": "sd-Sind-IN",
+ "und-Sinh": "si-Sinh-LK",
+ "und-Sogd": "sog-Sogd-UZ",
+ "und-Sogo": "sog-Sogo-UZ",
+ "und-Sora": "srb-Sora-IN",
+ "und-Soyo": "cmg-Soyo-MN",
+ "und-Sund": "su-Sund-ID",
+ "und-Sylo": "syl-Sylo-BD",
+ "und-Syrc": "syr-Syrc-IQ",
+ "und-TD": "fr-Latn-TD",
+ "und-TF": "fr-Latn-TF",
+ "und-TG": "fr-Latn-TG",
+ "und-TH": "th-Thai-TH",
+ "und-TJ": "tg-Cyrl-TJ",
+ "und-TK": "tkl-Latn-TK",
+ "und-TL": "pt-Latn-TL",
+ "und-TM": "tk-Latn-TM",
+ "und-TN": "ar-Arab-TN",
+ "und-TO": "to-Latn-TO",
+ "und-TR": "tr-Latn-TR",
+ "und-TV": "tvl-Latn-TV",
+ "und-TW": "zh-Hant-TW",
+ "und-TZ": "sw-Latn-TZ",
+ "und-Tagb": "tbw-Tagb-PH",
+ "und-Takr": "doi-Takr-IN",
+ "und-Tale": "tdd-Tale-CN",
+ "und-Talu": "khb-Talu-CN",
+ "und-Taml": "ta-Taml-IN",
+ "und-Tang": "txg-Tang-CN",
+ "und-Tavt": "blt-Tavt-VN",
+ "und-Telu": "te-Telu-IN",
+ "und-Tfng": "zgh-Tfng-MA",
+ "und-Tglg": "fil-Tglg-PH",
+ "und-Thaa": "dv-Thaa-MV",
+ "und-Thai": "th-Thai-TH",
+ "und-Thai-CN": "lcp-Thai-CN",
+ "und-Thai-KH": "kdt-Thai-KH",
+ "und-Thai-LA": "kdt-Thai-LA",
+ "und-Tibt": "bo-Tibt-CN",
+ "und-Tirh": "mai-Tirh-IN",
+ "und-UA": "uk-Cyrl-UA",
+ "und-UG": "sw-Latn-UG",
+ "und-UY": "es-Latn-UY",
+ "und-UZ": "uz-Latn-UZ",
+ "und-Ugar": "uga-Ugar-SY",
+ "und-VA": "it-Latn-VA",
+ "und-VE": "es-Latn-VE",
+ "und-VN": "vi-Latn-VN",
+ "und-VU": "bi-Latn-VU",
+ "und-Vaii": "vai-Vaii-LR",
+ "und-WF": "fr-Latn-WF",
+ "und-WS": "sm-Latn-WS",
+ "und-Wara": "hoc-Wara-IN",
+ "und-Wcho": "nnp-Wcho-IN",
+ "und-XK": "sq-Latn-XK",
+ "und-Xpeo": "peo-Xpeo-IR",
+ "und-Xsux": "akk-Xsux-IQ",
+ "und-YE": "ar-Arab-YE",
+ "und-YT": "fr-Latn-YT",
+ "und-Yezi": "ku-Yezi-GE",
+ "und-Yiii": "ii-Yiii-CN",
+ "und-ZW": "sn-Latn-ZW",
+ "und-Zanb": "cmg-Zanb-MN",
+ "unr": "unr-Beng-IN",
+ "unr-Deva": "unr-Deva-NP",
+ "unr-NP": "unr-Deva-NP",
+ "unx": "unx-Beng-IN",
+ "uok": "ema-Latn-ZZ",
+ "ur": "ur-Arab-PK",
+ "uri": "uri-Latn-ZZ",
+ "urt": "urt-Latn-ZZ",
+ "urw": "urw-Latn-ZZ",
+ "usa": "usa-Latn-ZZ",
+ "utr": "utr-Latn-ZZ",
+ "uvh": "uvh-Latn-ZZ",
+ "uvl": "uvl-Latn-ZZ",
+ "uz": "uz-Latn-UZ",
+ "uz-AF": "uz-Arab-AF",
+ "uz-Arab": "uz-Arab-AF",
+ "uz-CN": "uz-Cyrl-CN",
+ "vag": "vag-Latn-ZZ",
+ "vai": "vai-Vaii-LR",
+ "van": "van-Latn-ZZ",
+ "ve": "ve-Latn-ZA",
+ "vec": "vec-Latn-IT",
+ "vep": "vep-Latn-RU",
+ "vi": "vi-Latn-VN",
+ "vic": "vic-Latn-SX",
+ "viv": "viv-Latn-ZZ",
+ "vls": "vls-Latn-BE",
+ "vmf": "vmf-Latn-DE",
+ "vmw": "vmw-Latn-MZ",
+ "vo": "vo-Latn-001",
+ "vot": "vot-Latn-RU",
+ "vro": "vro-Latn-EE",
+ "vun": "vun-Latn-TZ",
+ "vut": "vut-Latn-ZZ",
+ "wa": "wa-Latn-BE",
+ "wae": "wae-Latn-CH",
+ "waj": "waj-Latn-ZZ",
+ "wal": "wal-Ethi-ET",
+ "wan": "wan-Latn-ZZ",
+ "war": "war-Latn-PH",
+ "wbp": "wbp-Latn-AU",
+ "wbq": "wbq-Telu-IN",
+ "wbr": "wbr-Deva-IN",
+ "wci": "wci-Latn-ZZ",
+ "wer": "wer-Latn-ZZ",
+ "wgi": "wgi-Latn-ZZ",
+ "whg": "whg-Latn-ZZ",
+ "wib": "wib-Latn-ZZ",
+ "wiu": "wiu-Latn-ZZ",
+ "wiv": "wiv-Latn-ZZ",
+ "wja": "wja-Latn-ZZ",
+ "wji": "wji-Latn-ZZ",
+ "wls": "wls-Latn-WF",
+ "wmo": "wmo-Latn-ZZ",
+ "wnc": "wnc-Latn-ZZ",
+ "wni": "wni-Arab-KM",
+ "wnu": "wnu-Latn-ZZ",
+ "wo": "wo-Latn-SN",
+ "wob": "wob-Latn-ZZ",
+ "wos": "wos-Latn-ZZ",
+ "wrs": "wrs-Latn-ZZ",
+ "wsg": "wsg-Gong-IN",
+ "wsk": "wsk-Latn-ZZ",
+ "wtm": "wtm-Deva-IN",
+ "wuu": "wuu-Hans-CN",
+ "wuv": "wuv-Latn-ZZ",
+ "wwa": "wwa-Latn-ZZ",
+ "xav": "xav-Latn-BR",
+ "xbi": "xbi-Latn-ZZ",
+ "xco": "xco-Chrs-UZ",
+ "xcr": "xcr-Cari-TR",
+ "xes": "xes-Latn-ZZ",
+ "xh": "xh-Latn-ZA",
+ "xla": "xla-Latn-ZZ",
+ "xlc": "xlc-Lyci-TR",
+ "xld": "xld-Lydi-TR",
+ "xmf": "xmf-Geor-GE",
+ "xmn": "xmn-Mani-CN",
+ "xmr": "xmr-Merc-SD",
+ "xna": "xna-Narb-SA",
+ "xnr": "xnr-Deva-IN",
+ "xog": "xog-Latn-UG",
+ "xon": "xon-Latn-ZZ",
+ "xpr": "xpr-Prti-IR",
+ "xrb": "xrb-Latn-ZZ",
+ "xsa": "xsa-Sarb-YE",
+ "xsi": "xsi-Latn-ZZ",
+ "xsm": "xsm-Latn-ZZ",
+ "xsr": "xsr-Deva-NP",
+ "xwe": "xwe-Latn-ZZ",
+ "yam": "yam-Latn-ZZ",
+ "yao": "yao-Latn-MZ",
+ "yap": "yap-Latn-FM",
+ "yas": "yas-Latn-ZZ",
+ "yat": "yat-Latn-ZZ",
+ "yav": "yav-Latn-CM",
+ "yay": "yay-Latn-ZZ",
+ "yaz": "yaz-Latn-ZZ",
+ "yba": "yba-Latn-ZZ",
+ "ybb": "ybb-Latn-CM",
+ "yby": "yby-Latn-ZZ",
+ "yer": "yer-Latn-ZZ",
+ "ygr": "ygr-Latn-ZZ",
+ "ygw": "ygw-Latn-ZZ",
+ "yi": "yi-Hebr-001",
+ "yko": "yko-Latn-ZZ",
+ "yle": "yle-Latn-ZZ",
+ "ylg": "ylg-Latn-ZZ",
+ "yll": "yll-Latn-ZZ",
+ "yml": "yml-Latn-ZZ",
+ "yo": "yo-Latn-NG",
+ "yon": "yon-Latn-ZZ",
+ "yrb": "yrb-Latn-ZZ",
+ "yre": "yre-Latn-ZZ",
+ "yrl": "yrl-Latn-BR",
+ "yss": "yss-Latn-ZZ",
+ "yua": "yua-Latn-MX",
+ "yue": "yue-Hant-HK",
+ "yue-CN": "yue-Hans-CN",
+ "yue-Hans": "yue-Hans-CN",
+ "yuj": "yuj-Latn-ZZ",
+ "yut": "yut-Latn-ZZ",
+ "yuw": "yuw-Latn-ZZ",
+ "za": "za-Latn-CN",
+ "zag": "zag-Latn-SD",
+ "zdj": "zdj-Arab-KM",
+ "zea": "zea-Latn-NL",
+ "zgh": "zgh-Tfng-MA",
+ "zh": "zh-Hans-CN",
+ "zh-AU": "zh-Hant-AU",
+ "zh-BN": "zh-Hant-BN",
+ "zh-Bopo": "zh-Bopo-TW",
+ "zh-GB": "zh-Hant-GB",
+ "zh-GF": "zh-Hant-GF",
+ "zh-HK": "zh-Hant-HK",
+ "zh-Hanb": "zh-Hanb-TW",
+ "zh-Hant": "zh-Hant-TW",
+ "zh-ID": "zh-Hant-ID",
+ "zh-MO": "zh-Hant-MO",
+ "zh-MY": "zh-Hant-MY",
+ "zh-PA": "zh-Hant-PA",
+ "zh-PF": "zh-Hant-PF",
+ "zh-PH": "zh-Hant-PH",
+ "zh-SR": "zh-Hant-SR",
+ "zh-TH": "zh-Hant-TH",
+ "zh-TW": "zh-Hant-TW",
+ "zh-US": "zh-Hant-US",
+ "zh-VN": "zh-Hant-VN",
+ "zhx": "zhx-Nshu-CN",
+ "zia": "zia-Latn-ZZ",
+ "zkt": "zkt-Kits-CN",
+ "zlm": "zlm-Latn-TG",
+ "zmi": "zmi-Latn-MY",
+ "zne": "zne-Latn-ZZ",
+ "zu": "zu-Latn-ZA",
+ "zza": "zza-Latn-TR",
+};
+
+// Extracted from likelySubtags.xml.
+// Derived from CLDR Supplemental Data, version 37.
+// https://unicode.org/Public/cldr/37/core.zip
+var minLikelySubtags = {
+ "aa-Latn-DJ": "aa-DJ",
+ "aa-Latn-ET": "aa",
+ "aai-Latn-ZZ": "aai",
+ "aak-Latn-ZZ": "aak",
+ "aau-Latn-ZZ": "aau",
+ "ab-Cyrl-GE": "ab",
+ "abi-Latn-ZZ": "abi",
+ "abq-Cyrl-ZZ": "abq",
+ "abr-Latn-GH": "abr",
+ "abt-Latn-ZZ": "abt",
+ "aby-Latn-ZZ": "aby",
+ "acd-Latn-ZZ": "acd",
+ "ace-Latn-ID": "ace",
+ "ach-Latn-UG": "ach",
+ "ada-Latn-GH": "ada",
+ "ade-Latn-ZZ": "ade",
+ "adj-Latn-ZZ": "adj",
+ "ady-Cyrl-RU": "ady",
+ "adz-Latn-ZZ": "adz",
+ "ae-Avst-IR": "ae",
+ "aeb-Arab-TN": "aeb",
+ "aey-Latn-ZZ": "aey",
+ "af-Latn-NA": "af-NA",
+ "af-Latn-ZA": "af",
+ "agc-Latn-ZZ": "agc",
+ "agd-Latn-ZZ": "agd",
+ "agg-Latn-ZZ": "agg",
+ "agm-Latn-ZZ": "agm",
+ "ago-Latn-ZZ": "ago",
+ "agq-Latn-CM": "agq",
+ "aha-Latn-ZZ": "aha",
+ "ahl-Latn-ZZ": "ahl",
+ "aho-Ahom-IN": "aho",
+ "ajg-Latn-ZZ": "ajg",
+ "ak-Latn-GH": "ak",
+ "akk-Xsux-IQ": "akk",
+ "ala-Latn-ZZ": "ala",
+ "ali-Latn-ZZ": "ali",
+ "aln-Latn-XK": "aln",
+ "alt-Cyrl-RU": "alt",
+ "am-Ethi-ET": "am",
+ "amm-Latn-ZZ": "amm",
+ "amn-Latn-ZZ": "amn",
+ "amo-Latn-NG": "amo",
+ "amp-Latn-ZZ": "amp",
+ "an-Latn-ES": "an",
+ "anc-Latn-ZZ": "anc",
+ "ank-Latn-ZZ": "ank",
+ "ann-Latn-ZZ": "ann",
+ "any-Latn-ZZ": "any",
+ "aoj-Latn-ZZ": "aoj",
+ "aom-Latn-ZZ": "aom",
+ "aoz-Latn-ID": "aoz",
+ "apc-Arab-ZZ": "apc",
+ "apd-Arab-TG": "apd",
+ "ape-Latn-ZZ": "ape",
+ "apr-Latn-ZZ": "apr",
+ "aps-Latn-ZZ": "aps",
+ "apz-Latn-ZZ": "apz",
+ "ar-Arab-AE": "ar-AE",
+ "ar-Arab-BH": "ar-BH",
+ "ar-Arab-DZ": "ar-DZ",
+ "ar-Arab-EG": "ar",
+ "ar-Arab-EH": "ar-EH",
+ "ar-Arab-IQ": "ar-IQ",
+ "ar-Arab-JO": "ar-JO",
+ "ar-Arab-KM": "ar-KM",
+ "ar-Arab-KW": "ar-KW",
+ "ar-Arab-LB": "ar-LB",
+ "ar-Arab-LY": "ar-LY",
+ "ar-Arab-MA": "ar-MA",
+ "ar-Arab-MR": "ar-MR",
+ "ar-Arab-OM": "ar-OM",
+ "ar-Arab-PS": "ar-PS",
+ "ar-Arab-QA": "ar-QA",
+ "ar-Arab-SA": "ar-SA",
+ "ar-Arab-SD": "ar-SD",
+ "ar-Arab-SY": "ar-SY",
+ "ar-Arab-TN": "ar-TN",
+ "ar-Arab-YE": "ar-YE",
+ "arc-Armi-IR": "arc",
+ "arc-Elym-IR": "arc-Elym",
+ "arc-Nbat-JO": "arc-Nbat",
+ "arc-Palm-SY": "arc-Palm",
+ "arh-Latn-ZZ": "arh",
+ "arn-Latn-CL": "arn",
+ "aro-Latn-BO": "aro",
+ "arq-Arab-DZ": "arq",
+ "ars-Arab-SA": "ars",
+ "ary-Arab-MA": "ary",
+ "arz-Arab-EG": "arz",
+ "as-Beng-IN": "as",
+ "asa-Latn-TZ": "asa",
+ "ase-Sgnw-US": "ase",
+ "asg-Latn-ZZ": "asg",
+ "aso-Latn-ZZ": "aso",
+ "ast-Latn-ES": "ast",
+ "ata-Latn-ZZ": "ata",
+ "atg-Latn-ZZ": "atg",
+ "atj-Latn-CA": "atj",
+ "auy-Latn-ZZ": "auy",
+ "av-Cyrl-RU": "av",
+ "avl-Arab-ZZ": "avl",
+ "avn-Latn-ZZ": "avn",
+ "avt-Latn-ZZ": "avt",
+ "avu-Latn-ZZ": "avu",
+ "awa-Deva-IN": "awa",
+ "awb-Latn-ZZ": "awb",
+ "awo-Latn-ZZ": "awo",
+ "awx-Latn-ZZ": "awx",
+ "ay-Latn-BO": "ay",
+ "ayb-Latn-ZZ": "ayb",
+ "az-Arab-IQ": "az-IQ",
+ "az-Arab-IR": "az-IR",
+ "az-Arab-TR": "az-Arab-TR",
+ "az-Cyrl-RU": "az-RU",
+ "az-Latn-AZ": "az",
+ "ba-Cyrl-RU": "ba",
+ "bal-Arab-PK": "bal",
+ "ban-Bali-ID": "ban-Bali",
+ "ban-Latn-ID": "ban",
+ "bap-Deva-NP": "bap",
+ "bar-Latn-AT": "bar",
+ "bas-Latn-CM": "bas",
+ "bav-Latn-ZZ": "bav",
+ "bax-Bamu-CM": "bax",
+ "bba-Latn-ZZ": "bba",
+ "bbb-Latn-ZZ": "bbb",
+ "bbc-Batk-ID": "bbc-Batk",
+ "bbc-Latn-ID": "bbc",
+ "bbd-Latn-ZZ": "bbd",
+ "bbj-Latn-CM": "bbj",
+ "bbp-Latn-ZZ": "bbp",
+ "bbr-Latn-ZZ": "bbr",
+ "bcf-Latn-ZZ": "bcf",
+ "bch-Latn-ZZ": "bch",
+ "bci-Latn-CI": "bci",
+ "bcm-Latn-ZZ": "bcm",
+ "bcn-Latn-ZZ": "bcn",
+ "bco-Latn-ZZ": "bco",
+ "bcq-Ethi-ZZ": "bcq",
+ "bcu-Latn-ZZ": "bcu",
+ "bdd-Latn-ZZ": "bdd",
+ "be-Cyrl-BY": "be",
+ "bef-Latn-ZZ": "bef",
+ "beh-Latn-ZZ": "beh",
+ "bej-Arab-SD": "bej",
+ "bem-Latn-ZM": "bem",
+ "bet-Latn-ZZ": "bet",
+ "bew-Latn-ID": "bew",
+ "bex-Latn-ZZ": "bex",
+ "bez-Latn-TZ": "bez",
+ "bfd-Latn-CM": "bfd",
+ "bfq-Taml-IN": "bfq",
+ "bft-Arab-PK": "bft",
+ "bfy-Deva-IN": "bfy",
+ "bg-Cyrl-BG": "bg",
+ "bg-Cyrl-RO": "bg-RO",
+ "bgc-Deva-IN": "bgc",
+ "bgn-Arab-PK": "bgn",
+ "bgx-Grek-TR": "bgx",
+ "bhb-Deva-IN": "bhb",
+ "bhg-Latn-ZZ": "bhg",
+ "bhi-Deva-IN": "bhi",
+ "bhl-Latn-ZZ": "bhl",
+ "bho-Deva-IN": "bho",
+ "bho-Deva-MU": "bho-MU",
+ "bho-Kthi-IN": "bho-Kthi",
+ "bhy-Latn-ZZ": "bhy",
+ "bi-Latn-VU": "bi",
+ "bib-Latn-ZZ": "bib",
+ "big-Latn-ZZ": "big",
+ "bik-Latn-PH": "bik",
+ "bim-Latn-ZZ": "bim",
+ "bin-Latn-NG": "bin",
+ "bio-Latn-ZZ": "bio",
+ "biq-Latn-ZZ": "biq",
+ "bjh-Latn-ZZ": "bjh",
+ "bji-Ethi-ZZ": "bji",
+ "bjj-Deva-IN": "bjj",
+ "bjn-Latn-ID": "bjn",
+ "bjo-Latn-ZZ": "bjo",
+ "bjr-Latn-ZZ": "bjr",
+ "bjt-Latn-SN": "bjt",
+ "bjz-Latn-ZZ": "bjz",
+ "bkc-Latn-ZZ": "bkc",
+ "bkm-Latn-CM": "bkm",
+ "bkq-Latn-ZZ": "bkq",
+ "bku-Buhd-PH": "bku-Buhd",
+ "bku-Latn-PH": "bku",
+ "bkv-Latn-ZZ": "bkv",
+ "blt-Tavt-VN": "blt",
+ "bm-Latn-ML": "bm",
+ "bmh-Latn-ZZ": "bmh",
+ "bmk-Latn-ZZ": "bmk",
+ "bmq-Latn-ML": "bmq",
+ "bmu-Latn-ZZ": "bmu",
+ "bn-Beng-BD": "bn",
+ "bng-Latn-ZZ": "bng",
+ "bnm-Latn-ZZ": "bnm",
+ "bnp-Latn-ZZ": "bnp",
+ "bo-Marc-CN": "bo-Marc",
+ "bo-Tibt-CN": "bo",
+ "boj-Latn-ZZ": "boj",
+ "bom-Latn-ZZ": "bom",
+ "bon-Latn-ZZ": "bon",
+ "bpy-Beng-IN": "bpy",
+ "bqc-Latn-ZZ": "bqc",
+ "bqi-Arab-IR": "bqi",
+ "bqp-Latn-ZZ": "bqp",
+ "bqv-Latn-CI": "bqv",
+ "br-Latn-FR": "br",
+ "bra-Deva-IN": "bra",
+ "brh-Arab-PK": "brh",
+ "brx-Deva-IN": "brx",
+ "brz-Latn-ZZ": "brz",
+ "bs-Latn-BA": "bs",
+ "bsj-Latn-ZZ": "bsj",
+ "bsq-Bass-LR": "bsq",
+ "bss-Latn-CM": "bss",
+ "bst-Ethi-ZZ": "bst",
+ "bto-Latn-PH": "bto",
+ "btt-Latn-ZZ": "btt",
+ "btv-Deva-PK": "btv",
+ "bua-Cyrl-RU": "bua",
+ "buc-Latn-YT": "buc",
+ "bud-Latn-ZZ": "bud",
+ "bug-Bugi-ID": "bug-Bugi",
+ "bug-Latn-ID": "bug",
+ "buk-Latn-ZZ": "buk",
+ "bum-Latn-CM": "bum",
+ "buo-Latn-ZZ": "buo",
+ "bus-Latn-ZZ": "bus",
+ "buu-Latn-ZZ": "buu",
+ "bvb-Latn-GQ": "bvb",
+ "bwd-Latn-ZZ": "bwd",
+ "bwr-Latn-ZZ": "bwr",
+ "bxh-Latn-ZZ": "bxh",
+ "bye-Latn-ZZ": "bye",
+ "byn-Ethi-ER": "byn",
+ "byr-Latn-ZZ": "byr",
+ "bys-Latn-ZZ": "bys",
+ "byv-Latn-CM": "byv",
+ "byx-Latn-ZZ": "byx",
+ "bza-Latn-ZZ": "bza",
+ "bze-Latn-ML": "bze",
+ "bzf-Latn-ZZ": "bzf",
+ "bzh-Latn-ZZ": "bzh",
+ "bzw-Latn-ZZ": "bzw",
+ "ca-Latn-AD": "ca-AD",
+ "ca-Latn-ES": "ca",
+ "cad-Latn-US": "cad",
+ "can-Latn-ZZ": "can",
+ "cbj-Latn-ZZ": "cbj",
+ "cch-Latn-NG": "cch",
+ "ccp-Cakm-BD": "ccp",
+ "ce-Cyrl-RU": "ce",
+ "ceb-Latn-PH": "ceb",
+ "cfa-Latn-ZZ": "cfa",
+ "cgg-Latn-UG": "cgg",
+ "ch-Latn-GU": "ch",
+ "chk-Latn-FM": "chk",
+ "chm-Cyrl-RU": "chm",
+ "cho-Latn-US": "cho",
+ "chp-Latn-CA": "chp",
+ "chr-Cher-US": "chr",
+ "cic-Latn-US": "cic",
+ "cja-Arab-KH": "cja",
+ "cjm-Cham-VN": "cjm",
+ "cjv-Latn-ZZ": "cjv",
+ "ckb-Arab-IQ": "ckb",
+ "ckl-Latn-ZZ": "ckl",
+ "cko-Latn-ZZ": "cko",
+ "cky-Latn-ZZ": "cky",
+ "cla-Latn-ZZ": "cla",
+ "cme-Latn-ZZ": "cme",
+ "cmg-Soyo-MN": "cmg",
+ "cmg-Zanb-MN": "cmg-Zanb",
+ "co-Latn-FR": "co",
+ "cop-Copt-EG": "cop",
+ "cps-Latn-PH": "cps",
+ "cr-Cans-CA": "cr",
+ "crh-Cyrl-UA": "crh",
+ "crj-Cans-CA": "crj",
+ "crk-Cans-CA": "crk",
+ "crl-Cans-CA": "crl",
+ "crm-Cans-CA": "crm",
+ "crs-Latn-SC": "crs",
+ "cs-Latn-CZ": "cs",
+ "csb-Latn-PL": "csb",
+ "csw-Cans-CA": "csw",
+ "ctd-Pauc-MM": "ctd",
+ "cu-Cyrl-RU": "cu",
+ "cu-Glag-BG": "cu-Glag",
+ "cv-Cyrl-RU": "cv",
+ "cy-Latn-GB": "cy",
+ "da-Latn-DK": "da",
+ "dad-Latn-ZZ": "dad",
+ "daf-Latn-ZZ": "daf",
+ "dag-Latn-ZZ": "dag",
+ "dah-Latn-ZZ": "dah",
+ "dak-Latn-US": "dak",
+ "dar-Cyrl-RU": "dar",
+ "dav-Latn-KE": "dav",
+ "dbd-Latn-ZZ": "dbd",
+ "dbq-Latn-ZZ": "dbq",
+ "dcc-Arab-IN": "dcc",
+ "ddn-Latn-ZZ": "ddn",
+ "de-Latn-AT": "de-AT",
+ "de-Latn-CH": "de-CH",
+ "de-Latn-DE": "de",
+ "de-Latn-EZ": "de-EZ",
+ "de-Latn-LI": "de-LI",
+ "ded-Latn-ZZ": "ded",
+ "den-Latn-CA": "den",
+ "dga-Latn-ZZ": "dga",
+ "dgh-Latn-ZZ": "dgh",
+ "dgi-Latn-ZZ": "dgi",
+ "dgl-Arab-ZZ": "dgl",
+ "dgr-Latn-CA": "dgr",
+ "dgz-Latn-ZZ": "dgz",
+ "dia-Latn-ZZ": "dia",
+ "dje-Latn-NE": "dje",
+ "dnj-Latn-CI": "dnj",
+ "dob-Latn-ZZ": "dob",
+ "doi-Arab-IN": "doi",
+ "doi-Dogr-IN": "doi-Dogr",
+ "doi-Takr-IN": "doi-Takr",
+ "dop-Latn-ZZ": "dop",
+ "dow-Latn-ZZ": "dow",
+ "dri-Latn-ZZ": "dri",
+ "drs-Ethi-ZZ": "drs",
+ "dsb-Latn-DE": "dsb",
+ "dtm-Latn-ML": "dtm",
+ "dtp-Latn-MY": "dtp",
+ "dts-Latn-ZZ": "dts",
+ "dty-Deva-NP": "dty",
+ "dua-Latn-CM": "dua",
+ "duc-Latn-ZZ": "duc",
+ "dud-Latn-ZZ": "dud",
+ "dug-Latn-ZZ": "dug",
+ "dv-Diak-MV": "dv-Diak",
+ "dv-Thaa-MV": "dv",
+ "dva-Latn-ZZ": "dva",
+ "dww-Latn-ZZ": "dww",
+ "dyo-Latn-SN": "dyo",
+ "dyu-Latn-BF": "dyu",
+ "dz-Tibt-BT": "dz",
+ "dzg-Latn-ZZ": "dzg",
+ "ebu-Latn-KE": "ebu",
+ "ee-Latn-GH": "ee",
+ "efi-Latn-NG": "efi",
+ "egl-Latn-IT": "egl",
+ "egy-Egyp-EG": "egy",
+ "eka-Latn-ZZ": "eka",
+ "eky-Kali-MM": "eky",
+ "el-Grek-CY": "el-CY",
+ "el-Grek-GR": "el",
+ "ema-Latn-ZZ": "ema",
+ "emi-Latn-ZZ": "emi",
+ "en-Latn-AU": "en-AU",
+ "en-Latn-DG": "en-DG",
+ "en-Latn-ET": "en-ET",
+ "en-Latn-GB": "en-GB",
+ "en-Latn-GU": "en-GU",
+ "en-Latn-NG": "en-NG",
+ "en-Latn-PG": "en-PG",
+ "en-Latn-US": "en",
+ "en-Latn-ZA": "en-ZA",
+ "en-Shaw-GB": "en-Shaw",
+ "enn-Latn-ZZ": "enn",
+ "enq-Latn-ZZ": "enq",
+ "eo-Latn-001": "eo",
+ "eri-Latn-ZZ": "eri",
+ "es-Latn-419": "es-419",
+ "es-Latn-AR": "es-AR",
+ "es-Latn-BO": "es-BO",
+ "es-Latn-CL": "es-CL",
+ "es-Latn-CO": "es-CO",
+ "es-Latn-CR": "es-CR",
+ "es-Latn-CU": "es-CU",
+ "es-Latn-DO": "es-DO",
+ "es-Latn-EA": "es-EA",
+ "es-Latn-EC": "es-EC",
+ "es-Latn-ES": "es",
+ "es-Latn-GQ": "es-GQ",
+ "es-Latn-GT": "es-GT",
+ "es-Latn-HN": "es-HN",
+ "es-Latn-IC": "es-IC",
+ "es-Latn-MX": "es-MX",
+ "es-Latn-NI": "es-NI",
+ "es-Latn-PA": "es-PA",
+ "es-Latn-PE": "es-PE",
+ "es-Latn-PR": "es-PR",
+ "es-Latn-SV": "es-SV",
+ "es-Latn-UY": "es-UY",
+ "es-Latn-VE": "es-VE",
+ "esg-Gonm-IN": "esg",
+ "esu-Latn-US": "esu",
+ "et-Latn-EE": "et",
+ "etr-Latn-ZZ": "etr",
+ "ett-Ital-IT": "ett",
+ "etu-Latn-ZZ": "etu",
+ "etx-Latn-ZZ": "etx",
+ "eu-Latn-ES": "eu",
+ "ewo-Latn-CM": "ewo",
+ "ext-Latn-ES": "ext",
+ "fa-Arab-AF": "fa-AF",
+ "fa-Arab-IR": "fa",
+ "fa-Arab-TJ": "fa-TJ",
+ "faa-Latn-ZZ": "faa",
+ "fab-Latn-ZZ": "fab",
+ "fag-Latn-ZZ": "fag",
+ "fai-Latn-ZZ": "fai",
+ "fan-Latn-GQ": "fan",
+ "ff-Adlm-GN": "ff-Adlm",
+ "ff-Latn-SN": "ff",
+ "ffi-Latn-ZZ": "ffi",
+ "ffm-Latn-ML": "ffm",
+ "fi-Latn-FI": "fi",
+ "fia-Arab-SD": "fia",
+ "fil-Latn-PH": "fil",
+ "fil-Tglg-PH": "fil-Tglg",
+ "fit-Latn-SE": "fit",
+ "fj-Latn-FJ": "fj",
+ "flr-Latn-ZZ": "flr",
+ "fmp-Latn-ZZ": "fmp",
+ "fo-Latn-FO": "fo",
+ "fod-Latn-ZZ": "fod",
+ "fon-Latn-BJ": "fon",
+ "for-Latn-ZZ": "for",
+ "fpe-Latn-ZZ": "fpe",
+ "fqs-Latn-ZZ": "fqs",
+ "fr-Brai-FR": "fr-Brai",
+ "fr-Dupl-FR": "fr-Dupl",
+ "fr-Latn-BF": "fr-BF",
+ "fr-Latn-BJ": "fr-BJ",
+ "fr-Latn-BL": "fr-BL",
+ "fr-Latn-CF": "fr-CF",
+ "fr-Latn-CG": "fr-CG",
+ "fr-Latn-CI": "fr-CI",
+ "fr-Latn-CM": "fr-CM",
+ "fr-Latn-DZ": "fr-DZ",
+ "fr-Latn-FR": "fr",
+ "fr-Latn-GA": "fr-GA",
+ "fr-Latn-GF": "fr-GF",
+ "fr-Latn-GN": "fr-GN",
+ "fr-Latn-GP": "fr-GP",
+ "fr-Latn-KM": "fr-KM",
+ "fr-Latn-LU": "fr-LU",
+ "fr-Latn-MA": "fr-MA",
+ "fr-Latn-MC": "fr-MC",
+ "fr-Latn-MF": "fr-MF",
+ "fr-Latn-MQ": "fr-MQ",
+ "fr-Latn-MR": "fr-MR",
+ "fr-Latn-NC": "fr-NC",
+ "fr-Latn-PF": "fr-PF",
+ "fr-Latn-PM": "fr-PM",
+ "fr-Latn-RE": "fr-RE",
+ "fr-Latn-SC": "fr-SC",
+ "fr-Latn-SN": "fr-SN",
+ "fr-Latn-SY": "fr-SY",
+ "fr-Latn-TD": "fr-TD",
+ "fr-Latn-TF": "fr-TF",
+ "fr-Latn-TG": "fr-TG",
+ "fr-Latn-TN": "fr-TN",
+ "fr-Latn-WF": "fr-WF",
+ "fr-Latn-YT": "fr-YT",
+ "frc-Latn-US": "frc",
+ "frp-Latn-FR": "frp",
+ "frr-Latn-DE": "frr",
+ "frs-Latn-DE": "frs",
+ "fub-Arab-CM": "fub",
+ "fud-Latn-WF": "fud",
+ "fue-Latn-ZZ": "fue",
+ "fuf-Latn-GN": "fuf",
+ "fuh-Latn-ZZ": "fuh",
+ "fuq-Latn-NE": "fuq",
+ "fur-Latn-IT": "fur",
+ "fuv-Latn-NG": "fuv",
+ "fuy-Latn-ZZ": "fuy",
+ "fvr-Latn-SD": "fvr",
+ "fy-Latn-NL": "fy",
+ "ga-Latn-IE": "ga",
+ "gaa-Latn-GH": "gaa",
+ "gaf-Latn-ZZ": "gaf",
+ "gag-Latn-MD": "gag",
+ "gah-Latn-ZZ": "gah",
+ "gaj-Latn-ZZ": "gaj",
+ "gam-Latn-ZZ": "gam",
+ "gan-Hans-CN": "gan",
+ "gaw-Latn-ZZ": "gaw",
+ "gay-Latn-ID": "gay",
+ "gba-Latn-ZZ": "gba",
+ "gbf-Latn-ZZ": "gbf",
+ "gbm-Deva-IN": "gbm",
+ "gby-Latn-ZZ": "gby",
+ "gbz-Arab-IR": "gbz",
+ "gcr-Latn-GF": "gcr",
+ "gd-Latn-GB": "gd",
+ "gde-Latn-ZZ": "gde",
+ "gdn-Latn-ZZ": "gdn",
+ "gdr-Latn-ZZ": "gdr",
+ "geb-Latn-ZZ": "geb",
+ "gej-Latn-ZZ": "gej",
+ "gel-Latn-ZZ": "gel",
+ "gez-Ethi-ET": "gez",
+ "gfk-Latn-ZZ": "gfk",
+ "ghs-Latn-ZZ": "ghs",
+ "gil-Latn-KI": "gil",
+ "gim-Latn-ZZ": "gim",
+ "gjk-Arab-PK": "gjk",
+ "gjn-Latn-ZZ": "gjn",
+ "gju-Arab-PK": "gju",
+ "gkn-Latn-ZZ": "gkn",
+ "gkp-Latn-ZZ": "gkp",
+ "gl-Latn-ES": "gl",
+ "glk-Arab-IR": "glk",
+ "gmm-Latn-ZZ": "gmm",
+ "gmv-Ethi-ZZ": "gmv",
+ "gn-Latn-PY": "gn",
+ "gnd-Latn-ZZ": "gnd",
+ "gng-Latn-ZZ": "gng",
+ "god-Latn-ZZ": "god",
+ "gof-Ethi-ZZ": "gof",
+ "goi-Latn-ZZ": "goi",
+ "gom-Deva-IN": "gom",
+ "gon-Telu-IN": "gon",
+ "gor-Latn-ID": "gor",
+ "gos-Latn-NL": "gos",
+ "got-Goth-UA": "got",
+ "grb-Latn-ZZ": "grb",
+ "grc-Cprt-CY": "grc",
+ "grc-Linb-GR": "grc-Linb",
+ "grt-Beng-IN": "grt",
+ "grw-Latn-ZZ": "grw",
+ "gsw-Latn-CH": "gsw",
+ "gu-Gujr-IN": "gu",
+ "gub-Latn-BR": "gub",
+ "guc-Latn-CO": "guc",
+ "gud-Latn-ZZ": "gud",
+ "gur-Latn-GH": "gur",
+ "guw-Latn-ZZ": "guw",
+ "gux-Latn-ZZ": "gux",
+ "guz-Latn-KE": "guz",
+ "gv-Latn-IM": "gv",
+ "gvf-Latn-ZZ": "gvf",
+ "gvr-Deva-NP": "gvr",
+ "gvs-Latn-ZZ": "gvs",
+ "gwc-Arab-ZZ": "gwc",
+ "gwi-Latn-CA": "gwi",
+ "gwt-Arab-ZZ": "gwt",
+ "gyi-Latn-ZZ": "gyi",
+ "ha-Arab-CM": "ha-CM",
+ "ha-Arab-NG": "ha-Arab",
+ "ha-Arab-SD": "ha-SD",
+ "ha-Latn-NE": "ha-NE",
+ "ha-Latn-NG": "ha",
+ "hag-Latn-ZZ": "hag",
+ "hak-Hans-CN": "hak",
+ "ham-Latn-ZZ": "ham",
+ "haw-Latn-US": "haw",
+ "haz-Arab-AF": "haz",
+ "hbb-Latn-ZZ": "hbb",
+ "hdy-Ethi-ZZ": "hdy",
+ "he-Hebr-IL": "he",
+ "hhy-Latn-ZZ": "hhy",
+ "hi-Deva-IN": "hi",
+ "hi-Latn-IN": "hi-Latn",
+ "hi-Mahj-IN": "hi-Mahj",
+ "hia-Latn-ZZ": "hia",
+ "hif-Deva-FJ": "hif-Deva",
+ "hif-Latn-FJ": "hif",
+ "hig-Latn-ZZ": "hig",
+ "hih-Latn-ZZ": "hih",
+ "hil-Latn-PH": "hil",
+ "hla-Latn-ZZ": "hla",
+ "hlu-Hluw-TR": "hlu",
+ "hmd-Plrd-CN": "hmd",
+ "hmt-Latn-ZZ": "hmt",
+ "hnd-Arab-PK": "hnd",
+ "hne-Deva-IN": "hne",
+ "hnj-Hmng-LA": "hnj",
+ "hnn-Hano-PH": "hnn-Hano",
+ "hnn-Latn-PH": "hnn",
+ "hno-Arab-PK": "hno",
+ "ho-Latn-PG": "ho",
+ "hoc-Deva-IN": "hoc",
+ "hoc-Wara-IN": "hoc-Wara",
+ "hoj-Deva-IN": "hoj",
+ "hot-Latn-ZZ": "hot",
+ "hr-Latn-HR": "hr",
+ "hsb-Latn-DE": "hsb",
+ "hsn-Hans-CN": "hsn",
+ "ht-Latn-HT": "ht",
+ "hu-Hung-HU": "hu-Hung",
+ "hu-Latn-HU": "hu",
+ "hui-Latn-ZZ": "hui",
+ "hy-Armn-AM": "hy",
+ "hz-Latn-NA": "hz",
+ "ia-Latn-001": "ia",
+ "ian-Latn-ZZ": "ian",
+ "iar-Latn-ZZ": "iar",
+ "iba-Latn-MY": "iba",
+ "ibb-Latn-NG": "ibb",
+ "iby-Latn-ZZ": "iby",
+ "ica-Latn-ZZ": "ica",
+ "ich-Latn-ZZ": "ich",
+ "id-Latn-ID": "id",
+ "idd-Latn-ZZ": "idd",
+ "idi-Latn-ZZ": "idi",
+ "idu-Latn-ZZ": "idu",
+ "ife-Latn-TG": "ife",
+ "ig-Latn-NG": "ig",
+ "igb-Latn-ZZ": "igb",
+ "ige-Latn-ZZ": "ige",
+ "ii-Yiii-CN": "ii",
+ "ijj-Latn-ZZ": "ijj",
+ "ik-Latn-US": "ik",
+ "ikk-Latn-ZZ": "ikk",
+ "ikt-Latn-CA": "ikt",
+ "ikw-Latn-ZZ": "ikw",
+ "ikx-Latn-ZZ": "ikx",
+ "ilo-Latn-PH": "ilo",
+ "imo-Latn-ZZ": "imo",
+ "inh-Cyrl-RU": "inh",
+ "io-Latn-001": "io",
+ "iou-Latn-ZZ": "iou",
+ "iri-Latn-ZZ": "iri",
+ "is-Latn-IS": "is",
+ "it-Latn-IT": "it",
+ "it-Latn-SM": "it-SM",
+ "it-Latn-VA": "it-VA",
+ "iu-Cans-CA": "iu",
+ "iwm-Latn-ZZ": "iwm",
+ "iws-Latn-ZZ": "iws",
+ "izh-Latn-RU": "izh",
+ "izi-Latn-ZZ": "izi",
+ "ja-Hira-JP": "ja-Hira",
+ "ja-Jpan-JP": "ja",
+ "ja-Kana-JP": "ja-Kana",
+ "jab-Latn-ZZ": "jab",
+ "jam-Latn-JM": "jam",
+ "jbo-Latn-001": "jbo",
+ "jbu-Latn-ZZ": "jbu",
+ "jen-Latn-ZZ": "jen",
+ "jgk-Latn-ZZ": "jgk",
+ "jgo-Latn-CM": "jgo",
+ "jib-Latn-ZZ": "jib",
+ "jmc-Latn-TZ": "jmc",
+ "jml-Deva-NP": "jml",
+ "jra-Latn-ZZ": "jra",
+ "jut-Latn-DK": "jut",
+ "jv-Java-ID": "jv-Java",
+ "jv-Latn-ID": "jv",
+ "ka-Geor-GE": "ka",
+ "kaa-Cyrl-UZ": "kaa",
+ "kab-Latn-DZ": "kab",
+ "kac-Latn-MM": "kac",
+ "kad-Latn-ZZ": "kad",
+ "kai-Latn-ZZ": "kai",
+ "kaj-Latn-NG": "kaj",
+ "kam-Latn-KE": "kam",
+ "kao-Latn-ML": "kao",
+ "kbd-Cyrl-RU": "kbd",
+ "kbd-Cyrl-TR": "kbd-TR",
+ "kbm-Latn-ZZ": "kbm",
+ "kbp-Latn-ZZ": "kbp",
+ "kbq-Latn-ZZ": "kbq",
+ "kbx-Latn-ZZ": "kbx",
+ "kby-Arab-NE": "kby",
+ "kcg-Latn-NG": "kcg",
+ "kck-Latn-ZW": "kck",
+ "kcl-Latn-ZZ": "kcl",
+ "kct-Latn-ZZ": "kct",
+ "kde-Latn-TZ": "kde",
+ "kdh-Arab-TG": "kdh",
+ "kdl-Latn-ZZ": "kdl",
+ "kdt-Thai-KH": "kdt-KH",
+ "kdt-Thai-LA": "kdt-LA",
+ "kdt-Thai-TH": "kdt",
+ "kea-Latn-CV": "kea",
+ "ken-Latn-CM": "ken",
+ "kez-Latn-ZZ": "kez",
+ "kfo-Latn-CI": "kfo",
+ "kfr-Deva-IN": "kfr",
+ "kfy-Deva-IN": "kfy",
+ "kg-Latn-CD": "kg",
+ "kge-Latn-ID": "kge",
+ "kgf-Latn-ZZ": "kgf",
+ "kgp-Latn-BR": "kgp",
+ "kha-Latn-IN": "kha",
+ "khb-Talu-CN": "khb",
+ "khn-Deva-IN": "khn",
+ "khq-Latn-ML": "khq",
+ "khs-Latn-ZZ": "khs",
+ "kht-Mymr-IN": "kht",
+ "khw-Arab-PK": "khw",
+ "khz-Latn-ZZ": "khz",
+ "ki-Latn-KE": "ki",
+ "kij-Latn-ZZ": "kij",
+ "kiu-Latn-TR": "kiu",
+ "kiw-Latn-ZZ": "kiw",
+ "kj-Latn-NA": "kj",
+ "kjd-Latn-ZZ": "kjd",
+ "kjg-Laoo-LA": "kjg",
+ "kjs-Latn-ZZ": "kjs",
+ "kjy-Latn-ZZ": "kjy",
+ "kk-Arab-AF": "kk-AF",
+ "kk-Arab-CN": "kk-CN",
+ "kk-Arab-IR": "kk-IR",
+ "kk-Arab-MN": "kk-MN",
+ "kk-Cyrl-KZ": "kk",
+ "kkc-Latn-ZZ": "kkc",
+ "kkj-Latn-CM": "kkj",
+ "kl-Latn-GL": "kl",
+ "kln-Latn-KE": "kln",
+ "klq-Latn-ZZ": "klq",
+ "klt-Latn-ZZ": "klt",
+ "klx-Latn-ZZ": "klx",
+ "km-Khmr-KH": "km",
+ "kmb-Latn-AO": "kmb",
+ "kmh-Latn-ZZ": "kmh",
+ "kmo-Latn-ZZ": "kmo",
+ "kms-Latn-ZZ": "kms",
+ "kmu-Latn-ZZ": "kmu",
+ "kmw-Latn-ZZ": "kmw",
+ "kn-Knda-IN": "kn",
+ "knf-Latn-GW": "knf",
+ "knp-Latn-ZZ": "knp",
+ "ko-Hang-KR": "ko-Hang",
+ "ko-Jamo-KR": "ko-Jamo",
+ "ko-Kore-KP": "ko-KP",
+ "ko-Kore-KR": "ko",
+ "koi-Cyrl-RU": "koi",
+ "kok-Deva-IN": "kok",
+ "kol-Latn-ZZ": "kol",
+ "kos-Latn-FM": "kos",
+ "koz-Latn-ZZ": "koz",
+ "kpe-Latn-LR": "kpe",
+ "kpf-Latn-ZZ": "kpf",
+ "kpo-Latn-ZZ": "kpo",
+ "kpr-Latn-ZZ": "kpr",
+ "kpx-Latn-ZZ": "kpx",
+ "kqb-Latn-ZZ": "kqb",
+ "kqf-Latn-ZZ": "kqf",
+ "kqs-Latn-ZZ": "kqs",
+ "kqy-Ethi-ZZ": "kqy",
+ "kr-Latn-ZZ": "kr",
+ "krc-Cyrl-RU": "krc",
+ "kri-Latn-SL": "kri",
+ "krj-Latn-PH": "krj",
+ "krl-Latn-RU": "krl",
+ "krs-Latn-ZZ": "krs",
+ "kru-Deva-IN": "kru",
+ "ks-Arab-GB": "ks-GB",
+ "ks-Arab-IN": "ks",
+ "ks-Deva-IN": "ks-Deva",
+ "ksb-Latn-TZ": "ksb",
+ "ksd-Latn-ZZ": "ksd",
+ "ksf-Latn-CM": "ksf",
+ "ksh-Latn-DE": "ksh",
+ "ksj-Latn-ZZ": "ksj",
+ "ksr-Latn-ZZ": "ksr",
+ "ktb-Ethi-ZZ": "ktb",
+ "ktm-Latn-ZZ": "ktm",
+ "kto-Latn-ZZ": "kto",
+ "ku-Arab-IQ": "ku-Arab",
+ "ku-Arab-LB": "ku-LB",
+ "ku-Latn-AM": "ku-AM",
+ "ku-Latn-GE": "ku-GE",
+ "ku-Latn-TR": "ku",
+ "ku-Yezi-GE": "ku-Yezi",
+ "kub-Latn-ZZ": "kub",
+ "kud-Latn-ZZ": "kud",
+ "kue-Latn-ZZ": "kue",
+ "kuj-Latn-ZZ": "kuj",
+ "kum-Cyrl-RU": "kum",
+ "kun-Latn-ZZ": "kun",
+ "kup-Latn-ZZ": "kup",
+ "kus-Latn-ZZ": "kus",
+ "kv-Cyrl-RU": "kv",
+ "kv-Perm-RU": "kv-Perm",
+ "kvg-Latn-ZZ": "kvg",
+ "kvr-Latn-ID": "kvr",
+ "kvx-Arab-PK": "kvx",
+ "kw-Latn-GB": "kw",
+ "kwj-Latn-ZZ": "kwj",
+ "kwo-Latn-ZZ": "kwo",
+ "kxa-Latn-ZZ": "kxa",
+ "kxc-Ethi-ZZ": "kxc",
+ "kxm-Thai-TH": "kxm",
+ "kxp-Arab-PK": "kxp",
+ "kxw-Latn-ZZ": "kxw",
+ "kxz-Latn-ZZ": "kxz",
+ "ky-Arab-CN": "ky-CN",
+ "ky-Cyrl-KG": "ky",
+ "ky-Latn-TR": "ky-TR",
+ "kye-Latn-ZZ": "kye",
+ "kyx-Latn-ZZ": "kyx",
+ "kzr-Latn-ZZ": "kzr",
+ "la-Latn-VA": "la",
+ "lab-Lina-GR": "lab",
+ "lad-Hebr-IL": "lad",
+ "lag-Latn-TZ": "lag",
+ "lah-Arab-PK": "lah",
+ "laj-Latn-UG": "laj",
+ "las-Latn-ZZ": "las",
+ "lb-Latn-LU": "lb",
+ "lbe-Cyrl-RU": "lbe",
+ "lbu-Latn-ZZ": "lbu",
+ "lbw-Latn-ID": "lbw",
+ "lcm-Latn-ZZ": "lcm",
+ "lcp-Thai-CN": "lcp",
+ "ldb-Latn-ZZ": "ldb",
+ "led-Latn-ZZ": "led",
+ "lee-Latn-ZZ": "lee",
+ "lem-Latn-ZZ": "lem",
+ "lep-Lepc-IN": "lep",
+ "leq-Latn-ZZ": "leq",
+ "leu-Latn-ZZ": "leu",
+ "lez-Aghb-RU": "lez-Aghb",
+ "lez-Cyrl-RU": "lez",
+ "lg-Latn-UG": "lg",
+ "lgg-Latn-ZZ": "lgg",
+ "li-Latn-NL": "li",
+ "lia-Latn-ZZ": "lia",
+ "lid-Latn-ZZ": "lid",
+ "lif-Deva-NP": "lif",
+ "lif-Limb-IN": "lif-Limb",
+ "lig-Latn-ZZ": "lig",
+ "lih-Latn-ZZ": "lih",
+ "lij-Latn-IT": "lij",
+ "lis-Lisu-CN": "lis",
+ "ljp-Latn-ID": "ljp",
+ "lki-Arab-IR": "lki",
+ "lkt-Latn-US": "lkt",
+ "lle-Latn-ZZ": "lle",
+ "lln-Latn-ZZ": "lln",
+ "lmn-Telu-IN": "lmn",
+ "lmo-Latn-IT": "lmo",
+ "lmp-Latn-ZZ": "lmp",
+ "ln-Latn-CD": "ln",
+ "lns-Latn-ZZ": "lns",
+ "lnu-Latn-ZZ": "lnu",
+ "lo-Laoo-LA": "lo",
+ "loj-Latn-ZZ": "loj",
+ "lok-Latn-ZZ": "lok",
+ "lol-Latn-CD": "lol",
+ "lor-Latn-ZZ": "lor",
+ "los-Latn-ZZ": "los",
+ "loz-Latn-ZM": "loz",
+ "lrc-Arab-IR": "lrc",
+ "lt-Latn-LT": "lt",
+ "ltg-Latn-LV": "ltg",
+ "lu-Latn-CD": "lu",
+ "lua-Latn-CD": "lua",
+ "luo-Latn-KE": "luo",
+ "luy-Latn-KE": "luy",
+ "luz-Arab-IR": "luz",
+ "lv-Latn-LV": "lv",
+ "lwl-Thai-TH": "lwl",
+ "lzh-Hans-CN": "lzh",
+ "lzh-Phag-CN": "lzh-Phag",
+ "lzz-Latn-TR": "lzz",
+ "mad-Latn-ID": "mad",
+ "maf-Latn-CM": "maf",
+ "mag-Deva-IN": "mag",
+ "mai-Deva-IN": "mai",
+ "mai-Tirh-IN": "mai-Tirh",
+ "mak-Latn-ID": "mak",
+ "mak-Maka-ID": "mak-Maka",
+ "man-Latn-GM": "man",
+ "man-Nkoo-GN": "man-GN",
+ "mas-Latn-KE": "mas",
+ "maw-Latn-ZZ": "maw",
+ "maz-Latn-MX": "maz",
+ "mbh-Latn-ZZ": "mbh",
+ "mbo-Latn-ZZ": "mbo",
+ "mbq-Latn-ZZ": "mbq",
+ "mbu-Latn-ZZ": "mbu",
+ "mbw-Latn-ZZ": "mbw",
+ "mci-Latn-ZZ": "mci",
+ "mcp-Latn-ZZ": "mcp",
+ "mcq-Latn-ZZ": "mcq",
+ "mcr-Latn-ZZ": "mcr",
+ "mcu-Latn-ZZ": "mcu",
+ "mda-Latn-ZZ": "mda",
+ "mde-Arab-ZZ": "mde",
+ "mdf-Cyrl-RU": "mdf",
+ "mdh-Latn-PH": "mdh",
+ "mdj-Latn-ZZ": "mdj",
+ "mdr-Latn-ID": "mdr",
+ "mdx-Ethi-ZZ": "mdx",
+ "med-Latn-ZZ": "med",
+ "mee-Latn-ZZ": "mee",
+ "mek-Latn-ZZ": "mek",
+ "men-Latn-SL": "men",
+ "men-Mend-SL": "men-Mend",
+ "mer-Latn-KE": "mer",
+ "met-Latn-ZZ": "met",
+ "meu-Latn-ZZ": "meu",
+ "mfa-Arab-TH": "mfa",
+ "mfe-Latn-MU": "mfe",
+ "mfn-Latn-ZZ": "mfn",
+ "mfo-Latn-ZZ": "mfo",
+ "mfq-Latn-ZZ": "mfq",
+ "mg-Latn-MG": "mg",
+ "mgh-Latn-MZ": "mgh",
+ "mgl-Latn-ZZ": "mgl",
+ "mgo-Latn-CM": "mgo",
+ "mgp-Deva-NP": "mgp",
+ "mgy-Latn-TZ": "mgy",
+ "mh-Latn-MH": "mh",
+ "mhi-Latn-ZZ": "mhi",
+ "mhl-Latn-ZZ": "mhl",
+ "mi-Latn-NZ": "mi",
+ "mif-Latn-ZZ": "mif",
+ "min-Latn-ID": "min",
+ "mis-Hatr-IQ": "mis",
+ "mis-Medf-NG": "mis-Medf",
+ "miw-Latn-ZZ": "miw",
+ "mk-Cyrl-AL": "mk-AL",
+ "mk-Cyrl-GR": "mk-GR",
+ "mk-Cyrl-MK": "mk",
+ "mki-Arab-ZZ": "mki",
+ "mkl-Latn-ZZ": "mkl",
+ "mkp-Latn-ZZ": "mkp",
+ "mkw-Latn-ZZ": "mkw",
+ "ml-Mlym-IN": "ml",
+ "mle-Latn-ZZ": "mle",
+ "mlp-Latn-ZZ": "mlp",
+ "mls-Latn-SD": "mls",
+ "mmo-Latn-ZZ": "mmo",
+ "mmu-Latn-ZZ": "mmu",
+ "mmx-Latn-ZZ": "mmx",
+ "mn-Cyrl-MN": "mn",
+ "mn-Mong-CN": "mn-CN",
+ "mna-Latn-ZZ": "mna",
+ "mnf-Latn-ZZ": "mnf",
+ "mni-Beng-IN": "mni",
+ "mni-Mtei-IN": "mni-Mtei",
+ "mnw-Mymr-MM": "mnw",
+ "mnw-Mymr-TH": "mnw-TH",
+ "moa-Latn-ZZ": "moa",
+ "moe-Latn-CA": "moe",
+ "moh-Latn-CA": "moh",
+ "mos-Latn-BF": "mos",
+ "mox-Latn-ZZ": "mox",
+ "mpp-Latn-ZZ": "mpp",
+ "mps-Latn-ZZ": "mps",
+ "mpt-Latn-ZZ": "mpt",
+ "mpx-Latn-ZZ": "mpx",
+ "mql-Latn-ZZ": "mql",
+ "mr-Deva-IN": "mr",
+ "mr-Modi-IN": "mr-Modi",
+ "mrd-Deva-NP": "mrd",
+ "mrj-Cyrl-RU": "mrj",
+ "mro-Mroo-BD": "mro",
+ "ms-Arab-CC": "ms-CC",
+ "ms-Arab-ID": "ms-Arab-ID",
+ "ms-Latn-BN": "ms-BN",
+ "ms-Latn-ID": "ms-ID",
+ "ms-Latn-MY": "ms",
+ "mt-Latn-MT": "mt",
+ "mtc-Latn-ZZ": "mtc",
+ "mtf-Latn-ZZ": "mtf",
+ "mti-Latn-ZZ": "mti",
+ "mtr-Deva-IN": "mtr",
+ "mua-Latn-CM": "mua",
+ "mur-Latn-ZZ": "mur",
+ "mus-Latn-US": "mus",
+ "mva-Latn-ZZ": "mva",
+ "mvn-Latn-ZZ": "mvn",
+ "mvy-Arab-PK": "mvy",
+ "mwk-Latn-ML": "mwk",
+ "mwr-Deva-IN": "mwr",
+ "mwv-Latn-ID": "mwv",
+ "mww-Hmnp-US": "mww",
+ "mxc-Latn-ZW": "mxc",
+ "mxm-Latn-ZZ": "mxm",
+ "my-Mymr-MM": "my",
+ "myk-Latn-ZZ": "myk",
+ "mym-Ethi-ZZ": "mym",
+ "myv-Cyrl-RU": "myv",
+ "myw-Latn-ZZ": "myw",
+ "myx-Latn-UG": "myx",
+ "myz-Mand-IR": "myz",
+ "mzk-Latn-ZZ": "mzk",
+ "mzm-Latn-ZZ": "mzm",
+ "mzn-Arab-IR": "mzn",
+ "mzp-Latn-ZZ": "mzp",
+ "mzw-Latn-ZZ": "mzw",
+ "mzz-Latn-ZZ": "mzz",
+ "na-Latn-NR": "na",
+ "nac-Latn-ZZ": "nac",
+ "naf-Latn-ZZ": "naf",
+ "nak-Latn-ZZ": "nak",
+ "nan-Hans-CN": "nan",
+ "nap-Latn-IT": "nap",
+ "naq-Latn-NA": "naq",
+ "nas-Latn-ZZ": "nas",
+ "nb-Latn-NO": "nb",
+ "nb-Latn-SJ": "nb-SJ",
+ "nca-Latn-ZZ": "nca",
+ "nce-Latn-ZZ": "nce",
+ "ncf-Latn-ZZ": "ncf",
+ "nch-Latn-MX": "nch",
+ "nco-Latn-ZZ": "nco",
+ "ncu-Latn-ZZ": "ncu",
+ "nd-Latn-ZW": "nd",
+ "ndc-Latn-MZ": "ndc",
+ "nds-Latn-DE": "nds",
+ "ne-Deva-BT": "ne-BT",
+ "ne-Deva-NP": "ne",
+ "neb-Latn-ZZ": "neb",
+ "new-Deva-NP": "new",
+ "new-Newa-NP": "new-Newa",
+ "nex-Latn-ZZ": "nex",
+ "nfr-Latn-ZZ": "nfr",
+ "ng-Latn-NA": "ng",
+ "nga-Latn-ZZ": "nga",
+ "ngb-Latn-ZZ": "ngb",
+ "ngl-Latn-MZ": "ngl",
+ "nhb-Latn-ZZ": "nhb",
+ "nhe-Latn-MX": "nhe",
+ "nhw-Latn-MX": "nhw",
+ "nif-Latn-ZZ": "nif",
+ "nii-Latn-ZZ": "nii",
+ "nij-Latn-ID": "nij",
+ "nin-Latn-ZZ": "nin",
+ "niu-Latn-NU": "niu",
+ "niy-Latn-ZZ": "niy",
+ "niz-Latn-ZZ": "niz",
+ "njo-Latn-IN": "njo",
+ "nkg-Latn-ZZ": "nkg",
+ "nko-Latn-ZZ": "nko",
+ "nl-Latn-AW": "nl-AW",
+ "nl-Latn-BE": "nl-BE",
+ "nl-Latn-NL": "nl",
+ "nl-Latn-SR": "nl-SR",
+ "nmg-Latn-CM": "nmg",
+ "nmz-Latn-ZZ": "nmz",
+ "nn-Latn-NO": "nn",
+ "nnf-Latn-ZZ": "nnf",
+ "nnh-Latn-CM": "nnh",
+ "nnk-Latn-ZZ": "nnk",
+ "nnm-Latn-ZZ": "nnm",
+ "nnp-Wcho-IN": "nnp",
+ "nod-Lana-TH": "nod",
+ "noe-Deva-IN": "noe",
+ "non-Runr-SE": "non",
+ "nop-Latn-ZZ": "nop",
+ "nou-Latn-ZZ": "nou",
+ "nqo-Nkoo-GN": "nqo",
+ "nr-Latn-ZA": "nr",
+ "nrb-Latn-ZZ": "nrb",
+ "nsk-Cans-CA": "nsk",
+ "nsn-Latn-ZZ": "nsn",
+ "nso-Latn-ZA": "nso",
+ "nss-Latn-ZZ": "nss",
+ "ntm-Latn-ZZ": "ntm",
+ "ntr-Latn-ZZ": "ntr",
+ "nui-Latn-ZZ": "nui",
+ "nup-Latn-ZZ": "nup",
+ "nus-Latn-SS": "nus",
+ "nuv-Latn-ZZ": "nuv",
+ "nux-Latn-ZZ": "nux",
+ "nv-Latn-US": "nv",
+ "nwb-Latn-ZZ": "nwb",
+ "nxq-Latn-CN": "nxq",
+ "nxr-Latn-ZZ": "nxr",
+ "ny-Latn-MW": "ny",
+ "nym-Latn-TZ": "nym",
+ "nyn-Latn-UG": "nyn",
+ "nzi-Latn-GH": "nzi",
+ "oc-Latn-FR": "oc",
+ "ogc-Latn-ZZ": "ogc",
+ "okr-Latn-ZZ": "okr",
+ "okv-Latn-ZZ": "okv",
+ "om-Latn-ET": "om",
+ "ong-Latn-ZZ": "ong",
+ "onn-Latn-ZZ": "onn",
+ "ons-Latn-ZZ": "ons",
+ "opm-Latn-ZZ": "opm",
+ "or-Orya-IN": "or",
+ "oro-Latn-ZZ": "oro",
+ "oru-Arab-ZZ": "oru",
+ "os-Cyrl-GE": "os",
+ "osa-Osge-US": "osa",
+ "ota-Arab-ZZ": "ota",
+ "otk-Orkh-MN": "otk",
+ "ozm-Latn-ZZ": "ozm",
+ "pa-Arab-PK": "pa-PK",
+ "pa-Guru-IN": "pa",
+ "pag-Latn-PH": "pag",
+ "pal-Phli-IR": "pal",
+ "pal-Phlp-CN": "pal-Phlp",
+ "pam-Latn-PH": "pam",
+ "pap-Latn-AW": "pap",
+ "pap-Latn-BQ": "pap-BQ",
+ "pap-Latn-CW": "pap-CW",
+ "pau-Latn-PW": "pau",
+ "pbi-Latn-ZZ": "pbi",
+ "pcd-Latn-FR": "pcd",
+ "pcm-Latn-NG": "pcm",
+ "pdc-Latn-US": "pdc",
+ "pdt-Latn-CA": "pdt",
+ "ped-Latn-ZZ": "ped",
+ "peo-Xpeo-IR": "peo",
+ "pex-Latn-ZZ": "pex",
+ "pfl-Latn-DE": "pfl",
+ "phl-Arab-ZZ": "phl",
+ "phn-Phnx-LB": "phn",
+ "pil-Latn-ZZ": "pil",
+ "pip-Latn-ZZ": "pip",
+ "pka-Brah-IN": "pka",
+ "pko-Latn-KE": "pko",
+ "pl-Latn-PL": "pl",
+ "pl-Latn-UA": "pl-UA",
+ "pla-Latn-ZZ": "pla",
+ "pms-Latn-IT": "pms",
+ "png-Latn-ZZ": "png",
+ "pnn-Latn-ZZ": "pnn",
+ "pnt-Grek-GR": "pnt",
+ "pon-Latn-FM": "pon",
+ "ppo-Latn-ZZ": "ppo",
+ "pra-Khar-PK": "pra",
+ "prd-Arab-IR": "prd",
+ "prg-Latn-001": "prg",
+ "ps-Arab-AF": "ps",
+ "pss-Latn-ZZ": "pss",
+ "pt-Latn-AO": "pt-AO",
+ "pt-Latn-BR": "pt",
+ "pt-Latn-CV": "pt-CV",
+ "pt-Latn-GW": "pt-GW",
+ "pt-Latn-MO": "pt-MO",
+ "pt-Latn-MZ": "pt-MZ",
+ "pt-Latn-PT": "pt-PT",
+ "pt-Latn-ST": "pt-ST",
+ "pt-Latn-TL": "pt-TL",
+ "ptp-Latn-ZZ": "ptp",
+ "puu-Latn-GA": "puu",
+ "pwa-Latn-ZZ": "pwa",
+ "qu-Latn-PE": "qu",
+ "quc-Latn-GT": "quc",
+ "qug-Latn-EC": "qug",
+ "rai-Latn-ZZ": "rai",
+ "raj-Deva-IN": "raj",
+ "rao-Latn-ZZ": "rao",
+ "rcf-Latn-RE": "rcf",
+ "rej-Latn-ID": "rej",
+ "rej-Rjng-ID": "rej-Rjng",
+ "rel-Latn-ZZ": "rel",
+ "res-Latn-ZZ": "res",
+ "rgn-Latn-IT": "rgn",
+ "rhg-Arab-MM": "rhg",
+ "rhg-Rohg-MM": "rhg-Rohg",
+ "ria-Latn-IN": "ria",
+ "rif-Latn-NL": "rif-NL",
+ "rif-Tfng-MA": "rif",
+ "rjs-Deva-NP": "rjs",
+ "rkt-Beng-BD": "rkt",
+ "rm-Latn-CH": "rm",
+ "rmf-Latn-FI": "rmf",
+ "rmo-Latn-CH": "rmo",
+ "rmt-Arab-IR": "rmt",
+ "rmu-Latn-SE": "rmu",
+ "rn-Latn-BI": "rn",
+ "rna-Latn-ZZ": "rna",
+ "rng-Latn-MZ": "rng",
+ "ro-Latn-MD": "ro-MD",
+ "ro-Latn-RO": "ro",
+ "rob-Latn-ID": "rob",
+ "rof-Latn-TZ": "rof",
+ "roo-Latn-ZZ": "roo",
+ "rro-Latn-ZZ": "rro",
+ "rtm-Latn-FJ": "rtm",
+ "ru-Cyrl-KZ": "ru-KZ",
+ "ru-Cyrl-RU": "ru",
+ "rue-Cyrl-UA": "rue",
+ "rug-Latn-SB": "rug",
+ "rw-Latn-RW": "rw",
+ "rwk-Latn-TZ": "rwk",
+ "rwo-Latn-ZZ": "rwo",
+ "ryu-Kana-JP": "ryu",
+ "sa-Bhks-IN": "sa-Bhks",
+ "sa-Deva-IN": "sa",
+ "sa-Gran-IN": "sa-Gran",
+ "sa-Nand-IN": "sa-Nand",
+ "sa-Shrd-IN": "sa-Shrd",
+ "sa-Sidd-IN": "sa-Sidd",
+ "saf-Latn-GH": "saf",
+ "sah-Cyrl-RU": "sah",
+ "saq-Latn-KE": "saq",
+ "sas-Latn-ID": "sas",
+ "sat-Olck-IN": "sat",
+ "sav-Latn-SN": "sav",
+ "saz-Saur-IN": "saz",
+ "sba-Latn-ZZ": "sba",
+ "sbe-Latn-ZZ": "sbe",
+ "sbp-Latn-TZ": "sbp",
+ "sc-Latn-IT": "sc",
+ "sck-Deva-IN": "sck",
+ "scl-Arab-ZZ": "scl",
+ "scn-Latn-IT": "scn",
+ "sco-Latn-GB": "sco",
+ "scs-Latn-CA": "scs",
+ "sd-Arab-PK": "sd",
+ "sd-Deva-IN": "sd-Deva",
+ "sd-Khoj-IN": "sd-Khoj",
+ "sd-Sind-IN": "sd-Sind",
+ "sdc-Latn-IT": "sdc",
+ "sdh-Arab-IR": "sdh",
+ "se-Latn-NO": "se",
+ "sef-Latn-CI": "sef",
+ "seh-Latn-MZ": "seh",
+ "sei-Latn-MX": "sei",
+ "ses-Latn-ML": "ses",
+ "sg-Latn-CF": "sg",
+ "sga-Ogam-IE": "sga",
+ "sgs-Latn-LT": "sgs",
+ "sgw-Ethi-ZZ": "sgw",
+ "sgz-Latn-ZZ": "sgz",
+ "shi-Tfng-MA": "shi",
+ "shk-Latn-ZZ": "shk",
+ "shn-Mymr-MM": "shn",
+ "shu-Arab-ZZ": "shu",
+ "si-Sinh-LK": "si",
+ "sid-Latn-ET": "sid",
+ "sig-Latn-ZZ": "sig",
+ "sil-Latn-ZZ": "sil",
+ "sim-Latn-ZZ": "sim",
+ "sjr-Latn-ZZ": "sjr",
+ "sk-Latn-SK": "sk",
+ "skc-Latn-ZZ": "skc",
+ "skr-Arab-PK": "skr",
+ "skr-Mult-PK": "skr-Mult",
+ "sks-Latn-ZZ": "sks",
+ "sl-Latn-SI": "sl",
+ "sld-Latn-ZZ": "sld",
+ "sli-Latn-PL": "sli",
+ "sll-Latn-ZZ": "sll",
+ "sly-Latn-ID": "sly",
+ "sm-Latn-AS": "sm-AS",
+ "sm-Latn-WS": "sm",
+ "sma-Latn-SE": "sma",
+ "smj-Latn-SE": "smj",
+ "smn-Latn-FI": "smn",
+ "smp-Samr-IL": "smp",
+ "smq-Latn-ZZ": "smq",
+ "sms-Latn-FI": "sms",
+ "sn-Latn-ZW": "sn",
+ "snc-Latn-ZZ": "snc",
+ "snk-Latn-ML": "snk",
+ "snp-Latn-ZZ": "snp",
+ "snx-Latn-ZZ": "snx",
+ "sny-Latn-ZZ": "sny",
+ "so-Latn-SO": "so",
+ "so-Osma-SO": "so-Osma",
+ "sog-Sogd-UZ": "sog",
+ "sog-Sogo-UZ": "sog-Sogo",
+ "sok-Latn-ZZ": "sok",
+ "soq-Latn-ZZ": "soq",
+ "sou-Thai-TH": "sou",
+ "soy-Latn-ZZ": "soy",
+ "spd-Latn-ZZ": "spd",
+ "spl-Latn-ZZ": "spl",
+ "sps-Latn-ZZ": "sps",
+ "sq-Elba-AL": "sq-Elba",
+ "sq-Latn-AL": "sq",
+ "sq-Latn-MK": "sq-MK",
+ "sq-Latn-XK": "sq-XK",
+ "sr-Cyrl-BA": "sr-BA",
+ "sr-Cyrl-RS": "sr",
+ "sr-Cyrl-XK": "sr-XK",
+ "sr-Latn-ME": "sr-ME",
+ "sr-Latn-RO": "sr-RO",
+ "sr-Latn-RU": "sr-RU",
+ "sr-Latn-TR": "sr-TR",
+ "srb-Sora-IN": "srb",
+ "srn-Latn-SR": "srn",
+ "srr-Latn-SN": "srr",
+ "srx-Deva-IN": "srx",
+ "ss-Latn-ZA": "ss",
+ "ssd-Latn-ZZ": "ssd",
+ "ssg-Latn-ZZ": "ssg",
+ "ssy-Latn-ER": "ssy",
+ "st-Latn-LS": "st-LS",
+ "st-Latn-ZA": "st",
+ "stk-Latn-ZZ": "stk",
+ "stq-Latn-DE": "stq",
+ "su-Latn-ID": "su",
+ "su-Sund-ID": "su-Sund",
+ "sua-Latn-ZZ": "sua",
+ "sue-Latn-ZZ": "sue",
+ "suk-Latn-TZ": "suk",
+ "sur-Latn-ZZ": "sur",
+ "sus-Latn-GN": "sus",
+ "sv-Latn-AX": "sv-AX",
+ "sv-Latn-SE": "sv",
+ "sw-Latn-CD": "sw-CD",
+ "sw-Latn-KE": "sw-KE",
+ "sw-Latn-TZ": "sw",
+ "sw-Latn-UG": "sw-UG",
+ "swb-Arab-YT": "swb",
+ "swg-Latn-DE": "swg",
+ "swp-Latn-ZZ": "swp",
+ "swv-Deva-IN": "swv",
+ "sxn-Latn-ID": "sxn",
+ "sxw-Latn-ZZ": "sxw",
+ "syl-Beng-BD": "syl",
+ "syl-Sylo-BD": "syl-Sylo",
+ "syr-Syrc-IQ": "syr",
+ "szl-Latn-PL": "szl",
+ "ta-Taml-IN": "ta",
+ "taj-Deva-NP": "taj",
+ "tal-Latn-ZZ": "tal",
+ "tan-Latn-ZZ": "tan",
+ "taq-Latn-ZZ": "taq",
+ "tbc-Latn-ZZ": "tbc",
+ "tbd-Latn-ZZ": "tbd",
+ "tbf-Latn-ZZ": "tbf",
+ "tbg-Latn-ZZ": "tbg",
+ "tbo-Latn-ZZ": "tbo",
+ "tbw-Latn-PH": "tbw",
+ "tbw-Tagb-PH": "tbw-Tagb",
+ "tbz-Latn-ZZ": "tbz",
+ "tci-Latn-ZZ": "tci",
+ "tcy-Knda-IN": "tcy",
+ "tdd-Tale-CN": "tdd",
+ "tdg-Deva-NP": "tdg",
+ "tdh-Deva-NP": "tdh",
+ "te-Telu-IN": "te",
+ "ted-Latn-ZZ": "ted",
+ "tem-Latn-SL": "tem",
+ "teo-Latn-UG": "teo",
+ "tet-Latn-TL": "tet",
+ "tfi-Latn-ZZ": "tfi",
+ "tg-Arab-PK": "tg-PK",
+ "tg-Cyrl-TJ": "tg",
+ "tgc-Latn-ZZ": "tgc",
+ "tgo-Latn-ZZ": "tgo",
+ "tgu-Latn-ZZ": "tgu",
+ "th-Thai-TH": "th",
+ "thl-Deva-NP": "thl",
+ "thq-Deva-NP": "thq",
+ "thr-Deva-NP": "thr",
+ "ti-Ethi-ER": "ti-ER",
+ "ti-Ethi-ET": "ti",
+ "tif-Latn-ZZ": "tif",
+ "tig-Ethi-ER": "tig",
+ "tik-Latn-ZZ": "tik",
+ "tim-Latn-ZZ": "tim",
+ "tio-Latn-ZZ": "tio",
+ "tiv-Latn-NG": "tiv",
+ "tk-Latn-AF": "tk-AF",
+ "tk-Latn-IR": "tk-IR",
+ "tk-Latn-TM": "tk",
+ "tkl-Latn-TK": "tkl",
+ "tkr-Latn-AZ": "tkr",
+ "tkt-Deva-NP": "tkt",
+ "tlf-Latn-ZZ": "tlf",
+ "tlx-Latn-ZZ": "tlx",
+ "tly-Latn-AZ": "tly",
+ "tmh-Latn-NE": "tmh",
+ "tmy-Latn-ZZ": "tmy",
+ "tn-Latn-ZA": "tn",
+ "tnh-Latn-ZZ": "tnh",
+ "to-Latn-TO": "to",
+ "tof-Latn-ZZ": "tof",
+ "tog-Latn-MW": "tog",
+ "toq-Latn-ZZ": "toq",
+ "tpi-Latn-PG": "tpi",
+ "tpm-Latn-ZZ": "tpm",
+ "tpz-Latn-ZZ": "tpz",
+ "tqo-Latn-ZZ": "tqo",
+ "tr-Latn-CY": "tr-CY",
+ "tr-Latn-TR": "tr",
+ "tru-Latn-TR": "tru",
+ "trv-Latn-TW": "trv",
+ "trw-Arab-ZZ": "trw",
+ "ts-Latn-ZA": "ts",
+ "tsd-Grek-GR": "tsd",
+ "tsg-Latn-PH": "tsg",
+ "tsj-Tibt-BT": "tsj",
+ "tsw-Latn-ZZ": "tsw",
+ "tt-Cyrl-RU": "tt",
+ "ttd-Latn-ZZ": "ttd",
+ "tte-Latn-ZZ": "tte",
+ "ttj-Latn-UG": "ttj",
+ "ttr-Latn-ZZ": "ttr",
+ "tts-Thai-TH": "tts",
+ "ttt-Latn-AZ": "ttt",
+ "tuh-Latn-ZZ": "tuh",
+ "tul-Latn-ZZ": "tul",
+ "tum-Latn-MW": "tum",
+ "tuq-Latn-ZZ": "tuq",
+ "tvd-Latn-ZZ": "tvd",
+ "tvl-Latn-TV": "tvl",
+ "tvu-Latn-ZZ": "tvu",
+ "twh-Latn-ZZ": "twh",
+ "twq-Latn-NE": "twq",
+ "txg-Tang-CN": "txg",
+ "ty-Latn-PF": "ty",
+ "tya-Latn-ZZ": "tya",
+ "tyv-Cyrl-RU": "tyv",
+ "tzm-Latn-MA": "tzm",
+ "ubu-Latn-ZZ": "ubu",
+ "udm-Cyrl-RU": "udm",
+ "ug-Arab-CN": "ug",
+ "ug-Cyrl-KZ": "ug-KZ",
+ "ug-Cyrl-MN": "ug-MN",
+ "uga-Ugar-SY": "uga",
+ "uk-Cyrl-MD": "uk-MD",
+ "uk-Cyrl-SK": "uk-SK",
+ "uk-Cyrl-UA": "uk",
+ "uli-Latn-FM": "uli",
+ "umb-Latn-AO": "umb",
+ "und-Latn-AQ": "und-AQ",
+ "und-Latn-BV": "und-BV",
+ "und-Latn-CP": "und-CP",
+ "und-Latn-GS": "und-GS",
+ "und-Latn-HM": "und-HM",
+ "unr-Beng-IN": "unr",
+ "unr-Deva-NP": "unr-NP",
+ "unx-Beng-IN": "unx",
+ "ur-Arab-IN": "ur-IN",
+ "ur-Arab-MU": "ur-MU",
+ "ur-Arab-PK": "ur",
+ "uri-Latn-ZZ": "uri",
+ "urt-Latn-ZZ": "urt",
+ "urw-Latn-ZZ": "urw",
+ "usa-Latn-ZZ": "usa",
+ "utr-Latn-ZZ": "utr",
+ "uvh-Latn-ZZ": "uvh",
+ "uvl-Latn-ZZ": "uvl",
+ "uz-Arab-AF": "uz-AF",
+ "uz-Cyrl-CN": "uz-CN",
+ "uz-Latn-UZ": "uz",
+ "vag-Latn-ZZ": "vag",
+ "vai-Vaii-LR": "vai",
+ "van-Latn-ZZ": "van",
+ "ve-Latn-ZA": "ve",
+ "vec-Latn-IT": "vec",
+ "vep-Latn-RU": "vep",
+ "vi-Latn-VN": "vi",
+ "vic-Latn-SX": "vic",
+ "viv-Latn-ZZ": "viv",
+ "vls-Latn-BE": "vls",
+ "vmf-Latn-DE": "vmf",
+ "vmw-Latn-MZ": "vmw",
+ "vo-Latn-001": "vo",
+ "vot-Latn-RU": "vot",
+ "vro-Latn-EE": "vro",
+ "vun-Latn-TZ": "vun",
+ "vut-Latn-ZZ": "vut",
+ "wa-Latn-BE": "wa",
+ "wae-Latn-CH": "wae",
+ "waj-Latn-ZZ": "waj",
+ "wal-Ethi-ET": "wal",
+ "wan-Latn-ZZ": "wan",
+ "war-Latn-PH": "war",
+ "wbp-Latn-AU": "wbp",
+ "wbq-Telu-IN": "wbq",
+ "wbr-Deva-IN": "wbr",
+ "wci-Latn-ZZ": "wci",
+ "wer-Latn-ZZ": "wer",
+ "wgi-Latn-ZZ": "wgi",
+ "whg-Latn-ZZ": "whg",
+ "wib-Latn-ZZ": "wib",
+ "wiu-Latn-ZZ": "wiu",
+ "wiv-Latn-ZZ": "wiv",
+ "wja-Latn-ZZ": "wja",
+ "wji-Latn-ZZ": "wji",
+ "wls-Latn-WF": "wls",
+ "wmo-Latn-ZZ": "wmo",
+ "wnc-Latn-ZZ": "wnc",
+ "wni-Arab-KM": "wni",
+ "wnu-Latn-ZZ": "wnu",
+ "wo-Latn-SN": "wo",
+ "wob-Latn-ZZ": "wob",
+ "wos-Latn-ZZ": "wos",
+ "wrs-Latn-ZZ": "wrs",
+ "wsg-Gong-IN": "wsg",
+ "wsk-Latn-ZZ": "wsk",
+ "wtm-Deva-IN": "wtm",
+ "wuu-Hans-CN": "wuu",
+ "wuv-Latn-ZZ": "wuv",
+ "wwa-Latn-ZZ": "wwa",
+ "xav-Latn-BR": "xav",
+ "xbi-Latn-ZZ": "xbi",
+ "xco-Chrs-UZ": "xco",
+ "xcr-Cari-TR": "xcr",
+ "xes-Latn-ZZ": "xes",
+ "xh-Latn-ZA": "xh",
+ "xla-Latn-ZZ": "xla",
+ "xlc-Lyci-TR": "xlc",
+ "xld-Lydi-TR": "xld",
+ "xmf-Geor-GE": "xmf",
+ "xmn-Mani-CN": "xmn",
+ "xmr-Merc-SD": "xmr",
+ "xmr-Mero-SD": "xmr-Mero",
+ "xna-Narb-SA": "xna",
+ "xnr-Deva-IN": "xnr",
+ "xog-Latn-UG": "xog",
+ "xon-Latn-ZZ": "xon",
+ "xpr-Prti-IR": "xpr",
+ "xrb-Latn-ZZ": "xrb",
+ "xsa-Sarb-YE": "xsa",
+ "xsi-Latn-ZZ": "xsi",
+ "xsm-Latn-ZZ": "xsm",
+ "xsr-Deva-NP": "xsr",
+ "xwe-Latn-ZZ": "xwe",
+ "yam-Latn-ZZ": "yam",
+ "yao-Latn-MZ": "yao",
+ "yap-Latn-FM": "yap",
+ "yas-Latn-ZZ": "yas",
+ "yat-Latn-ZZ": "yat",
+ "yav-Latn-CM": "yav",
+ "yay-Latn-ZZ": "yay",
+ "yaz-Latn-ZZ": "yaz",
+ "yba-Latn-ZZ": "yba",
+ "ybb-Latn-CM": "ybb",
+ "yby-Latn-ZZ": "yby",
+ "yer-Latn-ZZ": "yer",
+ "ygr-Latn-ZZ": "ygr",
+ "ygw-Latn-ZZ": "ygw",
+ "yi-Hebr-001": "yi",
+ "yi-Hebr-CA": "yi-CA",
+ "yi-Hebr-GB": "yi-GB",
+ "yi-Hebr-SE": "yi-SE",
+ "yi-Hebr-UA": "yi-UA",
+ "yi-Hebr-US": "yi-US",
+ "yko-Latn-ZZ": "yko",
+ "yle-Latn-ZZ": "yle",
+ "ylg-Latn-ZZ": "ylg",
+ "yll-Latn-ZZ": "yll",
+ "yml-Latn-ZZ": "yml",
+ "yo-Latn-NG": "yo",
+ "yon-Latn-ZZ": "yon",
+ "yrb-Latn-ZZ": "yrb",
+ "yre-Latn-ZZ": "yre",
+ "yrl-Latn-BR": "yrl",
+ "yss-Latn-ZZ": "yss",
+ "yua-Latn-MX": "yua",
+ "yue-Hans-CN": "yue-CN",
+ "yue-Hant-HK": "yue",
+ "yuj-Latn-ZZ": "yuj",
+ "yut-Latn-ZZ": "yut",
+ "yuw-Latn-ZZ": "yuw",
+ "za-Latn-CN": "za",
+ "zag-Latn-SD": "zag",
+ "zdj-Arab-KM": "zdj",
+ "zea-Latn-NL": "zea",
+ "zgh-Tfng-MA": "zgh",
+ "zh-Bopo-TW": "zh-Bopo",
+ "zh-Hanb-TW": "zh-Hanb",
+ "zh-Hani-CN": "zh-Hani",
+ "zh-Hans-CN": "zh",
+ "zh-Hant-AU": "zh-AU",
+ "zh-Hant-BN": "zh-BN",
+ "zh-Hant-GB": "zh-GB",
+ "zh-Hant-GF": "zh-GF",
+ "zh-Hant-HK": "zh-HK",
+ "zh-Hant-ID": "zh-ID",
+ "zh-Hant-MO": "zh-MO",
+ "zh-Hant-MY": "zh-MY",
+ "zh-Hant-PA": "zh-PA",
+ "zh-Hant-PF": "zh-PF",
+ "zh-Hant-PH": "zh-PH",
+ "zh-Hant-SR": "zh-SR",
+ "zh-Hant-TH": "zh-TH",
+ "zh-Hant-TW": "zh-TW",
+ "zh-Hant-US": "zh-US",
+ "zh-Hant-VN": "zh-VN",
+ "zhx-Nshu-CN": "zhx",
+ "zia-Latn-ZZ": "zia",
+ "zkt-Kits-CN": "zkt",
+ "zlm-Latn-TG": "zlm",
+ "zmi-Latn-MY": "zmi",
+ "zne-Latn-ZZ": "zne",
+ "zu-Latn-ZA": "zu",
+ "zza-Latn-TR": "zza",
+};
+
+for (let [tag, maximal] of Object.entries(maxLikelySubtags)) {
+ assertEq(new Intl.Locale(tag).maximize().toString(), maximal);
+}
+
+for (let [tag, minimal] of Object.entries(minLikelySubtags)) {
+ assertEq(new Intl.Locale(tag).minimize().toString(), minimal);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/Locale/likely-subtags.js b/js/src/tests/non262/Intl/Locale/likely-subtags.js
new file mode 100644
index 0000000000..73f00475d5
--- /dev/null
+++ b/js/src/tests/non262/Intl/Locale/likely-subtags.js
@@ -0,0 +1,61 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl'))
+
+var testDataMaximal = {
+ // Keeps "und" primary language.
+ "und-AQ": "und-Latn-AQ",
+
+ // Modifies primary language.
+ "und-Cyrl-RO": "bg-Cyrl-RO",
+}
+
+var testDataMinimal = {
+ // Undefined primary language.
+ "und": "en",
+ "und-Thai": "th",
+ "und-419": "es-419",
+ "und-150": "ru",
+ "und-AT": "de-AT",
+
+ // https://ssl.icu-project.org/trac/ticket/13786
+ "aae-Latn-IT": "aae-Latn-IT",
+ "aae-Thai-CO": "aae-Thai-CO",
+
+ // https://ssl.icu-project.org/trac/ticket/10220
+ // https://ssl.icu-project.org/trac/ticket/12345
+ "und-CW": "pap-CW",
+ "und-US": "en",
+ "zh-Hant": "zh-TW",
+ "zh-Hani": "zh-Hani",
+};
+
+// Add variants, extensions, and privateuse subtags and ensure they don't
+// modify the result of the likely subtags algorithms.
+var extras = [
+ "fonipa",
+ "a-not-assigned",
+ "u-attr",
+ "u-co",
+ "u-co-phonebk",
+ "x-private",
+];
+
+for (var [tag, maximal] of Object.entries(testDataMaximal)) {
+ assertEq(new Intl.Locale(tag).maximize().toString(), maximal);
+ assertEq(new Intl.Locale(maximal).maximize().toString(), maximal);
+
+ for (var extra of extras) {
+ assertEq(new Intl.Locale(tag + "-" + extra).maximize().toString(), maximal + "-" + extra);
+ }
+}
+
+for (var [tag, minimal] of Object.entries(testDataMinimal)) {
+ assertEq(new Intl.Locale(tag).minimize().toString(), minimal);
+ assertEq(new Intl.Locale(minimal).minimize().toString(), minimal);
+
+ for (var extra of extras) {
+ assertEq(new Intl.Locale(tag + "-" + extra).minimize().toString(), minimal + "-" + extra);
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/Locale/same-compartment.js b/js/src/tests/non262/Intl/Locale/same-compartment.js
new file mode 100644
index 0000000000..fb71c85f6c
--- /dev/null
+++ b/js/src/tests/non262/Intl/Locale/same-compartment.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl')||!this.wrapWithProto)
+
+var tag = "de-Latn-AT-u-ca-gregory-nu-latn-co-phonebk-kf-false-kn-hc-h23";
+var locale = new Intl.Locale(tag);
+var scwLocale = wrapWithProto(locale, Intl.Locale.prototype);
+
+for (var [key, {get, value = get}] of Object.entries(Object.getOwnPropertyDescriptors(Intl.Locale.prototype))) {
+ if (typeof value === "function") {
+ if (key !== "constructor") {
+ var expectedValue = value.call(locale);
+
+ if (typeof expectedValue === "string" || typeof expectedValue === "boolean") {
+ assertEq(value.call(scwLocale), expectedValue, key);
+ } else if (expectedValue instanceof Intl.Locale) {
+ assertEq(value.call(scwLocale).toString(), expectedValue.toString(), key);
+ } else {
+ throw new Error("unexpected result value");
+ }
+ } else {
+ assertEq(new value(scwLocale).toString(), new value(locale).toString(), key);
+ }
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/Locale/shell.js b/js/src/tests/non262/Intl/Locale/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Intl/Locale/shell.js
diff --git a/js/src/tests/non262/Intl/Locale/surface.js b/js/src/tests/non262/Intl/Locale/surface.js
new file mode 100644
index 0000000000..ba90182436
--- /dev/null
+++ b/js/src/tests/non262/Intl/Locale/surface.js
@@ -0,0 +1,98 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl'))
+
+function assertProperty(object, name, desc) {
+ assertEq(desc === undefined || (typeof desc === "object" && desc !== null), true,
+ "desc is a property descriptor");
+
+ var actual = Object.getOwnPropertyDescriptor(object, name);
+ if (desc === undefined) {
+ assertEq(actual, desc, `property ${String(name)} is absent`);
+ return;
+ }
+ assertEq(actual !== undefined, true, `property ${String(name)} is present`);
+
+ var fields = ["value", "writable", "enumerable", "configurable", "get", "set"];
+ for (var field of fields) {
+ if (Object.prototype.hasOwnProperty.call(desc, field)) {
+ assertEq(actual[field], desc[field], `unexpected value for ${field}`);
+ }
+ }
+}
+
+function assertBuiltinFunction(fn, length, name) {
+ assertProperty(fn, "length", {
+ value: length, writable: false, enumerable: false, configurable: true,
+ });
+}
+
+function assertBuiltinMethod(object, propName, length, name) {
+ var desc = Object.getOwnPropertyDescriptor(object, propName);
+ assertProperty(object, propName, {
+ value: desc.value, writable: true, enumerable: false, configurable: true
+ });
+ assertBuiltinFunction(desc.value, length, name);
+}
+
+function assertBuiltinGetter(object, propName, length, name) {
+ var desc = Object.getOwnPropertyDescriptor(object, propName);
+
+ assertBuiltinFunction(desc.get, length, name);
+}
+
+// Intl.Locale( tag[, options] )
+assertBuiltinFunction(Intl.Locale, 1, "Locale");
+
+// Properties of the Intl.Locale Constructor
+
+// Intl.Locale.prototype
+assertProperty(Intl.Locale, "prototype", {
+ value: Intl.Locale.prototype, writable: false, enumerable: false, configurable: false,
+});
+
+// Properties of the Intl.Locale Prototype Object
+
+// Intl.Locale.prototype.constructor
+assertProperty(Intl.Locale.prototype, "constructor", {
+ value: Intl.Locale, writable: true, enumerable: false, configurable: true,
+});
+
+// Intl.Locale.prototype[ @@toStringTag ]
+assertProperty(Intl.Locale.prototype, Symbol.toStringTag, {
+ value: "Intl.Locale", writable: false, enumerable: false, configurable: true,
+});
+
+// Intl.Locale.prototype.toString ()
+assertBuiltinMethod(Intl.Locale.prototype, "toString", 0, "toString");
+
+// get Intl.Locale.prototype.baseName
+assertBuiltinGetter(Intl.Locale.prototype, "baseName", 0, "get baseName");
+
+// get Intl.Locale.prototype.calendar
+assertBuiltinGetter(Intl.Locale.prototype, "calendar", 0, "get calendar");
+
+// get Intl.Locale.prototype.collation
+assertBuiltinGetter(Intl.Locale.prototype, "collation", 0, "get collation");
+
+// get Intl.Locale.prototype.hourCycle
+assertBuiltinGetter(Intl.Locale.prototype, "hourCycle", 0, "get hourCycle");
+
+// get Intl.Locale.prototype.caseFirst
+assertBuiltinGetter(Intl.Locale.prototype, "caseFirst", 0, "get caseFirst");
+
+// get Intl.Locale.prototype.numeric
+assertBuiltinGetter(Intl.Locale.prototype, "numeric", 0, "get numeric");
+
+// get Intl.Locale.prototype.numberingSystem
+assertBuiltinGetter(Intl.Locale.prototype, "numberingSystem", 0, "get numberingSystem");
+
+// get Intl.Locale.prototype.language
+assertBuiltinGetter(Intl.Locale.prototype, "language", 0, "get language");
+
+// get Intl.Locale.prototype.script
+assertBuiltinGetter(Intl.Locale.prototype, "script", 0, "get script");
+
+// get Intl.Locale.prototype.region
+assertBuiltinGetter(Intl.Locale.prototype, "region", 0, "get region");
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/NumberFormat/StringBuffer.js b/js/src/tests/non262/Intl/NumberFormat/StringBuffer.js
new file mode 100644
index 0000000000..70aaa7bd75
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/StringBuffer.js
@@ -0,0 +1,37 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// The implementation of the format function uses the C++ StringBuffer class,
+// which changes its storage model at 32 characters, and uses it in a way which
+// also means that there's no room for null-termination at this limit.
+// This test makes sure that none of this affects the output.
+
+var format = new Intl.NumberFormat("it-IT", {minimumFractionDigits: 1});
+
+assertEq(format.format(1123123123123123123123.1), "1.123.123.123.123.123.100.000,0");
+assertEq(format.format(12123123123123123123123.1), "12.123.123.123.123.122.000.000,0");
+assertEq(format.format(123123123123123123123123.1), "123.123.123.123.123.120.000.000,0");
+
+// Ensure the ICU output matches Number.prototype.toFixed.
+function formatToFixed(x) {
+ var mfd = format.resolvedOptions().maximumFractionDigits;
+ var s = x.toFixed(mfd);
+
+ // To keep it simple we assume |s| is always in exponential form.
+ var m = s.match(/^(\d)\.(\d+)e\+(\d+)$/);
+ assertEq(m !== null, true);
+ s = m[1] + m[2].padEnd(m[3], "0");
+
+ // Group digits and append fractional part.
+ m = s.match(/\d{1,3}(?=(?:\d{3})*$)/g);
+ assertEq(m !== null, true);
+ return m.join(".") + ",0";
+}
+
+assertEq(formatToFixed(1123123123123123123123.1), "1.123.123.123.123.123.100.000,0");
+assertEq(formatToFixed(12123123123123123123123.1), "12.123.123.123.123.122.000.000,0");
+assertEq(formatToFixed(123123123123123123123123.1), "123.123.123.123.123.120.000.000,0");
+
+reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/NumberFormat/bigint-int64.js b/js/src/tests/non262/Intl/NumberFormat/bigint-int64.js
new file mode 100644
index 0000000000..7b670d0873
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/bigint-int64.js
@@ -0,0 +1,40 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Ensure the int64_t optimization when formatting a BigInt value works correctly by testing with
+// various integers around the (u)int[32,64] limits.
+
+const limits = {
+ int32: {
+ min: -0x80000000n,
+ max: 0x7FFFFFFFn,
+ },
+ uint32: {
+ min: 0n,
+ max: 0xFFFFFFFFn
+ },
+ int64: {
+ min: -0x8000000000000000n,
+ max: 0x7FFFFFFFFFFFFFFFn,
+ },
+ uint64: {
+ min: 0n,
+ max: 0xFFFFFFFFFFFFFFFFn
+ },
+};
+
+const nf = new Intl.NumberFormat("en", {useGrouping: false});
+
+const diff = 10n;
+
+for (const int of Object.values(limits)) {
+ for (let i = -diff; i <= diff; ++i) {
+ let n = int.min + i;
+ assertEq(nf.format(n), n.toString());
+
+ let m = int.max + i;
+ assertEq(nf.format(m), m.toString());
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/browser.js b/js/src/tests/non262/Intl/NumberFormat/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/browser.js
diff --git a/js/src/tests/non262/Intl/NumberFormat/call.js b/js/src/tests/non262/Intl/NumberFormat/call.js
new file mode 100644
index 0000000000..9711a4d6a9
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/call.js
@@ -0,0 +1,182 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+function IsIntlService(c) {
+ return typeof c === "function" &&
+ c.hasOwnProperty("prototype") &&
+ c.prototype.hasOwnProperty("resolvedOptions");
+}
+
+function IsObject(o) {
+ return Object(o) === o;
+}
+
+function IsPrimitive(o) {
+ return Object(o) !== o;
+}
+
+function thisValues() {
+ const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsIntlService);
+
+ return [
+ // Primitive values.
+ ...[undefined, null, true, "abc", Symbol(), 123],
+
+ // Object values.
+ ...[{}, [], /(?:)/, function(){}, new Proxy({}, {})],
+
+ // Intl objects.
+ ...[].concat(...intlConstructors.map(ctor => {
+ let args = [];
+ if (ctor === Intl.DisplayNames) {
+ // Intl.DisplayNames can't be constructed without any arguments.
+ args = [undefined, {type: "language"}];
+ }
+
+ return [
+ // Instance of an Intl constructor.
+ new ctor(...args),
+
+ // Instance of a subclassed Intl constructor.
+ new class extends ctor {}(...args),
+
+ // Object inheriting from an Intl constructor prototype.
+ Object.create(ctor.prototype),
+
+ // Intl object not inheriting from its default prototype.
+ Object.setPrototypeOf(new ctor(...args), Object.prototype),
+ ];
+ })),
+ ];
+}
+
+const intlFallbackSymbol = Object.getOwnPropertySymbols(Intl.NumberFormat.call(Object.create(Intl.NumberFormat.prototype)))[0];
+
+// Invoking [[Call]] for Intl.NumberFormat returns a new instance unless called
+// with an instance inheriting from Intl.NumberFormat.prototype.
+for (let thisValue of thisValues()) {
+ let obj = Intl.NumberFormat.call(thisValue);
+
+ if (!Intl.NumberFormat.prototype.isPrototypeOf(thisValue)) {
+ assertEq(Object.is(obj, thisValue), false);
+ assertEq(obj instanceof Intl.NumberFormat, true);
+ if (IsObject(thisValue))
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+ } else {
+ assertEq(Object.is(obj, thisValue), true);
+ assertEq(obj instanceof Intl.NumberFormat, true);
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+ }
+}
+
+// Intl.NumberFormat uses the legacy Intl constructor compromise semantics.
+// - Test when InstanceofOperator(thisValue, %NumberFormat%) returns true.
+for (let thisValue of thisValues().filter(IsObject)) {
+ let hasInstanceCalled = false;
+ Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
+ value() {
+ assertEq(hasInstanceCalled, false);
+ hasInstanceCalled = true;
+ return true;
+ }, configurable: true
+ });
+ let obj = Intl.NumberFormat.call(thisValue);
+ delete Intl.NumberFormat[Symbol.hasInstance];
+
+ assertEq(Object.is(obj, thisValue), true);
+ assertEq(hasInstanceCalled, true);
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+}
+// - Test when InstanceofOperator(thisValue, %NumberFormat%) returns false.
+for (let thisValue of thisValues().filter(IsObject)) {
+ let hasInstanceCalled = false;
+ Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
+ value() {
+ assertEq(hasInstanceCalled, false);
+ hasInstanceCalled = true;
+ return false;
+ }, configurable: true
+ });
+ let obj = Intl.NumberFormat.call(thisValue);
+ delete Intl.NumberFormat[Symbol.hasInstance];
+
+ assertEq(Object.is(obj, thisValue), false);
+ assertEq(obj instanceof Intl.NumberFormat, true);
+ assertEq(hasInstanceCalled, true);
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+}
+// - Test with primitive values.
+for (let thisValue of thisValues().filter(IsPrimitive)) {
+ // Ensure @@hasInstance is not called.
+ Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
+ value() { assertEq(true, false); }, configurable: true
+ });
+ let obj = Intl.NumberFormat.call(thisValue);
+ delete Intl.NumberFormat[Symbol.hasInstance];
+
+ assertEq(Object.is(obj, thisValue), false);
+ assertEq(obj instanceof Intl.NumberFormat, true);
+}
+
+// Throws an error when attempting to install [[FallbackSymbol]] twice.
+{
+ let thisValue = Object.create(Intl.NumberFormat.prototype);
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+
+ assertEq(Intl.NumberFormat.call(thisValue), thisValue);
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+
+ assertThrowsInstanceOf(() => Intl.NumberFormat.call(thisValue), TypeError);
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+}
+
+// Throws an error when the thisValue is non-extensible.
+{
+ let thisValue = Object.create(Intl.NumberFormat.prototype);
+ Object.preventExtensions(thisValue);
+
+ assertThrowsInstanceOf(() => Intl.NumberFormat.call(thisValue), TypeError);
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+}
+
+// [[FallbackSymbol]] is installed as a frozen property holding an Intl.NumberFormat instance.
+{
+ let thisValue = Object.create(Intl.NumberFormat.prototype);
+ Intl.NumberFormat.call(thisValue);
+
+ let desc = Object.getOwnPropertyDescriptor(thisValue, intlFallbackSymbol);
+ assertEq(desc !== undefined, true);
+ assertEq(desc.writable, false);
+ assertEq(desc.enumerable, false);
+ assertEq(desc.configurable, false);
+ assertEq(desc.value instanceof Intl.NumberFormat, true);
+}
+
+// Ensure [[FallbackSymbol]] is installed last by changing the [[Prototype]]
+// during initialization.
+{
+ let thisValue = {};
+ let options = {
+ get useGrouping() {
+ Object.setPrototypeOf(thisValue, Intl.NumberFormat.prototype);
+ return false;
+ }
+ };
+ let obj = Intl.NumberFormat.call(thisValue, undefined, options);
+ assertEq(Object.is(obj, thisValue), true);
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+}
+{
+ let thisValue = Object.create(Intl.NumberFormat.prototype);
+ let options = {
+ get useGrouping() {
+ Object.setPrototypeOf(thisValue, Object.prototype);
+ return false;
+ }
+ };
+ let obj = Intl.NumberFormat.call(thisValue, undefined, options);
+ assertEq(Object.is(obj, thisValue), false);
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/construct-newtarget.js b/js/src/tests/non262/Intl/NumberFormat/construct-newtarget.js
new file mode 100644
index 0000000000..0053b2737e
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/construct-newtarget.js
@@ -0,0 +1,81 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+// Test subclassing %Intl.NumberFormat% works correctly.
+class MyNumberFormat extends Intl.NumberFormat {}
+
+var obj = new MyNumberFormat();
+assertEq(obj instanceof MyNumberFormat, true);
+assertEq(obj instanceof Intl.NumberFormat, true);
+assertEq(Object.getPrototypeOf(obj), MyNumberFormat.prototype);
+
+obj = Reflect.construct(MyNumberFormat, []);
+assertEq(obj instanceof MyNumberFormat, true);
+assertEq(obj instanceof Intl.NumberFormat, true);
+assertEq(Object.getPrototypeOf(obj), MyNumberFormat.prototype);
+
+obj = Reflect.construct(MyNumberFormat, [], MyNumberFormat);
+assertEq(obj instanceof MyNumberFormat, true);
+assertEq(obj instanceof Intl.NumberFormat, true);
+assertEq(Object.getPrototypeOf(obj), MyNumberFormat.prototype);
+
+obj = Reflect.construct(MyNumberFormat, [], Intl.NumberFormat);
+assertEq(obj instanceof MyNumberFormat, false);
+assertEq(obj instanceof Intl.NumberFormat, true);
+assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype);
+
+
+// Set a different constructor as NewTarget.
+obj = Reflect.construct(MyNumberFormat, [], Array);
+assertEq(obj instanceof MyNumberFormat, false);
+assertEq(obj instanceof Intl.NumberFormat, false);
+assertEq(obj instanceof Array, true);
+assertEq(Object.getPrototypeOf(obj), Array.prototype);
+
+obj = Reflect.construct(Intl.NumberFormat, [], Array);
+assertEq(obj instanceof Intl.NumberFormat, false);
+assertEq(obj instanceof Array, true);
+assertEq(Object.getPrototypeOf(obj), Array.prototype);
+
+
+// The prototype defaults to %NumberFormatPrototype% if null.
+function NewTargetNullPrototype() {}
+NewTargetNullPrototype.prototype = null;
+
+obj = Reflect.construct(Intl.NumberFormat, [], NewTargetNullPrototype);
+assertEq(obj instanceof Intl.NumberFormat, true);
+assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype);
+
+obj = Reflect.construct(MyNumberFormat, [], NewTargetNullPrototype);
+assertEq(obj instanceof MyNumberFormat, false);
+assertEq(obj instanceof Intl.NumberFormat, true);
+assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype);
+
+
+// "prototype" property is retrieved exactly once.
+var trapLog = [], getLog = [];
+var ProxiedConstructor = new Proxy(Intl.NumberFormat, new Proxy({
+ get(target, propertyKey, receiver) {
+ getLog.push(propertyKey);
+ return Reflect.get(target, propertyKey, receiver);
+ }
+}, {
+ get(target, propertyKey, receiver) {
+ trapLog.push(propertyKey);
+ return Reflect.get(target, propertyKey, receiver);
+ }
+}));
+
+obj = Reflect.construct(Intl.NumberFormat, [], ProxiedConstructor);
+assertEqArray(trapLog, ["get"]);
+assertEqArray(getLog, ["prototype"]);
+assertEq(obj instanceof Intl.NumberFormat, true);
+assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype);
+
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/NumberFormat/cross-compartment.js b/js/src/tests/non262/Intl/NumberFormat/cross-compartment.js
new file mode 100644
index 0000000000..806f889d6f
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/cross-compartment.js
@@ -0,0 +1,70 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+var otherGlobal = newGlobal();
+
+var numberFormat = new Intl.NumberFormat();
+var ccwNumberFormat = new otherGlobal.Intl.NumberFormat();
+
+// Test Intl.NumberFormat.prototype.format with a CCW object.
+var Intl_NumberFormat_format_get = Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, "format").get;
+
+assertEq(Intl_NumberFormat_format_get.call(ccwNumberFormat)(0),
+ Intl_NumberFormat_format_get.call(numberFormat)(0));
+
+// Test Intl.NumberFormat.prototype.formatToParts with a CCW object.
+var Intl_NumberFormat_formatToParts = Intl.NumberFormat.prototype.formatToParts;
+
+assertEq(deepEqual(Intl_NumberFormat_formatToParts.call(ccwNumberFormat, 0),
+ Intl_NumberFormat_formatToParts.call(numberFormat, 0)),
+ true);
+
+// Test Intl.NumberFormat.prototype.resolvedOptions with a CCW object.
+var Intl_NumberFormat_resolvedOptions = Intl.NumberFormat.prototype.resolvedOptions;
+
+assertEq(deepEqual(Intl_NumberFormat_resolvedOptions.call(ccwNumberFormat),
+ Intl_NumberFormat_resolvedOptions.call(numberFormat)),
+ true);
+
+// Special case for Intl.NumberFormat: The Intl fallback symbol.
+
+function fallbackSymbol(global) {
+ var NF = global.Intl.NumberFormat;
+ return Object.getOwnPropertySymbols(NF.call(Object.create(NF.prototype)))[0];
+}
+
+const intlFallbackSymbol = fallbackSymbol(this);
+const otherIntlFallbackSymbol = fallbackSymbol(otherGlobal);
+assertEq(intlFallbackSymbol === otherIntlFallbackSymbol, false);
+
+// Test when the fallback symbol points to a CCW NumberFormat object.
+var objWithFallbackCCWNumberFormat = {
+ __proto__: Intl.NumberFormat.prototype,
+ [intlFallbackSymbol]: ccwNumberFormat,
+};
+
+assertEq(Intl_NumberFormat_format_get.call(objWithFallbackCCWNumberFormat)(0),
+ Intl_NumberFormat_format_get.call(numberFormat)(0));
+
+assertEq(deepEqual(Intl_NumberFormat_resolvedOptions.call(objWithFallbackCCWNumberFormat),
+ Intl_NumberFormat_resolvedOptions.call(numberFormat)),
+ true);
+
+// Ensure the fallback symbol(s) are not accessed for CCW NumberFormat objects.
+var ccwNumberFormatWithPoisonedFallback = new otherGlobal.Intl.NumberFormat();
+Object.setPrototypeOf(ccwNumberFormatWithPoisonedFallback, Intl.NumberFormat.prototype);
+Object.defineProperty(ccwNumberFormatWithPoisonedFallback, intlFallbackSymbol, {
+ get() { throw new Error(); }
+});
+Object.defineProperty(ccwNumberFormatWithPoisonedFallback, otherIntlFallbackSymbol, {
+ get() { throw new Error(); }
+});
+
+assertEq(Intl_NumberFormat_format_get.call(ccwNumberFormatWithPoisonedFallback)(0),
+ Intl_NumberFormat_format_get.call(numberFormat)(0));
+
+assertEq(deepEqual(Intl_NumberFormat_resolvedOptions.call(ccwNumberFormatWithPoisonedFallback),
+ Intl_NumberFormat_resolvedOptions.call(numberFormat)),
+ true);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/currency-narrow-symbol.js b/js/src/tests/non262/Intl/NumberFormat/currency-narrow-symbol.js
new file mode 100644
index 0000000000..4873045685
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/currency-narrow-symbol.js
@@ -0,0 +1,40 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const {
+ Integer, Decimal, Fraction, Currency, Literal,
+} = NumberFormatParts;
+
+const testcases = [
+ {
+ locale: "en-CA",
+ options: {
+ style: "currency",
+ currency: "USD",
+ currencyDisplay: "narrowSymbol",
+ },
+ values: [
+ {value: 123, string: "$123.00",
+ parts: [Currency("$"), Integer("123"), Decimal("."), Fraction("00")]},
+ ],
+ },
+
+ // And for comparison "symbol" currency-display.
+
+ {
+ locale: "en-CA",
+ options: {
+ style: "currency",
+ currency: "USD",
+ currencyDisplay: "symbol",
+ },
+ values: [
+ {value: 123, string: "US$123.00",
+ parts: [Currency("US$"), Integer("123"), Decimal("."), Fraction("00")]},
+ ],
+ },
+];
+
+runNumberFormattingTestcases(testcases);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/currency-sign-accounting.js b/js/src/tests/non262/Intl/NumberFormat/currency-sign-accounting.js
new file mode 100644
index 0000000000..6f91d6edd4
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/currency-sign-accounting.js
@@ -0,0 +1,192 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const {
+ Nan, Inf, Integer, MinusSign, PlusSign, Decimal, Fraction,
+ Currency, Literal,
+} = NumberFormatParts;
+
+const testcases = [
+ // "auto": Show the sign on negative numbers only.
+ {
+ locale: "en",
+ options: {
+ style: "currency",
+ currency: "USD",
+ currencySign: "accounting",
+ signDisplay: "auto",
+ },
+ values: [
+ {value: +0, string: "$0.00",
+ parts: [Currency("$"), Integer("0"), Decimal("."), Fraction("00")]},
+ {value: -0, string: "($0.00)",
+ parts: [Literal("("), Currency("$"), Integer("0"), Decimal("."), Fraction("00"), Literal(")")]},
+
+ {value: 1, string: "$1.00",
+ parts: [Currency("$"), Integer("1"), Decimal("."), Fraction("00")]},
+ {value: -1, string: "($1.00)",
+ parts: [Literal("("), Currency("$"), Integer("1"), Decimal("."), Fraction("00"), Literal(")")]},
+
+ {value: Infinity, string: "$∞", parts: [Currency("$"), Inf("∞")]},
+ {value: -Infinity, string: "($∞)", parts: [Literal("("), Currency("$"), Inf("∞"), Literal(")")]},
+
+ {value: NaN, string: "$NaN", parts: [Currency("$"), Nan("NaN")]},
+ {value: -NaN, string: "$NaN", parts: [Currency("$"), Nan("NaN")]},
+ ],
+ },
+
+ // "never": Show the sign on neither positive nor negative numbers.
+ {
+ locale: "en",
+ options: {
+ style: "currency",
+ currency: "USD",
+ currencySign: "accounting",
+ signDisplay: "never",
+ },
+ values: [
+ {value: +0, string: "$0.00", parts: [Currency("$"), Integer("0"), Decimal("."), Fraction("00")]},
+ {value: -0, string: "$0.00", parts: [Currency("$"), Integer("0"), Decimal("."), Fraction("00")]},
+
+ {value: 1, string: "$1.00", parts: [Currency("$"), Integer("1"), Decimal("."), Fraction("00")]},
+ {value: -1, string: "$1.00", parts: [Currency("$"), Integer("1"), Decimal("."), Fraction("00")]},
+
+ {value: Infinity, string: "$∞", parts: [Currency("$"), Inf("∞")]},
+ {value: -Infinity, string: "$∞", parts: [Currency("$"), Inf("∞")]},
+
+ {value: NaN, string: "$NaN", parts: [Currency("$"), Nan("NaN")]},
+ {value: -NaN, string: "$NaN", parts: [Currency("$"), Nan("NaN")]},
+ ],
+ },
+
+ // "always": Show the sign on positive and negative numbers including zero.
+ {
+ locale: "en",
+ options: {
+ style: "currency",
+ currency: "USD",
+ currencySign: "accounting",
+ signDisplay: "always",
+ },
+ values: [
+ {value: +0, string: "+$0.00",
+ parts: [PlusSign("+"), Currency("$"), Integer("0"), Decimal("."), Fraction("00")]},
+ {value: -0, string: "($0.00)",
+ parts: [Literal("("), Currency("$"), Integer("0"), Decimal("."), Fraction("00"), Literal(")")]},
+
+ {value: 1, string: "+$1.00",
+ parts: [PlusSign("+"), Currency("$"), Integer("1"), Decimal("."), Fraction("00")]},
+ {value: -1, string: "($1.00)",
+ parts: [Literal("("), Currency("$"), Integer("1"), Decimal("."), Fraction("00"), Literal(")")]},
+
+ {value: Infinity, string: "+$∞", parts: [PlusSign("+"), Currency("$"), Inf("∞")]},
+ {value: -Infinity, string: "($∞)", parts: [Literal("("), Currency("$"), Inf("∞"), Literal(")")]},
+
+ {value: NaN, string: "+$NaN", parts: [PlusSign("+"), Currency("$"), Nan("NaN")]},
+ {value: -NaN, string: "+$NaN", parts: [PlusSign("+"), Currency("$"), Nan("NaN")]},
+ ],
+ },
+
+ // "exceptZero": Show the sign on positive and negative numbers but not zero.
+ {
+ locale: "en",
+ options: {
+ style: "currency",
+ currency: "USD",
+ currencySign: "accounting",
+ signDisplay: "exceptZero",
+ },
+ values: [
+ {value: +0, string: "$0.00",
+ parts: [Currency("$"), Integer("0"), Decimal("."), Fraction("00")]},
+ {value: -0, string: "$0.00",
+ parts: [Currency("$"), Integer("0"), Decimal("."), Fraction("00")]},
+
+ {value: 1, string: "+$1.00",
+ parts: [PlusSign("+"), Currency("$"), Integer("1"), Decimal("."), Fraction("00")]},
+ {value: -1, string: "($1.00)",
+ parts: [Literal("("), Currency("$"), Integer("1"), Decimal("."), Fraction("00"), Literal(")")]},
+
+ {value: Infinity, string: "+$∞", parts: [PlusSign("+"), Currency("$"), Inf("∞")]},
+ {value: -Infinity, string: "($∞)", parts: [Literal("("), Currency("$"), Inf("∞"), Literal(")")]},
+
+ {value: NaN, string: "$NaN", parts: [Currency("$"), Nan("NaN")]},
+ {value: -NaN, string: "$NaN", parts: [Currency("$"), Nan("NaN")]},
+ ],
+ },
+
+ // Tests with suppressed fractional digits.
+
+ // "auto": Show the sign on negative numbers only.
+ {
+ locale: "en",
+ options: {
+ style: "currency",
+ currency: "USD",
+ currencySign: "accounting",
+ signDisplay: "auto",
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ },
+ values: [
+ {value: +0.1, string: "$0", parts: [Currency("$"), Integer("0")]},
+ {value: -0.1, string: "($0)", parts: [Literal("("), Currency("$"), Integer("0"), Literal(")")]},
+ ],
+ },
+
+ // "never": Show the sign on neither positive nor negative numbers.
+ {
+ locale: "en",
+ options: {
+ style: "currency",
+ currency: "USD",
+ currencySign: "accounting",
+ signDisplay: "never",
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ },
+ values: [
+ {value: +0.1, string: "$0", parts: [Currency("$"), Integer("0")]},
+ {value: -0.1, string: "$0", parts: [Currency("$"), Integer("0")]},
+ ],
+ },
+
+ // "always": Show the sign on positive and negative numbers including zero.
+ {
+ locale: "en",
+ options: {
+ style: "currency",
+ currency: "USD",
+ currencySign: "accounting",
+ signDisplay: "always",
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ },
+ values: [
+ {value: +0.1, string: "+$0", parts: [PlusSign("+"), Currency("$"), Integer("0")]},
+ {value: -0.1, string: "($0)", parts: [Literal("("), Currency("$"), Integer("0"), Literal(")")]},
+ ],
+ },
+
+ // "exceptZero": Show the sign on positive and negative numbers but not zero.
+ {
+ locale: "en",
+ options: {
+ style: "currency",
+ currency: "USD",
+ currencySign: "accounting",
+ signDisplay: "exceptZero",
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ },
+
+ values: [
+ {value: +0.1, string: "$0", parts: [Currency("$"), Integer("0")]},
+ {value: -0.1, string: "$0", parts: [Currency("$"), Integer("0")]},
+ ],
+ }
+];
+
+runNumberFormattingTestcases(testcases);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/duplicate-singleton-variant.js b/js/src/tests/non262/Intl/NumberFormat/duplicate-singleton-variant.js
new file mode 100644
index 0000000000..42bf78c3fe
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/duplicate-singleton-variant.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Check for duplicate variants and singletons case-insensitively, but don't
+// check in privateuse components.
+
+function checkInvalidLocale(locale)
+{
+ try
+ {
+ new Intl.NumberFormat(locale);
+ throw new Error("didn't throw");
+ }
+ catch (e)
+ {
+ assertEq(e instanceof RangeError, true,
+ "expected RangeError for locale '" + locale + "', got " + e);
+ }
+}
+
+var badLocales =
+ [
+ "en-u-foo-U-foo",
+ "en-tester-Tester",
+ "en-tesTER-TESter",
+ "de-DE-u-kn-true-U-kn-true",
+ "ar-u-foo-q-bar-u-baz",
+ "ar-z-moo-u-foo-q-bar-z-eit-u-baz",
+ ];
+
+for (var locale of badLocales)
+ checkInvalidLocale(locale);
+
+// Fully-privateuse locales are rejected.
+for (var locale of badLocales)
+ assertThrowsInstanceOf(() => new Intl.NumberFormat("x-" + locale), RangeError);
+
+// Locales with trailing privateuse also okay.
+for (var locale of badLocales)
+{
+ new Intl.NumberFormat("en-x-" + locale).format(5);
+ new Intl.NumberFormat("en-u-foo-x-u-" + locale).format(5);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/format-as-code-or-name.js b/js/src/tests/non262/Intl/NumberFormat/format-as-code-or-name.js
new file mode 100644
index 0000000000..ba257ecd71
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/format-as-code-or-name.js
@@ -0,0 +1,75 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 1093421;
+var summary =
+ "new Intl.NumberFormat(..., { style: 'currency', currency: '...', " +
+ "currencyDisplay: 'name' or 'code' }) should have behavior other than " +
+ "throwing";
+
+print(BUGNUMBER + ": " + summary);
+
+//-----------------------------------------------------------------------------
+
+// Test that currencyDisplay: "code" behaves correctly and doesn't throw.
+
+var usdCodeOptions =
+ {
+ style: "currency",
+ currency: "USD",
+ currencyDisplay: "code",
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ };
+var usDollarsCode = new Intl.NumberFormat("en-US", usdCodeOptions);
+assertEq(/USD/.test(usDollarsCode.format(25)), true);
+
+// ISO 4217 currency codes are formed from an ISO 3166-1 alpha-2 country code
+// followed by a third letter. ISO 3166 guarantees that no country code
+// starting with "X" will ever be assigned. Stepping carefully around a few
+// 4217-designated special "currencies", XQQ will never have a representation.
+// Thus, yes: this really is specified to work, as unrecognized or unsupported
+// codes pass into the string unmodified.
+var xqqCodeOptions =
+ {
+ style: "currency",
+ currency: "XQQ",
+ currencyDisplay: "code",
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ };
+var xqqMoneyCode = new Intl.NumberFormat("en-US", xqqCodeOptions);
+assertEq(/XQQ/.test(xqqMoneyCode.format(25)), true);
+
+// Test that currencyDisplay: "name" behaves without throwing. (Unlike the two
+// above tests, the results here aren't guaranteed as the name is
+// implementation-defined.)
+var usdNameOptions =
+ {
+ style: "currency",
+ currency: "USD",
+ currencyDisplay: "name",
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ };
+var usDollarsName = new Intl.NumberFormat("en-US", usdNameOptions);
+assertEq(usDollarsName.format(25), "25 US dollars");
+
+// But if the implementation doesn't recognize the currency, the provided code
+// is used in place of a proper name, unmolested.
+var xqqNameOptions =
+ {
+ style: "currency",
+ currency: "XQQ",
+ currencyDisplay: "name",
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ };
+var xqqMoneyName = new Intl.NumberFormat("en-US", xqqNameOptions);
+assertEq(/XQQ/.test(xqqMoneyName.format(25)), true);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/format.js b/js/src/tests/non262/Intl/NumberFormat/format.js
new file mode 100644
index 0000000000..a71a877566
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/format.js
@@ -0,0 +1,55 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tests the format function with a diverse set of locales and options.
+
+var format;
+
+// Locale en-US; default options.
+format = new Intl.NumberFormat("en-us");
+assertEq(format.format(0), "0");
+assertEq(format.format(-1), "-1");
+assertEq(format.format(123456789.123456789), "123,456,789.123");
+
+// Locale en-US; currency USD.
+// The US dollar uses two fractional digits, and negative values are commonly
+// parenthesized.
+format = new Intl.NumberFormat("en-us", {style: "currency", currency: "USD"});
+assertEq(format.format(0), "$0.00");
+assertEq(format.format(-1), "-$1.00");
+assertEq(format.format(123456789.123456789), "$123,456,789.12");
+
+// Locale ja-JP; currency JPY.
+// The Japanese yen has no subunit in real life.
+format = new Intl.NumberFormat("ja-jp", {style: "currency", currency: "JPY"});
+assertEq(format.format(0), "¥0");
+assertEq(format.format(-1), "-¥1");
+assertEq(format.format(123456789.123456789), "¥123,456,789");
+
+// Locale ar-JO; currency JOD.
+// The Jordanian Dinar divides into 1000 fils. Jordan uses (real) Arabic digits.
+format = new Intl.NumberFormat("ar-jo", {style: "currency", currency: "JOD"});
+assertEq(format.format(0), "٠٫٠٠٠ د.أ.‏");
+assertEq(format.format(-1), "؜-١٫٠٠٠ د.أ.‏");
+assertEq(format.format(123456789.123456789), "١٢٣٬٤٥٦٬٧٨٩٫١٢٣ د.أ.‏");
+
+// Locale th-TH; Thai digits, percent, two significant digits.
+format = new Intl.NumberFormat("th-th-u-nu-thai",
+ {style: "percent",
+ minimumSignificantDigits: 2,
+ maximumSignificantDigits: 2});
+assertEq(format.format(0), "๐.๐%");
+assertEq(format.format(-0.01), "-๑.๐%");
+assertEq(format.format(1.10), "๑๑๐%");
+
+
+// Test the .name property of the "format" getter.
+var desc = Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, "format");
+assertEq(desc !== undefined, true);
+assertEq(typeof desc.get, "function");
+assertEq(desc.get.name, "get format");
+
+
+reportCompare(0, 0, 'ok');
diff --git a/js/src/tests/non262/Intl/NumberFormat/formatToParts.js b/js/src/tests/non262/Intl/NumberFormat/formatToParts.js
new file mode 100644
index 0000000000..3ac7c3f4d3
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/formatToParts.js
@@ -0,0 +1,357 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 1289882;
+var summary = "Implement Intl.NumberFormat.prototype.formatToParts";
+
+print(BUGNUMBER + ": " + summary);
+
+//-----------------------------------------------------------------------------
+
+assertEq("formatToParts" in Intl.NumberFormat(), true);
+
+// NOTE: Some of these tests exercise standard behavior (e.g. that format and
+// formatToParts expose the same formatted string). But much of this,
+// like the exact-formatted-string expectations, is technically
+// implementation-dependent. This is necessary as a practical matter to
+// properly test the conversion from ICU's nested-field exposure to
+// ECMA-402's sequential-parts exposure.
+
+var {
+ Nan, Inf, Integer, Group, Decimal, Fraction,
+ MinusSign, PlusSign, PercentSign, Currency, Literal,
+} = NumberFormatParts;
+
+//-----------------------------------------------------------------------------
+
+// Test -0's partitioning now that it's not treated like +0.
+// https://github.com/tc39/ecma402/pull/232
+
+var deadSimpleFormatter = new Intl.NumberFormat("en-US");
+
+assertParts(deadSimpleFormatter, -0,
+ [MinusSign("-"), Integer("0")]);
+
+// Test behavior of a currency with code formatting.
+var usdCodeOptions =
+ {
+ style: "currency",
+ currency: "USD",
+ currencyDisplay: "code",
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ };
+var usDollarsCode = new Intl.NumberFormat("en-US", usdCodeOptions);
+
+assertParts(usDollarsCode, 25,
+ [Currency("USD"), Literal("\xA0"), Integer("25")]);
+
+// ISO 4217 currency codes are formed from an ISO 3166-1 alpha-2 country code
+// followed by a third letter. ISO 3166 guarantees that no country code
+// starting with "X" will ever be assigned. Stepping carefully around a few
+// 4217-designated special "currencies", XQQ will never have a representation.
+// Thus, yes: this really is specified to work, as unrecognized or unsupported
+// codes pass into the string unmodified.
+var xqqCodeOptions =
+ {
+ style: "currency",
+ currency: "XQQ",
+ currencyDisplay: "code",
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ };
+var xqqMoneyCode = new Intl.NumberFormat("en-US", xqqCodeOptions);
+
+assertParts(xqqMoneyCode, 25,
+ [Currency("XQQ"), Literal("\xA0"), Integer("25")]);
+
+// Test currencyDisplay: "name".
+var usdNameOptions =
+ {
+ style: "currency",
+ currency: "USD",
+ currencyDisplay: "name",
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ };
+var usDollarsName = new Intl.NumberFormat("en-US", usdNameOptions);
+
+assertParts(usDollarsName, 25,
+ [Integer("25"), Literal(" "), Currency("US dollars")]);
+
+var usdNameGroupingOptions =
+ {
+ style: "currency",
+ currency: "USD",
+ currencyDisplay: "name",
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ };
+var usDollarsNameGrouping =
+ new Intl.NumberFormat("en-US", usdNameGroupingOptions);
+
+assertParts(usDollarsNameGrouping, 12345678,
+ [Integer("12"),
+ Group(","),
+ Integer("345"),
+ Group(","),
+ Integer("678"),
+ Literal(" "),
+ Currency("US dollars")]);
+
+// But if the implementation doesn't recognize the currency, the provided code
+// is used in place of a proper name, unmolested.
+var xqqNameOptions =
+ {
+ style: "currency",
+ currency: "XQQ",
+ currencyDisplay: "name",
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ };
+var xqqMoneyName = new Intl.NumberFormat("en-US", xqqNameOptions);
+
+assertParts(xqqMoneyName, 25,
+ [Integer("25"), Literal(" "), Currency("XQQ")]);
+
+// Test some currencies with fractional components.
+
+var usdNameFractionOptions =
+ {
+ style: "currency",
+ currency: "USD",
+ currencyDisplay: "name",
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ };
+var usdNameFractionFormatter =
+ new Intl.NumberFormat("en-US", usdNameFractionOptions);
+
+// The US national surplus (i.e. debt) as of October 18, 2016. (Replicating
+// data from a comment in builtin/Intl/NumberFormat.cpp.)
+var usNationalSurplus = -19766580028249.41;
+
+assertParts(usdNameFractionFormatter, usNationalSurplus,
+ [MinusSign("-"),
+ Integer("19"),
+ Group(","),
+ Integer("766"),
+ Group(","),
+ Integer("580"),
+ Group(","),
+ Integer("028"),
+ Group(","),
+ Integer("249"),
+ Decimal("."),
+ Fraction("41"),
+ Literal(" "),
+ Currency("US dollars")]);
+
+// Percents in various forms.
+
+var usPercentOptions =
+ {
+ style: "percent",
+ minimumFractionDigits: 1,
+ maximumFractionDigits: 1,
+ };
+var usPercentFormatter =
+ new Intl.NumberFormat("en-US", usPercentOptions);
+
+assertParts(usPercentFormatter, 0.375,
+ [Integer("37"), Decimal("."), Fraction("5"), PercentSign("%")]);
+
+assertParts(usPercentFormatter, -1284.375,
+ [MinusSign("-"),
+ Integer("128"),
+ Group(","),
+ Integer("437"),
+ Decimal("."),
+ Fraction("5"),
+ PercentSign("%")]);
+
+assertParts(usPercentFormatter, NaN,
+ [Nan("NaN"), PercentSign("%")]);
+
+assertParts(usPercentFormatter, Infinity,
+ [Inf("∞"), PercentSign("%")]);
+
+assertParts(usPercentFormatter, -Infinity,
+ [MinusSign("-"), Inf("∞"), PercentSign("%")]);
+
+var dePercentOptions =
+ {
+ style: "percent",
+ minimumFractionDigits: 1,
+ maximumFractionDigits: 1,
+ };
+var dePercentFormatter =
+ new Intl.NumberFormat("de", dePercentOptions);
+
+assertParts(dePercentFormatter, 0.375,
+ [Integer("37"), Decimal(","), Fraction("5"), Literal("\xA0"), PercentSign("%")]);
+
+assertParts(dePercentFormatter, -1284.375,
+ [MinusSign("-"),
+ Integer("128"),
+ Group("."),
+ Integer("437"),
+ Decimal(","),
+ Fraction("5"),
+ Literal("\xA0"),
+ PercentSign("%")]);
+
+assertParts(dePercentFormatter, NaN,
+ [Nan("NaN"), Literal("\xA0"), PercentSign("%")]);
+
+assertParts(dePercentFormatter, Infinity,
+ [Inf("∞"), Literal("\xA0"), PercentSign("%")]);
+
+assertParts(dePercentFormatter, -Infinity,
+ [MinusSign("-"), Inf("∞"), Literal("\xA0"), PercentSign("%")]);
+
+var arPercentOptions =
+ {
+ style: "percent",
+ minimumFractionDigits: 2,
+ };
+var arPercentFormatter =
+ new Intl.NumberFormat("ar-IQ", arPercentOptions);
+
+assertParts(arPercentFormatter, -135.32,
+ [Literal("\u{061C}"),
+ MinusSign("-"),
+ Integer("١٣"),
+ Group("٬"),
+ Integer("٥٣٢"),
+ Decimal("٫"),
+ Fraction("٠٠"),
+ PercentSign("٪"),
+ Literal("\u{061C}")]);
+
+// Decimals.
+
+var usDecimalOptions =
+ {
+ style: "decimal",
+ maximumFractionDigits: 7 // minimum defaults to 0
+ };
+var usDecimalFormatter =
+ new Intl.NumberFormat("en-US", usDecimalOptions);
+
+assertParts(usDecimalFormatter, 42,
+ [Integer("42")]);
+
+assertParts(usDecimalFormatter, 1337,
+ [Integer("1"), Group(","), Integer("337")]);
+
+assertParts(usDecimalFormatter, -6.25,
+ [MinusSign("-"), Integer("6"), Decimal("."), Fraction("25")]);
+
+assertParts(usDecimalFormatter, -1376.25,
+ [MinusSign("-"),
+ Integer("1"),
+ Group(","),
+ Integer("376"),
+ Decimal("."),
+ Fraction("25")]);
+
+assertParts(usDecimalFormatter, 124816.8359375,
+ [Integer("124"),
+ Group(","),
+ Integer("816"),
+ Decimal("."),
+ Fraction("8359375")]);
+
+var usNoGroupingDecimalOptions =
+ {
+ style: "decimal",
+ useGrouping: false,
+ maximumFractionDigits: 7 // minimum defaults to 0
+ };
+var usNoGroupingDecimalFormatter =
+ new Intl.NumberFormat("en-US", usNoGroupingDecimalOptions);
+
+assertParts(usNoGroupingDecimalFormatter, 1337,
+ [Integer("1337")]);
+
+assertParts(usNoGroupingDecimalFormatter, -6.25,
+ [MinusSign("-"), Integer("6"), Decimal("."), Fraction("25")]);
+
+assertParts(usNoGroupingDecimalFormatter, -1376.25,
+ [MinusSign("-"),
+ Integer("1376"),
+ Decimal("."),
+ Fraction("25")]);
+
+assertParts(usNoGroupingDecimalFormatter, 124816.8359375,
+ [Integer("124816"),
+ Decimal("."),
+ Fraction("8359375")]);
+
+var deDecimalOptions =
+ {
+ style: "decimal",
+ maximumFractionDigits: 7 // minimum defaults to 0
+ };
+var deDecimalFormatter =
+ new Intl.NumberFormat("de-DE", deDecimalOptions);
+
+assertParts(deDecimalFormatter, 42,
+ [Integer("42")]);
+
+assertParts(deDecimalFormatter, 1337,
+ [Integer("1"), Group("."), Integer("337")]);
+
+assertParts(deDecimalFormatter, -6.25,
+ [MinusSign("-"), Integer("6"), Decimal(","), Fraction("25")]);
+
+assertParts(deDecimalFormatter, -1376.25,
+ [MinusSign("-"),
+ Integer("1"),
+ Group("."),
+ Integer("376"),
+ Decimal(","),
+ Fraction("25")]);
+
+assertParts(deDecimalFormatter, 124816.8359375,
+ [Integer("124"),
+ Group("."),
+ Integer("816"),
+ Decimal(","),
+ Fraction("8359375")]);
+
+var deNoGroupingDecimalOptions =
+ {
+ style: "decimal",
+ useGrouping: false,
+ maximumFractionDigits: 7 // minimum defaults to 0
+ };
+var deNoGroupingDecimalFormatter =
+ new Intl.NumberFormat("de-DE", deNoGroupingDecimalOptions);
+
+assertParts(deNoGroupingDecimalFormatter, 1337,
+ [Integer("1337")]);
+
+assertParts(deNoGroupingDecimalFormatter, -6.25,
+ [MinusSign("-"), Integer("6"), Decimal(","), Fraction("25")]);
+
+assertParts(deNoGroupingDecimalFormatter, -1376.25,
+ [MinusSign("-"),
+ Integer("1376"),
+ Decimal(","),
+ Fraction("25")]);
+
+assertParts(deNoGroupingDecimalFormatter, 124816.8359375,
+ [Integer("124816"),
+ Decimal(","),
+ Fraction("8359375")]);
+
+//-----------------------------------------------------------------------------
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, 'ok');
+
+print("Tests complete");
diff --git a/js/src/tests/non262/Intl/NumberFormat/formatting-NaN.js b/js/src/tests/non262/Intl/NumberFormat/formatting-NaN.js
new file mode 100644
index 0000000000..430d74222e
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/formatting-NaN.js
@@ -0,0 +1,35 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 1484943;
+var summary = "Don't crash doing format/formatToParts on -NaN";
+
+print(BUGNUMBER + ": " + summary);
+
+//-----------------------------------------------------------------------------
+
+assertEq("formatToParts" in Intl.NumberFormat(), true);
+
+var nf = new Intl.NumberFormat("en-US");
+var parts;
+
+var values = [NaN, -NaN];
+
+for (var v of values)
+{
+ assertEq(nf.format(v), "NaN");
+
+ parts = nf.formatToParts(v);
+ assertEq(parts.length, 1);
+ assertEq(parts[0].type, "nan");
+ assertEq(parts[0].value, "NaN");
+}
+
+//-----------------------------------------------------------------------------
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, 'ok');
+
+print("Tests complete");
diff --git a/js/src/tests/non262/Intl/NumberFormat/negativeZeroFractionDigits.js b/js/src/tests/non262/Intl/NumberFormat/negativeZeroFractionDigits.js
new file mode 100644
index 0000000000..0db461f50e
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/negativeZeroFractionDigits.js
@@ -0,0 +1,21 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const optionsList = [
+ {minimumFractionDigits: -0, maximumFractionDigits: -0},
+ {minimumFractionDigits: -0, maximumFractionDigits: +0},
+ {minimumFractionDigits: +0, maximumFractionDigits: -0},
+ {minimumFractionDigits: +0, maximumFractionDigits: +0},
+];
+
+for (let options of optionsList) {
+ let numberFormat = new Intl.NumberFormat("en-US", options);
+
+ let {minimumFractionDigits, maximumFractionDigits} = numberFormat.resolvedOptions();
+ assertEq(minimumFractionDigits, +0);
+ assertEq(maximumFractionDigits, +0);
+
+ assertEq(numberFormat.format(123), "123");
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/notation-compact-long.js b/js/src/tests/non262/Intl/NumberFormat/notation-compact-long.js
new file mode 100644
index 0000000000..576c45858c
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/notation-compact-long.js
@@ -0,0 +1,134 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const {
+ Nan, Inf, Integer, MinusSign, PlusSign, Decimal, Fraction, Group, Literal,
+ Compact,
+} = NumberFormatParts;
+
+const testcases = [
+ {
+ locale: "en",
+ options: {
+ notation: "compact",
+ compactDisplay: "long",
+ },
+ values: [
+ {value: +0, string: "0", parts: [Integer("0")]},
+ {value: -0, string: "-0", parts: [MinusSign("-"), Integer("0")]},
+ {value: 0n, string: "0", parts: [Integer("0")]},
+
+ {value: 1, string: "1", parts: [Integer("1")]},
+ {value: 10, string: "10", parts: [Integer("10")]},
+ {value: 100, string: "100", parts: [Integer("100")]},
+ {value: 1000, string: "1 thousand", parts: [Integer("1"), Literal(" "), Compact("thousand")]},
+ {value: 10000, string: "10 thousand", parts: [Integer("10"), Literal(" "), Compact("thousand")]},
+ {value: 100000, string: "100 thousand", parts: [Integer("100"), Literal(" "), Compact("thousand")]},
+ {value: 1000000, string: "1 million", parts: [Integer("1"), Literal(" "), Compact("million")]},
+ {value: 10000000, string: "10 million", parts: [Integer("10"), Literal(" "), Compact("million")]},
+ {value: 100000000, string: "100 million", parts: [Integer("100"), Literal(" "), Compact("million")]},
+ {value: 1000000000, string: "1 billion", parts: [Integer("1"), Literal(" "), Compact("billion")]},
+ {value: 10000000000, string: "10 billion", parts: [Integer("10"), Literal(" "), Compact("billion")]},
+ {value: 100000000000, string: "100 billion", parts: [Integer("100"), Literal(" "), Compact("billion")]},
+ {value: 1000000000000, string: "1 trillion", parts: [Integer("1"), Literal(" "), Compact("trillion")]},
+ {value: 10000000000000, string: "10 trillion", parts: [Integer("10"), Literal(" "), Compact("trillion")]},
+ {value: 100000000000000, string: "100 trillion", parts: [Integer("100"), Literal(" "), Compact("trillion")]},
+ {value: 1000000000000000, string: "1000 trillion", parts: [Integer("1000"), Literal(" "), Compact("trillion")]},
+ {value: 10000000000000000, string: "10,000 trillion", parts: [Integer("10"), Group(","), Integer("000"), Literal(" "), Compact("trillion")]},
+ {value: 100000000000000000, string: "100,000 trillion", parts: [Integer("100"), Group(","), Integer("000"), Literal(" "), Compact("trillion")]},
+
+ {value: 1n, string: "1", parts: [Integer("1")]},
+ {value: 10n, string: "10", parts: [Integer("10")]},
+ {value: 100n, string: "100", parts: [Integer("100")]},
+ {value: 1000n, string: "1 thousand", parts: [Integer("1"), Literal(" "), Compact("thousand")]},
+ {value: 10000n, string: "10 thousand", parts: [Integer("10"), Literal(" "), Compact("thousand")]},
+ {value: 100000n, string: "100 thousand", parts: [Integer("100"), Literal(" "), Compact("thousand")]},
+ {value: 1000000n, string: "1 million", parts: [Integer("1"), Literal(" "), Compact("million")]},
+ {value: 10000000n, string: "10 million", parts: [Integer("10"), Literal(" "), Compact("million")]},
+ {value: 100000000n, string: "100 million", parts: [Integer("100"), Literal(" "), Compact("million")]},
+ {value: 1000000000n, string: "1 billion", parts: [Integer("1"), Literal(" "), Compact("billion")]},
+ {value: 10000000000n, string: "10 billion", parts: [Integer("10"), Literal(" "), Compact("billion")]},
+ {value: 100000000000n, string: "100 billion", parts: [Integer("100"), Literal(" "), Compact("billion")]},
+ {value: 1000000000000n, string: "1 trillion", parts: [Integer("1"), Literal(" "), Compact("trillion")]},
+ {value: 10000000000000n, string: "10 trillion", parts: [Integer("10"), Literal(" "), Compact("trillion")]},
+ {value: 100000000000000n, string: "100 trillion", parts: [Integer("100"), Literal(" "), Compact("trillion")]},
+ {value: 1000000000000000n, string: "1000 trillion", parts: [Integer("1000"), Literal(" "), Compact("trillion")]},
+ {value: 10000000000000000n, string: "10,000 trillion", parts: [Integer("10"), Group(","), Integer("000"), Literal(" "), Compact("trillion")]},
+ {value: 100000000000000000n, string: "100,000 trillion", parts: [Integer("100"), Group(","), Integer("000"), Literal(" "), Compact("trillion")]},
+
+ {value: 0.1, string: "0.1", parts: [Integer("0"), Decimal("."), Fraction("1")]},
+ {value: 0.01, string: "0.01", parts: [Integer("0"), Decimal("."), Fraction("01")]},
+ {value: 0.001, string: "0.001", parts: [Integer("0"), Decimal("."), Fraction("001")]},
+ {value: 0.0001, string: "0.0001", parts: [Integer("0"), Decimal("."), Fraction("0001")]},
+ {value: 0.00001, string: "0.00001", parts: [Integer("0"), Decimal("."), Fraction("00001")]},
+ {value: 0.000001, string: "0.000001", parts: [Integer("0"), Decimal("."), Fraction("000001")]},
+ {value: 0.0000001, string: "0.0000001", parts: [Integer("0"), Decimal("."), Fraction("0000001")]},
+
+ {value: 12, string: "12", parts: [Integer("12")]},
+ {value: 123, string: "123", parts: [Integer("123")]},
+ {value: 1234, string: "1.2 thousand", parts: [Integer("1"), Decimal("."), Fraction("2"), Literal(" "), Compact("thousand")]},
+ {value: 12345, string: "12 thousand", parts: [Integer("12"), Literal(" "), Compact("thousand")]},
+ {value: 123456, string: "123 thousand", parts: [Integer("123"), Literal(" "), Compact("thousand")]},
+ {value: 1234567, string: "1.2 million", parts: [Integer("1"), Decimal("."), Fraction("2"), Literal(" "), Compact("million")]},
+ {value: 12345678, string: "12 million", parts: [Integer("12"), Literal(" "), Compact("million")]},
+ {value: 123456789, string: "123 million", parts: [Integer("123"), Literal(" "), Compact("million")]},
+
+ {value: Infinity, string: "∞", parts: [Inf("∞")]},
+ {value: -Infinity, string: "-∞", parts: [MinusSign("-"), Inf("∞")]},
+
+ {value: NaN, string: "NaN", parts: [Nan("NaN")]},
+ {value: -NaN, string: "NaN", parts: [Nan("NaN")]},
+ ],
+ },
+
+ // The "{compactName}" placeholder may have different plural forms.
+ {
+ locale: "de",
+ options: {
+ notation: "compact",
+ compactDisplay: "long",
+ },
+ values: [
+ {value: 1e6, string: "1 Million", parts: [Integer("1"), Literal(" "), Compact("Million")]},
+ {value: 2e6, string: "2 Millionen", parts: [Integer("2"), Literal(" "), Compact("Millionen")]},
+ {value: 1e9, string: "1 Milliarde", parts: [Integer("1"), Literal(" "), Compact("Milliarde")]},
+ {value: 2e9, string: "2 Milliarden", parts: [Integer("2"), Literal(" "), Compact("Milliarden")]},
+ ],
+ },
+
+ // Digits are grouped in myriads (every 10,000) in Japanese.
+ {
+ locale: "ja",
+ options: {
+ notation: "compact",
+ compactDisplay: "long",
+ },
+ values: [
+ {value: 1, string: "1", parts: [Integer("1")]},
+ {value: 10, string: "10", parts: [Integer("10")]},
+ {value: 100, string: "100", parts: [Integer("100")]},
+ {value: 1000, string: "1000", parts: [Integer("1000")]},
+
+ {value: 10e3, string: "1\u4E07", parts: [Integer("1"), Compact("\u4E07")]},
+ {value: 100e3, string: "10\u4E07", parts: [Integer("10"), Compact("\u4E07")]},
+ {value: 1000e3, string: "100\u4E07", parts: [Integer("100"), Compact("\u4E07")]},
+ {value: 10000e3, string: "1000\u4E07", parts: [Integer("1000"), Compact("\u4E07")]},
+
+ {value: 10e7, string: "1\u5104", parts: [Integer("1"), Compact("\u5104")]},
+ {value: 100e7, string: "10\u5104", parts: [Integer("10"), Compact("\u5104")]},
+ {value: 1000e7, string: "100\u5104", parts: [Integer("100"), Compact("\u5104")]},
+ {value: 10000e7, string: "1000\u5104", parts: [Integer("1000"), Compact("\u5104")]},
+
+ {value: 10e11, string: "1\u5146", parts: [Integer("1"), Compact("\u5146")]},
+ {value: 100e11, string: "10\u5146", parts: [Integer("10"), Compact("\u5146")]},
+ {value: 1000e11, string: "100\u5146", parts: [Integer("100"), Compact("\u5146")]},
+ {value: 10000e11, string: "1000\u5146", parts: [Integer("1000"), Compact("\u5146")]},
+
+ {value: 100000e11, string: "10,000\u5146", parts: [Integer("10"), Group(","), Integer("000"), Compact("\u5146")]},
+ ],
+ },
+];
+
+runNumberFormattingTestcases(testcases);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/notation-compact-short.js b/js/src/tests/non262/Intl/NumberFormat/notation-compact-short.js
new file mode 100644
index 0000000000..f3a546294d
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/notation-compact-short.js
@@ -0,0 +1,136 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const {
+ Nan, Inf, Integer, MinusSign, PlusSign, Decimal, Fraction, Group, Literal,
+ Compact,
+} = NumberFormatParts;
+
+const testcases = [
+ {
+ locale: "en",
+ options: {
+ notation: "compact",
+ compactDisplay: "short",
+ },
+ values: [
+ {value: +0, string: "0", parts: [Integer("0")]},
+ {value: -0, string: "-0", parts: [MinusSign("-"), Integer("0")]},
+ {value: 0n, string: "0", parts: [Integer("0")]},
+
+ {value: 1, string: "1", parts: [Integer("1")]},
+ {value: 10, string: "10", parts: [Integer("10")]},
+ {value: 100, string: "100", parts: [Integer("100")]},
+ {value: 1000, string: "1K", parts: [Integer("1"), Compact("K")]},
+ {value: 10000, string: "10K", parts: [Integer("10"), Compact("K")]},
+ {value: 100000, string: "100K", parts: [Integer("100"), Compact("K")]},
+ {value: 1000000, string: "1M", parts: [Integer("1"), Compact("M")]},
+ {value: 10000000, string: "10M", parts: [Integer("10"), Compact("M")]},
+ {value: 100000000, string: "100M", parts: [Integer("100"), Compact("M")]},
+ {value: 1000000000, string: "1B", parts: [Integer("1"), Compact("B")]},
+ {value: 10000000000, string: "10B", parts: [Integer("10"), Compact("B")]},
+ {value: 100000000000, string: "100B", parts: [Integer("100"), Compact("B")]},
+ {value: 1000000000000, string: "1T", parts: [Integer("1"), Compact("T")]},
+ {value: 10000000000000, string: "10T", parts: [Integer("10"), Compact("T")]},
+ {value: 100000000000000, string: "100T", parts: [Integer("100"), Compact("T")]},
+ {value: 1000000000000000, string: "1000T", parts: [Integer("1000"), Compact("T")]},
+ {value: 10000000000000000, string: "10,000T", parts: [Integer("10"), Group(","), Integer("000"), Compact("T")]},
+ {value: 100000000000000000, string: "100,000T", parts: [Integer("100"), Group(","), Integer("000"), Compact("T")]},
+
+ {value: 1n, string: "1", parts: [Integer("1")]},
+ {value: 10n, string: "10", parts: [Integer("10")]},
+ {value: 100n, string: "100", parts: [Integer("100")]},
+ {value: 1000n, string: "1K", parts: [Integer("1"), Compact("K")]},
+ {value: 10000n, string: "10K", parts: [Integer("10"), Compact("K")]},
+ {value: 100000n, string: "100K", parts: [Integer("100"), Compact("K")]},
+ {value: 1000000n, string: "1M", parts: [Integer("1"), Compact("M")]},
+ {value: 10000000n, string: "10M", parts: [Integer("10"), Compact("M")]},
+ {value: 100000000n, string: "100M", parts: [Integer("100"), Compact("M")]},
+ {value: 1000000000n, string: "1B", parts: [Integer("1"), Compact("B")]},
+ {value: 10000000000n, string: "10B", parts: [Integer("10"), Compact("B")]},
+ {value: 100000000000n, string: "100B", parts: [Integer("100"), Compact("B")]},
+ {value: 1000000000000n, string: "1T", parts: [Integer("1"), Compact("T")]},
+ {value: 10000000000000n, string: "10T", parts: [Integer("10"), Compact("T")]},
+ {value: 100000000000000n, string: "100T", parts: [Integer("100"), Compact("T")]},
+ {value: 1000000000000000n, string: "1000T", parts: [Integer("1000"), Compact("T")]},
+ {value: 10000000000000000n, string: "10,000T", parts: [Integer("10"), Group(","), Integer("000"), Compact("T")]},
+ {value: 100000000000000000n, string: "100,000T", parts: [Integer("100"), Group(","), Integer("000"), Compact("T")]},
+
+ {value: 0.1, string: "0.1", parts: [Integer("0"), Decimal("."), Fraction("1")]},
+ {value: 0.01, string: "0.01", parts: [Integer("0"), Decimal("."), Fraction("01")]},
+ {value: 0.001, string: "0.001", parts: [Integer("0"), Decimal("."), Fraction("001")]},
+ {value: 0.0001, string: "0.0001", parts: [Integer("0"), Decimal("."), Fraction("0001")]},
+ {value: 0.00001, string: "0.00001", parts: [Integer("0"), Decimal("."), Fraction("00001")]},
+ {value: 0.000001, string: "0.000001", parts: [Integer("0"), Decimal("."), Fraction("000001")]},
+ {value: 0.0000001, string: "0.0000001", parts: [Integer("0"), Decimal("."), Fraction("0000001")]},
+
+ {value: 12, string: "12", parts: [Integer("12")]},
+ {value: 123, string: "123", parts: [Integer("123")]},
+ {value: 1234, string: "1.2K", parts: [Integer("1"), Decimal("."), Fraction("2"), Compact("K")]},
+ {value: 12345, string: "12K", parts: [Integer("12"), Compact("K")]},
+ {value: 123456, string: "123K", parts: [Integer("123"), Compact("K")]},
+ {value: 1234567, string: "1.2M", parts: [Integer("1"), Decimal("."), Fraction("2"), Compact("M")]},
+ {value: 12345678, string: "12M", parts: [Integer("12"), Compact("M")]},
+ {value: 123456789, string: "123M", parts: [Integer("123"), Compact("M")]},
+
+ {value: Infinity, string: "∞", parts: [Inf("∞")]},
+ {value: -Infinity, string: "-∞", parts: [MinusSign("-"), Inf("∞")]},
+
+ {value: NaN, string: "NaN", parts: [Nan("NaN")]},
+ {value: -NaN, string: "NaN", parts: [Nan("NaN")]},
+ ],
+ },
+
+ // Compact symbol can consist of multiple characters, e.g. when it's an abbreviation.
+ {
+ locale: "de",
+ options: {
+ notation: "compact",
+ compactDisplay: "short",
+ },
+ values: [
+ {value: 1e6, string: "1\u00A0Mio.", parts: [Integer("1"), Literal("\u00A0"), Compact("Mio.")]},
+ {value: 1e9, string: "1\u00A0Mrd.", parts: [Integer("1"), Literal("\u00A0"), Compact("Mrd.")]},
+ {value: 1e12, string: "1\u00A0Bio.", parts: [Integer("1"), Literal("\u00A0"), Compact("Bio.")]},
+
+ // CLDR doesn't support "Billiarde" (Brd.), Trillion (Trill.), etc.
+ {value: 1e15, string: "1000\u00A0Bio.", parts: [Integer("1000"), Literal("\u00A0"), Compact("Bio.")]},
+ ],
+ },
+
+ // Digits are grouped in myriads (every 10,000) in Chinese.
+ {
+ locale: "zh",
+ options: {
+ notation: "compact",
+ compactDisplay: "short",
+ },
+ values: [
+ {value: 1, string: "1", parts: [Integer("1")]},
+ {value: 10, string: "10", parts: [Integer("10")]},
+ {value: 100, string: "100", parts: [Integer("100")]},
+ {value: 1000, string: "1000", parts: [Integer("1000")]},
+
+ {value: 10e3, string: "1\u4E07", parts: [Integer("1"), Compact("\u4E07")]},
+ {value: 100e3, string: "10\u4E07", parts: [Integer("10"), Compact("\u4E07")]},
+ {value: 1000e3, string: "100\u4E07", parts: [Integer("100"), Compact("\u4E07")]},
+ {value: 10000e3, string: "1000\u4E07", parts: [Integer("1000"), Compact("\u4E07")]},
+
+ {value: 10e7, string: "1\u4EBF", parts: [Integer("1"), Compact("\u4EBF")]},
+ {value: 100e7, string: "10\u4EBF", parts: [Integer("10"), Compact("\u4EBF")]},
+ {value: 1000e7, string: "100\u4EBF", parts: [Integer("100"), Compact("\u4EBF")]},
+ {value: 10000e7, string: "1000\u4EBF", parts: [Integer("1000"), Compact("\u4EBF")]},
+
+ {value: 10e11, string: "1\u4E07\u4EBF", parts: [Integer("1"), Compact("\u4E07\u4EBF")]},
+ {value: 100e11, string: "10\u4E07\u4EBF", parts: [Integer("10"), Compact("\u4E07\u4EBF")]},
+ {value: 1000e11, string: "100\u4E07\u4EBF", parts: [Integer("100"), Compact("\u4E07\u4EBF")]},
+ {value: 10000e11, string: "1000\u4E07\u4EBF", parts: [Integer("1000"), Compact("\u4E07\u4EBF")]},
+
+ {value: 100000e11, string: "10,000\u4E07\u4EBF", parts: [Integer("10"), Group(","), Integer("000"), Compact("\u4E07\u4EBF")]},
+ ],
+ },
+];
+
+runNumberFormattingTestcases(testcases);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/notation-engineering.js b/js/src/tests/non262/Intl/NumberFormat/notation-engineering.js
new file mode 100644
index 0000000000..b4c0e19e53
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/notation-engineering.js
@@ -0,0 +1,136 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const {
+ Nan, Inf, Integer, MinusSign, PlusSign, Decimal, Fraction, Group,
+ ExponentSeparator, ExponentInteger, ExponentMinusSign,
+} = NumberFormatParts;
+
+const testcases = [
+ {
+ locale: "en",
+ options: {
+ notation: "engineering",
+ },
+ values: [
+ {value: +0, string: "0E0", parts: [Integer("0"), ExponentSeparator("E"), ExponentInteger("0")]},
+ {value: -0, string: "-0E0", parts: [MinusSign("-"), Integer("0"), ExponentSeparator("E"), ExponentInteger("0")]},
+ {value: 0n, string: "0E0", parts: [Integer("0"), ExponentSeparator("E"), ExponentInteger("0")]},
+
+ {value: 1, string: "1E0", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("0")]},
+ {value: 10, string: "10E0", parts: [Integer("10"), ExponentSeparator("E"), ExponentInteger("0")]},
+ {value: 100, string: "100E0", parts: [Integer("100"), ExponentSeparator("E"), ExponentInteger("0")]},
+ {value: 1000, string: "1E3", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("3")]},
+ {value: 10000, string: "10E3", parts: [Integer("10"), ExponentSeparator("E"), ExponentInteger("3")]},
+ {value: 100000, string: "100E3", parts: [Integer("100"), ExponentSeparator("E"), ExponentInteger("3")]},
+ {value: 1000000, string: "1E6", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("6")]},
+
+ {value: 1n, string: "1E0", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("0")]},
+ {value: 10n, string: "10E0", parts: [Integer("10"), ExponentSeparator("E"), ExponentInteger("0")]},
+ {value: 100n, string: "100E0", parts: [Integer("100"), ExponentSeparator("E"), ExponentInteger("0")]},
+ {value: 1000n, string: "1E3", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("3")]},
+ {value: 10000n, string: "10E3", parts: [Integer("10"), ExponentSeparator("E"), ExponentInteger("3")]},
+ {value: 100000n, string: "100E3", parts: [Integer("100"), ExponentSeparator("E"), ExponentInteger("3")]},
+ {value: 1000000n, string: "1E6", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("6")]},
+
+ {value: 0.1, string: "100E-3", parts: [Integer("100"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("3")]},
+ {value: 0.01, string: "10E-3", parts: [Integer("10"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("3")]},
+ {value: 0.001, string: "1E-3", parts: [Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("3")]},
+ {value: 0.0001, string: "100E-6", parts: [Integer("100"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("6")]},
+ {value: 0.00001, string: "10E-6", parts: [Integer("10"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("6")]},
+ {value: 0.000001, string: "1E-6", parts: [Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("6")]},
+ {value: 0.0000001, string: "100E-9", parts: [Integer("100"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("9")]},
+
+ {value: Infinity, string: "∞", parts: [Inf("∞")]},
+ {value: -Infinity, string: "-∞", parts: [MinusSign("-"), Inf("∞")]},
+
+ {value: NaN, string: "NaN", parts: [Nan("NaN")]},
+ {value: -NaN, string: "NaN", parts: [Nan("NaN")]},
+ ],
+ },
+
+ // Exponent modifications take place early, so while in the "standard" notation
+ // `Intl.NumberFormat("en", {maximumFractionDigits: 0}).format(0.1)` returns "0", for
+ // "engineering" notation the result string is not "0", but instead "100E-3".
+
+ {
+ locale: "en",
+ options: {
+ notation: "engineering",
+ maximumFractionDigits: 0,
+ },
+ values: [
+ {value: 0.1, string: "100E-3", parts: [
+ Integer("100"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("3")
+ ]},
+ ],
+ },
+
+ {
+ locale: "en",
+ options: {
+ notation: "engineering",
+ minimumFractionDigits: 4,
+ },
+ values: [
+ {value: 10, string: "10.0000E0", parts: [
+ Integer("10"), Decimal("."), Fraction("0000"), ExponentSeparator("E"), ExponentInteger("0")
+ ]},
+ {value: 0.1, string: "100.0000E-3", parts: [
+ Integer("100"), Decimal("."), Fraction("0000"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("3")
+ ]},
+ ],
+ },
+
+ {
+ locale: "en",
+ options: {
+ notation: "engineering",
+ minimumIntegerDigits: 4,
+ },
+ values: [
+ {value: 10, string: "0,010E0", parts: [
+ Integer("0"), Group(","), Integer("010"), ExponentSeparator("E"), ExponentInteger("0")
+ ]},
+ {value: 0.1, string: "0,100E-3", parts: [
+ Integer("0"), Group(","), Integer("100"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("3")
+ ]},
+ ],
+ },
+
+ {
+ locale: "en",
+ options: {
+ notation: "engineering",
+ minimumSignificantDigits: 4,
+ },
+ values: [
+ {value: 10, string: "10.00E0", parts: [
+ Integer("10"), Decimal("."), Fraction("00"), ExponentSeparator("E"), ExponentInteger("0")
+ ]},
+ {value: 0.1, string: "100.0E-3", parts: [
+ Integer("100"), Decimal("."), Fraction("0"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("3")
+ ]},
+ ],
+ },
+
+ {
+ locale: "en",
+ options: {
+ notation: "engineering",
+ maximumSignificantDigits: 1,
+ },
+ values: [
+ {value: 12, string: "10E0", parts: [
+ Integer("10"), ExponentSeparator("E"), ExponentInteger("0")
+ ]},
+ {value: 0.12, string: "100E-3", parts: [
+ Integer("100"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("3")
+ ]},
+ ],
+ },
+];
+
+runNumberFormattingTestcases(testcases);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/notation-scientific.js b/js/src/tests/non262/Intl/NumberFormat/notation-scientific.js
new file mode 100644
index 0000000000..f14a660129
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/notation-scientific.js
@@ -0,0 +1,136 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const {
+ Nan, Inf, Integer, MinusSign, PlusSign, Decimal, Fraction, Group,
+ ExponentSeparator, ExponentInteger, ExponentMinusSign,
+} = NumberFormatParts;
+
+const testcases = [
+ {
+ locale: "en",
+ options: {
+ notation: "scientific",
+ },
+ values: [
+ {value: +0, string: "0E0", parts: [Integer("0"), ExponentSeparator("E"), ExponentInteger("0")]},
+ {value: -0, string: "-0E0", parts: [MinusSign("-"), Integer("0"), ExponentSeparator("E"), ExponentInteger("0")]},
+ {value: 0n, string: "0E0", parts: [Integer("0"), ExponentSeparator("E"), ExponentInteger("0")]},
+
+ {value: 1, string: "1E0", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("0")]},
+ {value: 10, string: "1E1", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("1")]},
+ {value: 100, string: "1E2", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("2")]},
+ {value: 1000, string: "1E3", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("3")]},
+ {value: 10000, string: "1E4", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("4")]},
+ {value: 100000, string: "1E5", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("5")]},
+ {value: 1000000, string: "1E6", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("6")]},
+
+ {value: 1n, string: "1E0", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("0")]},
+ {value: 10n, string: "1E1", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("1")]},
+ {value: 100n, string: "1E2", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("2")]},
+ {value: 1000n, string: "1E3", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("3")]},
+ {value: 10000n, string: "1E4", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("4")]},
+ {value: 100000n, string: "1E5", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("5")]},
+ {value: 1000000n, string: "1E6", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("6")]},
+
+ {value: 0.1, string: "1E-1", parts: [Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("1")]},
+ {value: 0.01, string: "1E-2", parts: [Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("2")]},
+ {value: 0.001, string: "1E-3", parts: [Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("3")]},
+ {value: 0.0001, string: "1E-4", parts: [Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("4")]},
+ {value: 0.00001, string: "1E-5", parts: [Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("5")]},
+ {value: 0.000001, string: "1E-6", parts: [Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("6")]},
+ {value: 0.0000001, string: "1E-7", parts: [Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("7")]},
+
+ {value: Infinity, string: "∞", parts: [Inf("∞")]},
+ {value: -Infinity, string: "-∞", parts: [MinusSign("-"), Inf("∞")]},
+
+ {value: NaN, string: "NaN", parts: [Nan("NaN")]},
+ {value: -NaN, string: "NaN", parts: [Nan("NaN")]},
+ ],
+ },
+
+ // Exponent modifications take place early, so while in the "standard" notation
+ // `Intl.NumberFormat("en", {maximumFractionDigits: 0}).format(0.1)` returns "0", for
+ // "scientific" notation the result string is not "0", but instead "1E-1".
+
+ {
+ locale: "en",
+ options: {
+ notation: "scientific",
+ maximumFractionDigits: 0,
+ },
+ values: [
+ {value: 0.1, string: "1E-1", parts: [
+ Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("1")
+ ]},
+ ],
+ },
+
+ {
+ locale: "en",
+ options: {
+ notation: "scientific",
+ minimumFractionDigits: 4,
+ },
+ values: [
+ {value: 10, string: "1.0000E1", parts: [
+ Integer("1"), Decimal("."), Fraction("0000"), ExponentSeparator("E"), ExponentInteger("1")
+ ]},
+ {value: 0.1, string: "1.0000E-1", parts: [
+ Integer("1"), Decimal("."), Fraction("0000"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("1")
+ ]},
+ ],
+ },
+
+ {
+ locale: "en",
+ options: {
+ notation: "scientific",
+ minimumIntegerDigits: 4,
+ },
+ values: [
+ {value: 10, string: "0,001E1", parts: [
+ Integer("0"), Group(","), Integer("001"), ExponentSeparator("E"), ExponentInteger("1")
+ ]},
+ {value: 0.1, string: "0,001E-1", parts: [
+ Integer("0"), Group(","), Integer("001"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("1")
+ ]},
+ ],
+ },
+
+ {
+ locale: "en",
+ options: {
+ notation: "scientific",
+ minimumSignificantDigits: 4,
+ },
+ values: [
+ {value: 10, string: "1.000E1", parts: [
+ Integer("1"), Decimal("."), Fraction("000"), ExponentSeparator("E"), ExponentInteger("1")
+ ]},
+ {value: 0.1, string: "1.000E-1", parts: [
+ Integer("1"), Decimal("."), Fraction("000"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("1")
+ ]},
+ ],
+ },
+
+ {
+ locale: "en",
+ options: {
+ notation: "scientific",
+ maximumSignificantDigits: 1,
+ },
+ values: [
+ {value: 12, string: "1E1", parts: [
+ Integer("1"), ExponentSeparator("E"), ExponentInteger("1")
+ ]},
+ {value: 0.12, string: "1E-1", parts: [
+ Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("1")
+ ]},
+ ],
+ },
+];
+
+runNumberFormattingTestcases(testcases);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/numberingSystem-format.js b/js/src/tests/non262/Intl/NumberFormat/numberingSystem-format.js
new file mode 100644
index 0000000000..c0b9ba78ed
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/numberingSystem-format.js
@@ -0,0 +1,18 @@
+for (let [numberingSystem, {digits, algorithmic}] of Object.entries(numberingSystems)) {
+ if (algorithmic) {
+ // We don't yet support algorithmic numbering systems.
+ continue;
+ }
+
+ let nf = new Intl.NumberFormat("en", {numberingSystem});
+
+ assertEq([...digits].length, 10, "expect exactly ten digits for each numbering system");
+
+ let i = 0;
+ for (let digit of digits) {
+ assertEq(nf.format(i++), digit);
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/numberingSystem-option.js b/js/src/tests/non262/Intl/NumberFormat/numberingSystem-option.js
new file mode 100644
index 0000000000..4624ee5192
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/numberingSystem-option.js
@@ -0,0 +1,60 @@
+const defaultLocale = "en";
+const defaultNumberingSystem = new Intl.NumberFormat(defaultLocale).resolvedOptions().numberingSystem;
+
+function createWithLocale(locale, numberingSystem) {
+ return new Intl.NumberFormat(locale, {numberingSystem});
+}
+
+function create(numberingSystem) {
+ return createWithLocale(defaultLocale, numberingSystem);
+}
+
+// Empty string should throw.
+assertThrowsInstanceOf(() => create(""), RangeError);
+
+// Trailing \0 should throw.
+assertThrowsInstanceOf(() => create("latn\0"), RangeError);
+
+// Too short or too long strings should throw.
+assertThrowsInstanceOf(() => create("a"), RangeError);
+assertThrowsInstanceOf(() => create("toolongstring"), RangeError);
+
+// Throw even when prefix is valid.
+assertThrowsInstanceOf(() => create("latn-toolongstring"), RangeError);
+
+// |numberingSystem| can be set to |undefined|.
+let nf = create(undefined);
+assertEq(nf.resolvedOptions().numberingSystem, defaultNumberingSystem);
+
+// Unsupported numbering systems are ignored.
+nf = create("xxxxxxxx");
+assertEq(nf.resolvedOptions().numberingSystem, defaultNumberingSystem);
+
+// Numbering system in options overwrite Unicode extension keyword.
+nf = createWithLocale(`${defaultLocale}-u-nu-thai`, "arab");
+assertEq(nf.resolvedOptions().locale, defaultLocale);
+assertEq(nf.resolvedOptions().numberingSystem, "arab");
+
+// |numberingSystem| option ignores case.
+nf = create("ARAB");
+assertEq(nf.resolvedOptions().locale, defaultLocale);
+assertEq(nf.resolvedOptions().numberingSystem, "arab");
+
+for (let [numberingSystem, {algorithmic}] of Object.entries(numberingSystems)) {
+ let nf1 = new Intl.NumberFormat(`${defaultLocale}-u-nu-${numberingSystem}`);
+ let nf2 = new Intl.NumberFormat(defaultLocale, {numberingSystem});
+
+ if (!algorithmic) {
+ assertEq(nf1.resolvedOptions().numberingSystem, numberingSystem);
+ assertEq(nf2.resolvedOptions().numberingSystem, numberingSystem);
+ } else {
+ // We don't yet support algorithmic numbering systems.
+ assertEq(nf1.resolvedOptions().numberingSystem, defaultNumberingSystem);
+ assertEq(nf2.resolvedOptions().numberingSystem, defaultNumberingSystem);
+ }
+
+ assertEq(nf2.format(0), nf1.format(0));
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/options-emulate-undefined.js b/js/src/tests/non262/Intl/NumberFormat/options-emulate-undefined.js
new file mode 100644
index 0000000000..ddb2d61350
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/options-emulate-undefined.js
@@ -0,0 +1,13 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// createIsHTMLDDA is only available when running tests in the shell,
+// not the browser
+if (typeof createIsHTMLDDA === "function") {
+ let nf = new Intl.NumberFormat('en-US', createIsHTMLDDA());
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/remove-unicode-extensions.js b/js/src/tests/non262/Intl/NumberFormat/remove-unicode-extensions.js
new file mode 100644
index 0000000000..87cfc317cb
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/remove-unicode-extensions.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Locale processing is supposed to internally remove any Unicode extension
+// sequences in the locale. Test that various weird testcases invoking
+// algorithmic edge cases don't assert or throw exceptions.
+
+var weirdCases =
+ [
+ "en-x-u-foo",
+ "en-a-bar-x-u-foo",
+ "en-x-u-foo-a-bar",
+ "en-a-bar-u-baz-x-u-foo",
+ ];
+
+for (var locale of weirdCases)
+ Intl.NumberFormat(locale).format(5);
+
+assertThrowsInstanceOf(() => Intl.NumberFormat("x-u-foo"), RangeError);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/shell.js b/js/src/tests/non262/Intl/NumberFormat/shell.js
new file mode 100644
index 0000000000..da786bbcc2
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/shell.js
@@ -0,0 +1,48 @@
+function GenericPartCreator(type) {
+ return str => ({ type, value: str });
+}
+
+const NumberFormatParts = {
+ Nan: GenericPartCreator("nan"),
+ Inf: GenericPartCreator("infinity"),
+ Integer: GenericPartCreator("integer"),
+ Group: GenericPartCreator("group"),
+ Decimal: GenericPartCreator("decimal"),
+ Fraction: GenericPartCreator("fraction"),
+ MinusSign: GenericPartCreator("minusSign"),
+ PlusSign: GenericPartCreator("plusSign"),
+ PercentSign: GenericPartCreator("percentSign"),
+ Currency: GenericPartCreator("currency"),
+ Literal: GenericPartCreator("literal"),
+ ExponentSeparator: GenericPartCreator("exponentSeparator"),
+ ExponentMinusSign: GenericPartCreator("exponentMinusSign"),
+ ExponentInteger: GenericPartCreator("exponentInteger"),
+ Compact: GenericPartCreator("compact"),
+ Unit: GenericPartCreator("unit"),
+};
+
+function assertParts(nf, x, expected) {
+ var parts = nf.formatToParts(x);
+ assertEq(parts.map(part => part.value).join(""), nf.format(x),
+ "formatToParts and format must agree");
+
+ var len = parts.length;
+ assertEq(len, expected.length, "parts count mismatch");
+ for (var i = 0; i < len; i++) {
+ assertEq(parts[i].type, expected[i].type, "type mismatch at " + i);
+ assertEq(parts[i].value, expected[i].value, "value mismatch at " + i);
+ }
+}
+
+function runNumberFormattingTestcases(testcases) {
+ for (let {locale, options, values} of testcases) {
+ let nf = new Intl.NumberFormat(locale, options);
+
+ for (let {value, string, parts} of values) {
+ assertEq(nf.format(value), string,
+ `locale=${locale}, options=${JSON.stringify(options)}, value=${value}`);
+
+ assertParts(nf, value, parts);
+ }
+ }
+}
diff --git a/js/src/tests/non262/Intl/NumberFormat/sign-display.js b/js/src/tests/non262/Intl/NumberFormat/sign-display.js
new file mode 100644
index 0000000000..b18517d847
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/sign-display.js
@@ -0,0 +1,187 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const {
+ Nan, Inf, Integer, MinusSign, PlusSign, Decimal, Fraction
+} = NumberFormatParts;
+
+const testcases = [
+ // "auto": Show the sign on negative numbers only.
+ {
+ locale: "en",
+ options: {
+ signDisplay: "auto",
+ },
+ values: [
+ {value: +0, string: "0", parts: [Integer("0")]},
+ {value: -0, string: "-0", parts: [MinusSign("-"), Integer("0")]},
+ {value: 0n, string: "0", parts: [Integer("0")]},
+
+ {value: 1, string: "1", parts: [Integer("1")]},
+ {value: -1, string: "-1", parts: [MinusSign("-"), Integer("1")]},
+ {value: 1n, string: "1", parts: [Integer("1")]},
+ {value: -1n, string: "-1", parts: [MinusSign("-"), Integer("1")]},
+
+ {value: 0.1, string: "0.1", parts: [Integer("0"), Decimal("."), Fraction("1")]},
+ {value: -0.1, string: "-0.1", parts: [MinusSign("-"), Integer("0"), Decimal("."), Fraction("1")]},
+
+ {value: 0.9, string: "0.9", parts: [Integer("0"), Decimal("."), Fraction("9")]},
+ {value: -0.9, string: "-0.9", parts: [MinusSign("-"), Integer("0"), Decimal("."), Fraction("9")]},
+
+ {value: Infinity, string: "∞", parts: [Inf("∞")]},
+ {value: -Infinity, string: "-∞", parts: [MinusSign("-"), Inf("∞")]},
+
+ {value: NaN, string: "NaN", parts: [Nan("NaN")]},
+ {value: -NaN, string: "NaN", parts: [Nan("NaN")]},
+ ],
+ },
+
+ // "never": Show the sign on neither positive nor negative numbers.
+ {
+ locale: "en",
+ options: {
+ signDisplay: "never",
+ },
+ values: [
+ {value: +0, string: "0", parts: [Integer("0")]},
+ {value: -0, string: "0", parts: [Integer("0")]},
+ {value: 0n, string: "0", parts: [Integer("0")]},
+
+ {value: 1, string: "1", parts: [Integer("1")]},
+ {value: -1, string: "1", parts: [Integer("1")]},
+ {value: 1n, string: "1", parts: [Integer("1")]},
+ {value: -1n, string: "1", parts: [Integer("1")]},
+
+ {value: 0.1, string: "0.1", parts: [Integer("0"), Decimal("."), Fraction("1")]},
+ {value: -0.1, string: "0.1", parts: [Integer("0"), Decimal("."), Fraction("1")]},
+
+ {value: 0.9, string: "0.9", parts: [Integer("0"), Decimal("."), Fraction("9")]},
+ {value: -0.9, string: "0.9", parts: [Integer("0"), Decimal("."), Fraction("9")]},
+
+ {value: Infinity, string: "∞", parts: [Inf("∞")]},
+ {value: -Infinity, string: "∞", parts: [Inf("∞")]},
+
+ {value: NaN, string: "NaN", parts: [Nan("NaN")]},
+ {value: -NaN, string: "NaN", parts: [Nan("NaN")]},
+ ],
+ },
+
+ // "always": Show the sign on positive and negative numbers including zero.
+ {
+ locale: "en",
+ options: {
+ signDisplay: "always",
+ },
+ values: [
+ {value: +0, string: "+0", parts: [PlusSign("+"), Integer("0")]},
+ {value: -0, string: "-0", parts: [MinusSign("-"), Integer("0")]},
+ {value: 0n, string: "+0", parts: [PlusSign("+"), Integer("0")]},
+
+ {value: 1, string: "+1", parts: [PlusSign("+"), Integer("1")]},
+ {value: -1, string: "-1", parts: [MinusSign("-"), Integer("1")]},
+ {value: 1n, string: "+1", parts: [PlusSign("+"), Integer("1")]},
+ {value: -1n, string: "-1", parts: [MinusSign("-"), Integer("1")]},
+
+ {value: 0.1, string: "+0.1", parts: [PlusSign("+"), Integer("0"), Decimal("."), Fraction("1")]},
+ {value: -0.1, string: "-0.1", parts: [MinusSign("-"), Integer("0"), Decimal("."), Fraction("1")]},
+
+ {value: 0.9, string: "+0.9", parts: [PlusSign("+"), Integer("0"), Decimal("."), Fraction("9")]},
+ {value: -0.9, string: "-0.9", parts: [MinusSign("-"), Integer("0"), Decimal("."), Fraction("9")]},
+
+ {value: Infinity, string: "+∞", parts: [PlusSign("+"), Inf("∞")]},
+ {value: -Infinity, string: "-∞", parts: [MinusSign("-"), Inf("∞")]},
+
+ {value: NaN, string: "+NaN", parts: [PlusSign("+"), Nan("NaN")]},
+ {value: -NaN, string: "+NaN", parts: [PlusSign("+"), Nan("NaN")]},
+ ],
+ },
+
+ // "exceptZero": Show the sign on positive and negative numbers but not zero.
+ {
+ locale: "en",
+ options: {
+ signDisplay: "exceptZero",
+ },
+ values: [
+ {value: +0, string: "0", parts: [Integer("0")]},
+ {value: -0, string: "0", parts: [Integer("0")]},
+ {value: 0n, string: "0", parts: [Integer("0")]},
+
+ {value: 1, string: "+1", parts: [PlusSign("+"), Integer("1")]},
+ {value: -1, string: "-1", parts: [MinusSign("-"), Integer("1")]},
+ {value: 1n, string: "+1", parts: [PlusSign("+"), Integer("1")]},
+ {value: -1n, string: "-1", parts: [MinusSign("-"), Integer("1")]},
+
+ {value: 0.1, string: "+0.1", parts: [PlusSign("+"), Integer("0"), Decimal("."), Fraction("1")]},
+ {value: -0.1, string: "-0.1", parts: [MinusSign("-"), Integer("0"), Decimal("."), Fraction("1")]},
+
+ {value: 0.9, string: "+0.9", parts: [PlusSign("+"), Integer("0"), Decimal("."), Fraction("9")]},
+ {value: -0.9, string: "-0.9", parts: [MinusSign("-"), Integer("0"), Decimal("."), Fraction("9")]},
+
+ {value: Infinity, string: "+∞", parts: [PlusSign("+"), Inf("∞")]},
+ {value: -Infinity, string: "-∞", parts: [MinusSign("-"), Inf("∞")]},
+
+ {value: NaN, string: "NaN", parts: [Nan("NaN")]},
+ {value: -NaN, string: "NaN", parts: [Nan("NaN")]},
+ ],
+ },
+
+ // Tests with suppressed fractional digits.
+
+ // "auto": Show the sign on negative numbers only.
+ {
+ locale: "en",
+ options: {
+ signDisplay: "auto",
+ maximumFractionDigits: 0,
+ },
+ values: [
+ {value: +0.1, string: "0", parts: [Integer("0")]},
+ {value: -0.1, string: "-0", parts: [MinusSign("-"), Integer("0")]},
+ ],
+ },
+
+ // "never": Show the sign on neither positive nor negative numbers.
+ {
+ locale: "en",
+ options: {
+ signDisplay: "never",
+ maximumFractionDigits: 0,
+ },
+ values: [
+ {value: +0.1, string: "0", parts: [Integer("0")]},
+ {value: -0.1, string: "0", parts: [Integer("0")]},
+ ],
+ },
+
+ // "always": Show the sign on positive and negative numbers including zero.
+ {
+ locale: "en",
+ options: {
+ signDisplay: "always",
+ maximumFractionDigits: 0,
+ },
+ values: [
+ {value: +0.1, string: "+0", parts: [PlusSign("+"), Integer("0")]},
+ {value: -0.1, string: "-0", parts: [MinusSign("-"), Integer("0")]},
+ ],
+ },
+
+ // "exceptZero": Show the sign on positive and negative numbers but not zero.
+ {
+ locale: "en",
+ options: {
+ signDisplay: "exceptZero",
+ maximumFractionDigits: 0,
+ },
+
+ values: [
+ {value: +0.1, string: "0", parts: [Integer("0")]},
+ {value: -0.1, string: "0", parts: [Integer("0")]},
+ ],
+ }
+];
+
+runNumberFormattingTestcases(testcases);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/significantDigitsOfZero.js b/js/src/tests/non262/Intl/NumberFormat/significantDigitsOfZero.js
new file mode 100644
index 0000000000..c569313266
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/significantDigitsOfZero.js
@@ -0,0 +1,38 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+// -- test that NumberFormat correctly formats 0 with various numbers of significant digits
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var testData = [
+ {minimumSignificantDigits: 1, maximumSignificantDigits: 1, expected: "0"},
+ {minimumSignificantDigits: 1, maximumSignificantDigits: 2, expected: "0"},
+ {minimumSignificantDigits: 1, maximumSignificantDigits: 3, expected: "0"},
+ {minimumSignificantDigits: 1, maximumSignificantDigits: 4, expected: "0"},
+ {minimumSignificantDigits: 1, maximumSignificantDigits: 5, expected: "0"},
+ {minimumSignificantDigits: 2, maximumSignificantDigits: 2, expected: "0.0"},
+ {minimumSignificantDigits: 2, maximumSignificantDigits: 3, expected: "0.0"},
+ {minimumSignificantDigits: 2, maximumSignificantDigits: 4, expected: "0.0"},
+ {minimumSignificantDigits: 2, maximumSignificantDigits: 5, expected: "0.0"},
+ {minimumSignificantDigits: 3, maximumSignificantDigits: 3, expected: "0.00"},
+ {minimumSignificantDigits: 3, maximumSignificantDigits: 4, expected: "0.00"},
+ {minimumSignificantDigits: 3, maximumSignificantDigits: 5, expected: "0.00"},
+];
+
+for (var i = 0; i < testData.length; i++) {
+ var min = testData[i].minimumSignificantDigits;
+ var max = testData[i].maximumSignificantDigits;
+ var options = {minimumSignificantDigits: min, maximumSignificantDigits: max};
+ var format = new Intl.NumberFormat("en-US", options);
+ var actual = format.format(0);
+ var expected = testData[i].expected;
+ assertEq(actual, expected,
+ "Wrong formatted string for 0 with " +
+ "minimumSignificantDigits " + min +
+ ", maximumSignificantDigits " + max +
+ ": expected \"" + expected +
+ "\", actual \"" + actual + "\"");
+}
+
+reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/supportedLocalesOf.js b/js/src/tests/non262/Intl/NumberFormat/supportedLocalesOf.js
new file mode 100644
index 0000000000..5ba4470a98
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/supportedLocalesOf.js
@@ -0,0 +1,371 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||xulRuntime.shell)
+// -- test in browser only that ICU has locale data for all Mozilla languages
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This array contains the locales that ICU supports in
+// number formatting whose languages Mozilla localizes Firefox into.
+// Current as of ICU 50.1.2 and Firefox March 2013.
+var locales = [
+ "af",
+ "af-NA",
+ "af-ZA",
+ "ar",
+ "ar-001",
+ "ar-AE",
+ "ar-BH",
+ "ar-DJ",
+ "ar-DZ",
+ "ar-EG",
+ "ar-EH",
+ "ar-ER",
+ "ar-IL",
+ "ar-IQ",
+ "ar-JO",
+ "ar-KM",
+ "ar-KW",
+ "ar-LB",
+ "ar-LY",
+ "ar-MA",
+ "ar-MR",
+ "ar-OM",
+ "ar-PS",
+ "ar-QA",
+ "ar-SA",
+ "ar-SD",
+ "ar-SO",
+ "ar-SY",
+ "ar-TD",
+ "ar-TN",
+ "ar-YE",
+ "as",
+ "as-IN",
+ "be",
+ "be-BY",
+ "bg",
+ "bg-BG",
+ "bn",
+ "bn-BD",
+ "bn-IN",
+ "br",
+ "br-FR",
+ "bs",
+ "bs-Cyrl",
+ "bs-Cyrl-BA",
+ "bs-Latn",
+ "bs-Latn-BA",
+ "ca",
+ "ca-AD",
+ "ca-ES",
+ "cs",
+ "cs-CZ",
+ "cy",
+ "cy-GB",
+ "da",
+ "da-DK",
+ "de",
+ "de-AT",
+ "de-BE",
+ "de-CH",
+ "de-DE",
+ "de-LI",
+ "de-LU",
+ "el",
+ "el-CY",
+ "el-GR",
+ "en",
+ "en-150",
+ "en-AG",
+ "en-AS",
+ "en-AU",
+ "en-BB",
+ "en-BE",
+ "en-BM",
+ "en-BS",
+ "en-BW",
+ "en-BZ",
+ "en-CA",
+ "en-CM",
+ "en-DM",
+ "en-FJ",
+ "en-FM",
+ "en-GB",
+ "en-GD",
+ "en-GG",
+ "en-GH",
+ "en-GI",
+ "en-GM",
+ "en-GU",
+ "en-GY",
+ "en-HK",
+ "en-IE",
+ "en-IM",
+ "en-IN",
+ "en-JE",
+ "en-JM",
+ "en-KE",
+ "en-KI",
+ "en-KN",
+ "en-KY",
+ "en-LC",
+ "en-LR",
+ "en-LS",
+ "en-MG",
+ "en-MH",
+ "en-MP",
+ "en-MT",
+ "en-MU",
+ "en-MW",
+ "en-NA",
+ "en-NG",
+ "en-NZ",
+ "en-PG",
+ "en-PH",
+ "en-PK",
+ "en-PR",
+ "en-PW",
+ "en-SB",
+ "en-SC",
+ "en-SG",
+ "en-SL",
+ "en-SS",
+ "en-SZ",
+ "en-TC",
+ "en-TO",
+ "en-TT",
+ "en-TZ",
+ "en-UG",
+ "en-UM",
+ "en-US",
+ "en-US-POSIX",
+ "en-VC",
+ "en-VG",
+ "en-VI",
+ "en-VU",
+ "en-WS",
+ "en-ZA",
+ "en-ZM",
+ "en-ZW",
+ "eo",
+ "es",
+ "es-419",
+ "es-AR",
+ "es-BO",
+ "es-CL",
+ "es-CO",
+ "es-CR",
+ "es-CU",
+ "es-DO",
+ "es-EA",
+ "es-EC",
+ "es-ES",
+ "es-GQ",
+ "es-GT",
+ "es-HN",
+ "es-IC",
+ "es-MX",
+ "es-NI",
+ "es-PA",
+ "es-PE",
+ "es-PH",
+ "es-PR",
+ "es-PY",
+ "es-SV",
+ "es-US",
+ "es-UY",
+ "es-VE",
+ "et",
+ "et-EE",
+ "eu",
+ "eu-ES",
+ "fa",
+ "fa-AF",
+ "fa-IR",
+ "ff",
+ "ff-SN",
+ "fi",
+ "fi-FI",
+ "fr",
+ "fr-BE",
+ "fr-BF",
+ "fr-BI",
+ "fr-BJ",
+ "fr-BL",
+ "fr-CA",
+ "fr-CD",
+ "fr-CF",
+ "fr-CG",
+ "fr-CH",
+ "fr-CI",
+ "fr-CM",
+ "fr-DJ",
+ "fr-DZ",
+ "fr-FR",
+ "fr-GA",
+ "fr-GF",
+ "fr-GN",
+ "fr-GP",
+ "fr-GQ",
+ "fr-HT",
+ "fr-KM",
+ "fr-LU",
+ "fr-MA",
+ "fr-MC",
+ "fr-MF",
+ "fr-MG",
+ "fr-ML",
+ "fr-MQ",
+ "fr-MR",
+ "fr-MU",
+ "fr-NC",
+ "fr-NE",
+ "fr-PF",
+ "fr-RE",
+ "fr-RW",
+ "fr-SC",
+ "fr-SN",
+ "fr-SY",
+ "fr-TD",
+ "fr-TG",
+ "fr-TN",
+ "fr-VU",
+ "fr-YT",
+ "ga",
+ "ga-IE",
+ "gl",
+ "gl-ES",
+ "gu",
+ "gu-IN",
+ "he",
+ "he-IL",
+ "hi",
+ "hi-IN",
+ "hr",
+ "hr-BA",
+ "hr-HR",
+ "hu",
+ "hu-HU",
+ "hy",
+ "hy-AM",
+ "id",
+ "id-ID",
+ "is",
+ "is-IS",
+ "it",
+ "it-CH",
+ "it-IT",
+ "it-SM",
+ "ja",
+ "ja-JP",
+ "kk",
+ "kk-Cyrl",
+ "kk-Cyrl-KZ",
+ "km",
+ "km-KH",
+ "kn",
+ "kn-IN",
+ "ko",
+ "ko-KP",
+ "ko-KR",
+ "lt",
+ "lt-LT",
+ "lv",
+ "lv-LV",
+ "mk",
+ "mk-MK",
+ "ml",
+ "ml-IN",
+ "mr",
+ "mr-IN",
+ "nb",
+ "nb-NO",
+ "nl",
+ "nl-AW",
+ "nl-BE",
+ "nl-CW",
+ "nl-NL",
+ "nl-SR",
+ "nl-SX",
+ "nn",
+ "nn-NO",
+ "or",
+ "or-IN",
+ "pa",
+ "pa-Arab",
+ "pa-Arab-PK",
+ "pa-Guru",
+ "pa-Guru-IN",
+ "pl",
+ "pl-PL",
+ "pt",
+ "pt-AO",
+ "pt-BR",
+ "pt-CV",
+ "pt-GW",
+ "pt-MO",
+ "pt-MZ",
+ "pt-PT",
+ "pt-ST",
+ "pt-TL",
+ "rm",
+ "rm-CH",
+ "ro",
+ "ro-MD",
+ "ro-RO",
+ "ru",
+ "ru-BY",
+ "ru-KG",
+ "ru-KZ",
+ "ru-MD",
+ "ru-RU",
+ "ru-UA",
+ "si",
+ "si-LK",
+ "sk",
+ "sk-SK",
+ "sl",
+ "sl-SI",
+ "sq",
+ "sq-AL",
+ "sq-MK",
+ "sr",
+ "sr-Cyrl",
+ "sr-Cyrl-BA",
+ "sr-Cyrl-ME",
+ "sr-Cyrl-RS",
+ "sr-Latn",
+ "sr-Latn-BA",
+ "sr-Latn-ME",
+ "sr-Latn-RS",
+ "sv",
+ "sv-AX",
+ "sv-FI",
+ "sv-SE",
+ "te",
+ "te-IN",
+ "th",
+ "th-TH",
+ "tr",
+ "tr-CY",
+ "tr-TR",
+ "uk",
+ "uk-UA",
+ "vi",
+ "vi-VN",
+ "zh",
+ "zh-Hans",
+ "zh-Hans-CN",
+ "zh-Hans-HK",
+ "zh-Hans-MO",
+ "zh-Hans-SG",
+ "zh-Hant",
+ "zh-Hant-HK",
+ "zh-Hant-MO",
+ "zh-Hant-TW",
+];
+
+var count = Intl.NumberFormat.supportedLocalesOf(locales).length;
+
+reportCompare(locales.length, count, "Number of supported locales in Intl.NumberFormat");
diff --git a/js/src/tests/non262/Intl/NumberFormat/toStringTag.js b/js/src/tests/non262/Intl/NumberFormat/toStringTag.js
new file mode 100644
index 0000000000..9ad0901a0b
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/toStringTag.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var desc = Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, Symbol.toStringTag);
+
+assertEq(desc !== undefined, true);
+assertEq(desc.value, "Intl.NumberFormat");
+assertEq(desc.writable, false);
+assertEq(desc.enumerable, false);
+assertEq(desc.configurable, true);
+
+assertEq(Object.prototype.toString.call(Intl.NumberFormat.prototype), "[object Intl.NumberFormat]");
+assertEq(Object.prototype.toString.call(new Intl.NumberFormat), "[object Intl.NumberFormat]");
+
+Object.defineProperty(Intl.NumberFormat.prototype, Symbol.toStringTag, {value: "NumberFormat"});
+
+assertEq(Object.prototype.toString.call(Intl.NumberFormat.prototype), "[object NumberFormat]");
+assertEq(Object.prototype.toString.call(new Intl.NumberFormat), "[object NumberFormat]");
+
+delete Intl.NumberFormat.prototype[Symbol.toStringTag];
+
+assertEq(Object.prototype.toString.call(Intl.NumberFormat.prototype), "[object Object]");
+assertEq(Object.prototype.toString.call(new Intl.NumberFormat), "[object Object]");
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/unit-compound-combinations.js b/js/src/tests/non262/Intl/NumberFormat/unit-compound-combinations.js
new file mode 100644
index 0000000000..5160d08d74
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/unit-compound-combinations.js
@@ -0,0 +1,63 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Generated by make_intl_data.py. DO NOT EDIT.
+
+const sanctionedSimpleUnitIdentifiers = [
+ "acre",
+ "bit",
+ "byte",
+ "celsius",
+ "centimeter",
+ "day",
+ "degree",
+ "fahrenheit",
+ "fluid-ounce",
+ "foot",
+ "gallon",
+ "gigabit",
+ "gigabyte",
+ "gram",
+ "hectare",
+ "hour",
+ "inch",
+ "kilobit",
+ "kilobyte",
+ "kilogram",
+ "kilometer",
+ "liter",
+ "megabit",
+ "megabyte",
+ "meter",
+ "mile",
+ "mile-scandinavian",
+ "milliliter",
+ "millimeter",
+ "millisecond",
+ "minute",
+ "month",
+ "ounce",
+ "percent",
+ "petabyte",
+ "pound",
+ "second",
+ "stone",
+ "terabit",
+ "terabyte",
+ "week",
+ "yard",
+ "year"
+];
+
+// Test all simple unit identifier combinations are allowed.
+
+for (const numerator of sanctionedSimpleUnitIdentifiers) {
+ for (const denominator of sanctionedSimpleUnitIdentifiers) {
+ const unit = `${numerator}-per-${denominator}`;
+ const nf = new Intl.NumberFormat("en", {style: "unit", unit});
+
+ assertEq(nf.format(1), nf.formatToParts(1).map(p => p.value).join(""));
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/unit-formatToParts-has-unit-field.js b/js/src/tests/non262/Intl/NumberFormat/unit-formatToParts-has-unit-field.js
new file mode 100644
index 0000000000..fc433711e7
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/unit-formatToParts-has-unit-field.js
@@ -0,0 +1,88 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Generated by make_intl_data.py. DO NOT EDIT.
+
+const sanctionedSimpleUnitIdentifiers = [
+ "acre",
+ "bit",
+ "byte",
+ "celsius",
+ "centimeter",
+ "day",
+ "degree",
+ "fahrenheit",
+ "fluid-ounce",
+ "foot",
+ "gallon",
+ "gigabit",
+ "gigabyte",
+ "gram",
+ "hectare",
+ "hour",
+ "inch",
+ "kilobit",
+ "kilobyte",
+ "kilogram",
+ "kilometer",
+ "liter",
+ "megabit",
+ "megabyte",
+ "meter",
+ "mile",
+ "mile-scandinavian",
+ "milliliter",
+ "millimeter",
+ "millisecond",
+ "minute",
+ "month",
+ "ounce",
+ "percent",
+ "petabyte",
+ "pound",
+ "second",
+ "stone",
+ "terabit",
+ "terabyte",
+ "week",
+ "yard",
+ "year"
+];
+
+// Test only English and Chinese to keep the overall runtime reasonable.
+//
+// Chinese is included because it contains more than one "unit" element for
+// certain unit combinations.
+const locales = ["en", "zh"];
+
+// Plural rules for English only differentiate between "one" and "other". Plural
+// rules for Chinese only use "other". That means we only need to test two values
+// per unit.
+const values = [0, 1];
+
+// Ensure unit formatters contain at least one "unit" element.
+
+for (const locale of locales) {
+ for (const unit of sanctionedSimpleUnitIdentifiers) {
+ const nf = new Intl.NumberFormat(locale, {style: "unit", unit});
+
+ for (const value of values) {
+ assertEq(nf.formatToParts(value).some(e => e.type === "unit"), true,
+ `locale=${locale}, unit=${unit}`);
+ }
+ }
+
+ for (const numerator of sanctionedSimpleUnitIdentifiers) {
+ for (const denominator of sanctionedSimpleUnitIdentifiers) {
+ const unit = `${numerator}-per-${denominator}`;
+ const nf = new Intl.NumberFormat(locale, {style: "unit", unit});
+
+ for (const value of values) {
+ assertEq(nf.formatToParts(value).some(e => e.type === "unit"), true,
+ `locale=${locale}, unit=${unit}`);
+ }
+ }
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/unit-well-formed.js b/js/src/tests/non262/Intl/NumberFormat/unit-well-formed.js
new file mode 100644
index 0000000000..ec1ab4beda
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/unit-well-formed.js
@@ -0,0 +1,250 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Generated by make_intl_data.py. DO NOT EDIT.
+
+const sanctionedSimpleUnitIdentifiers = [
+ "acre",
+ "bit",
+ "byte",
+ "celsius",
+ "centimeter",
+ "day",
+ "degree",
+ "fahrenheit",
+ "fluid-ounce",
+ "foot",
+ "gallon",
+ "gigabit",
+ "gigabyte",
+ "gram",
+ "hectare",
+ "hour",
+ "inch",
+ "kilobit",
+ "kilobyte",
+ "kilogram",
+ "kilometer",
+ "liter",
+ "megabit",
+ "megabyte",
+ "meter",
+ "mile",
+ "mile-scandinavian",
+ "milliliter",
+ "millimeter",
+ "millisecond",
+ "minute",
+ "month",
+ "ounce",
+ "percent",
+ "petabyte",
+ "pound",
+ "second",
+ "stone",
+ "terabit",
+ "terabyte",
+ "week",
+ "yard",
+ "year"
+];
+
+const allUnits = [
+ "acceleration-g-force",
+ "acceleration-meter-per-square-second",
+ "angle-arc-minute",
+ "angle-arc-second",
+ "angle-degree",
+ "angle-radian",
+ "angle-revolution",
+ "area-acre",
+ "area-dunam",
+ "area-hectare",
+ "area-square-centimeter",
+ "area-square-foot",
+ "area-square-inch",
+ "area-square-kilometer",
+ "area-square-meter",
+ "area-square-mile",
+ "area-square-yard",
+ "concentr-karat",
+ "concentr-milligram-per-deciliter",
+ "concentr-millimole-per-liter",
+ "concentr-mole",
+ "concentr-percent",
+ "concentr-permille",
+ "concentr-permillion",
+ "concentr-permyriad",
+ "consumption-liter-per-100-kilometer",
+ "consumption-liter-per-kilometer",
+ "consumption-mile-per-gallon",
+ "consumption-mile-per-gallon-imperial",
+ "digital-bit",
+ "digital-byte",
+ "digital-gigabit",
+ "digital-gigabyte",
+ "digital-kilobit",
+ "digital-kilobyte",
+ "digital-megabit",
+ "digital-megabyte",
+ "digital-petabyte",
+ "digital-terabit",
+ "digital-terabyte",
+ "duration-century",
+ "duration-day",
+ "duration-day-person",
+ "duration-decade",
+ "duration-hour",
+ "duration-microsecond",
+ "duration-millisecond",
+ "duration-minute",
+ "duration-month",
+ "duration-month-person",
+ "duration-nanosecond",
+ "duration-second",
+ "duration-week",
+ "duration-week-person",
+ "duration-year",
+ "duration-year-person",
+ "electric-ampere",
+ "electric-milliampere",
+ "electric-ohm",
+ "electric-volt",
+ "energy-british-thermal-unit",
+ "energy-calorie",
+ "energy-electronvolt",
+ "energy-foodcalorie",
+ "energy-joule",
+ "energy-kilocalorie",
+ "energy-kilojoule",
+ "energy-kilowatt-hour",
+ "energy-therm-us",
+ "force-newton",
+ "force-pound-force",
+ "frequency-gigahertz",
+ "frequency-hertz",
+ "frequency-kilohertz",
+ "frequency-megahertz",
+ "graphics-dot-per-centimeter",
+ "graphics-dot-per-inch",
+ "graphics-em",
+ "graphics-megapixel",
+ "graphics-pixel",
+ "graphics-pixel-per-centimeter",
+ "graphics-pixel-per-inch",
+ "length-astronomical-unit",
+ "length-centimeter",
+ "length-decimeter",
+ "length-fathom",
+ "length-foot",
+ "length-furlong",
+ "length-inch",
+ "length-kilometer",
+ "length-light-year",
+ "length-meter",
+ "length-micrometer",
+ "length-mile",
+ "length-mile-scandinavian",
+ "length-millimeter",
+ "length-nanometer",
+ "length-nautical-mile",
+ "length-parsec",
+ "length-picometer",
+ "length-point",
+ "length-solar-radius",
+ "length-yard",
+ "light-lux",
+ "light-solar-luminosity",
+ "mass-carat",
+ "mass-dalton",
+ "mass-earth-mass",
+ "mass-gram",
+ "mass-kilogram",
+ "mass-metric-ton",
+ "mass-microgram",
+ "mass-milligram",
+ "mass-ounce",
+ "mass-ounce-troy",
+ "mass-pound",
+ "mass-solar-mass",
+ "mass-stone",
+ "mass-ton",
+ "power-gigawatt",
+ "power-horsepower",
+ "power-kilowatt",
+ "power-megawatt",
+ "power-milliwatt",
+ "power-watt",
+ "pressure-atmosphere",
+ "pressure-bar",
+ "pressure-hectopascal",
+ "pressure-inch-ofhg",
+ "pressure-kilopascal",
+ "pressure-megapascal",
+ "pressure-millibar",
+ "pressure-millimeter-ofhg",
+ "pressure-pascal",
+ "pressure-pound-force-per-square-inch",
+ "speed-kilometer-per-hour",
+ "speed-knot",
+ "speed-meter-per-second",
+ "speed-mile-per-hour",
+ "temperature-celsius",
+ "temperature-fahrenheit",
+ "temperature-generic",
+ "temperature-kelvin",
+ "torque-newton-meter",
+ "torque-pound-force-foot",
+ "volume-acre-foot",
+ "volume-barrel",
+ "volume-bushel",
+ "volume-centiliter",
+ "volume-cubic-centimeter",
+ "volume-cubic-foot",
+ "volume-cubic-inch",
+ "volume-cubic-kilometer",
+ "volume-cubic-meter",
+ "volume-cubic-mile",
+ "volume-cubic-yard",
+ "volume-cup",
+ "volume-cup-metric",
+ "volume-deciliter",
+ "volume-fluid-ounce",
+ "volume-fluid-ounce-imperial",
+ "volume-gallon",
+ "volume-gallon-imperial",
+ "volume-hectoliter",
+ "volume-liter",
+ "volume-megaliter",
+ "volume-milliliter",
+ "volume-pint",
+ "volume-pint-metric",
+ "volume-quart",
+ "volume-tablespoon",
+ "volume-teaspoon"
+];
+
+// Test only sanctioned unit identifiers are allowed.
+
+for (const typeAndUnit of allUnits) {
+ const [_, type, unit] = typeAndUnit.match(/(\w+)-(.+)/);
+
+ let allowed;
+ if (unit.includes("-per-")) {
+ const [numerator, denominator] = unit.split("-per-");
+ allowed = sanctionedSimpleUnitIdentifiers.includes(numerator) &&
+ sanctionedSimpleUnitIdentifiers.includes(denominator);
+ } else {
+ allowed = sanctionedSimpleUnitIdentifiers.includes(unit);
+ }
+
+ if (allowed) {
+ const nf = new Intl.NumberFormat("en", {style: "unit", unit});
+ assertEq(nf.format(1), nf.formatToParts(1).map(p => p.value).join(""));
+ } else {
+ assertThrowsInstanceOf(() => new Intl.NumberFormat("en", {style: "unit", unit}),
+ RangeError, `Missing error for "${typeAndUnit}"`);
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/unit.js b/js/src/tests/non262/Intl/NumberFormat/unit.js
new file mode 100644
index 0000000000..7abc5142e0
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/unit.js
@@ -0,0 +1,104 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const {
+ Nan, Inf, Integer, MinusSign, PlusSign, Literal,
+ Unit
+} = NumberFormatParts;
+
+const testcases = [
+ {
+ locale: "en",
+ options: {
+ style: "unit",
+ unit: "meter",
+ unitDisplay: "short",
+ },
+ values: [
+ {value: +0, string: "0 m", parts: [Integer("0"), Literal(" "), Unit("m")]},
+ {value: -0, string: "-0 m", parts: [MinusSign("-"), Integer("0"), Literal(" "), Unit("m")]},
+ {value: 0n, string: "0 m", parts: [Integer("0"), Literal(" "), Unit("m")]},
+
+ {value: 1, string: "1 m", parts: [Integer("1"), Literal(" "), Unit("m")]},
+ {value: -1, string: "-1 m", parts: [MinusSign("-"), Integer("1"), Literal(" "), Unit("m")]},
+ {value: 1n, string: "1 m", parts: [Integer("1"), Literal(" "), Unit("m")]},
+ {value: -1n, string: "-1 m", parts: [MinusSign("-"), Integer("1"), Literal(" "), Unit("m")]},
+
+ {value: Infinity, string: "∞ m", parts: [Inf("∞"), Literal(" "), Unit("m")]},
+ {value: -Infinity, string: "-∞ m", parts: [MinusSign("-"), Inf("∞"), Literal(" "), Unit("m")]},
+
+ {value: NaN, string: "NaN m", parts: [Nan("NaN"), Literal(" "), Unit("m")]},
+ {value: -NaN, string: "NaN m", parts: [Nan("NaN"), Literal(" "), Unit("m")]},
+ ],
+ },
+
+ {
+ locale: "en",
+ options: {
+ style: "unit",
+ unit: "meter",
+ unitDisplay: "narrow",
+ },
+ values: [
+ {value: +0, string: "0m", parts: [Integer("0"), Unit("m")]},
+ {value: -0, string: "-0m", parts: [MinusSign("-"), Integer("0"), Unit("m")]},
+ {value: 0n, string: "0m", parts: [Integer("0"), Unit("m")]},
+
+ {value: 1, string: "1m", parts: [Integer("1"), Unit("m")]},
+ {value: -1, string: "-1m", parts: [MinusSign("-"), Integer("1"), Unit("m")]},
+ {value: 1n, string: "1m", parts: [Integer("1"), Unit("m")]},
+ {value: -1n, string: "-1m", parts: [MinusSign("-"), Integer("1"), Unit("m")]},
+
+ {value: Infinity, string: "∞m", parts: [Inf("∞"), Unit("m")]},
+ {value: -Infinity, string: "-∞m", parts: [MinusSign("-"), Inf("∞"), Unit("m")]},
+
+ {value: NaN, string: "NaNm", parts: [Nan("NaN"), Unit("m")]},
+ {value: -NaN, string: "NaNm", parts: [Nan("NaN"), Unit("m")]},
+ ],
+ },
+
+ {
+ locale: "en",
+ options: {
+ style: "unit",
+ unit: "meter",
+ unitDisplay: "long",
+ },
+ values: [
+ {value: +0, string: "0 meters", parts: [Integer("0"), Literal(" "), Unit("meters")]},
+ {value: -0, string: "-0 meters", parts: [MinusSign("-"), Integer("0"), Literal(" "), Unit("meters")]},
+ {value: 0n, string: "0 meters", parts: [Integer("0"), Literal(" "), Unit("meters")]},
+
+ {value: 1, string: "1 meter", parts: [Integer("1"), Literal(" "), Unit("meter")]},
+ {value: -1, string: "-1 meter", parts: [MinusSign("-"), Integer("1"), Literal(" "), Unit("meter")]},
+ {value: 1n, string: "1 meter", parts: [Integer("1"), Literal(" "), Unit("meter")]},
+ {value: -1n, string: "-1 meter", parts: [MinusSign("-"), Integer("1"), Literal(" "), Unit("meter")]},
+
+ {value: Infinity, string: "∞ meters", parts: [Inf("∞"), Literal(" "), Unit("meters")]},
+ {value: -Infinity, string: "-∞ meters", parts: [MinusSign("-"), Inf("∞"), Literal(" "), Unit("meters")]},
+
+ {value: NaN, string: "NaN meters", parts: [Nan("NaN"), Literal(" "), Unit("meters")]},
+ {value: -NaN, string: "NaN meters", parts: [Nan("NaN"), Literal(" "), Unit("meters")]},
+ ],
+ },
+
+ // Ensure the correct compound unit is automatically selected by ICU. For
+ // example instead of "50 chilometri al orari", 50 km/h should return
+ // "50 chilometri orari" in Italian.
+
+ {
+ locale: "it",
+ options: {
+ style: "unit",
+ unit: "kilometer-per-hour",
+ unitDisplay: "long",
+ },
+ values: [
+ {value: 50, string: "50 chilometri orari", parts: [Integer("50"), Literal(" "), Unit("chilometri orari")]},
+ ],
+ },
+];
+
+runNumberFormattingTestcases(testcases);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/NumberFormat/unwrapping.js b/js/src/tests/non262/Intl/NumberFormat/unwrapping.js
new file mode 100644
index 0000000000..7e66c43916
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/unwrapping.js
@@ -0,0 +1,247 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Test UnwrapNumberFormat operation.
+
+const numberFormatFunctions = [];
+numberFormatFunctions.push({
+ function: Intl.NumberFormat.prototype.resolvedOptions,
+ unwrap: true,
+});
+numberFormatFunctions.push({
+ function: Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, "format").get,
+ unwrap: true,
+});
+numberFormatFunctions.push({
+ function: Intl.NumberFormat.prototype.formatToParts,
+ unwrap: false,
+});
+
+function IsIntlService(c) {
+ return typeof c === "function" &&
+ c.hasOwnProperty("prototype") &&
+ c.prototype.hasOwnProperty("resolvedOptions");
+}
+
+function IsObject(o) {
+ return Object(o) === o;
+}
+
+function IsPrimitive(o) {
+ return Object(o) !== o;
+}
+
+function intlObjects(ctor) {
+ let args = [];
+ if (ctor === Intl.DisplayNames) {
+ // Intl.DisplayNames can't be constructed without any arguments.
+ args = [undefined, {type: "language"}];
+ }
+
+ return [
+ // Instance of an Intl constructor.
+ new ctor(...args),
+
+ // Instance of a subclassed Intl constructor.
+ new class extends ctor {}(...args),
+
+ // Intl object not inheriting from its default prototype.
+ Object.setPrototypeOf(new ctor(...args), Object.prototype),
+ ];
+}
+
+function thisValues(C) {
+ const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsIntlService);
+
+ return [
+ // Primitive values.
+ ...[undefined, null, true, "abc", Symbol(), 123],
+
+ // Object values.
+ ...[{}, [], /(?:)/, function(){}, new Proxy({}, {})],
+
+ // Intl objects.
+ ...[].concat(...intlConstructors.filter(ctor => ctor !== C).map(intlObjects)),
+
+ // Object inheriting from an Intl constructor prototype.
+ ...intlConstructors.map(ctor => Object.create(ctor.prototype)),
+ ];
+}
+
+const intlFallbackSymbol = Object.getOwnPropertySymbols(Intl.NumberFormat.call(Object.create(Intl.NumberFormat.prototype)))[0];
+
+// Test Intl.NumberFormat.prototype methods.
+for (let {function: numberFormatFunction, unwrap} of numberFormatFunctions) {
+ // Test a TypeError is thrown when the this-value isn't an initialized
+ // Intl.NumberFormat instance.
+ for (let thisValue of thisValues(Intl.NumberFormat)) {
+ assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError);
+ }
+
+ // And test no error is thrown for initialized Intl.NumberFormat instances.
+ for (let thisValue of intlObjects(Intl.NumberFormat)) {
+ numberFormatFunction.call(thisValue);
+ }
+
+ // Manually add [[FallbackSymbol]] to objects and then repeat the tests from above.
+ for (let thisValue of thisValues(Intl.NumberFormat)) {
+ assertThrowsInstanceOf(() => numberFormatFunction.call({
+ __proto__: Intl.NumberFormat.prototype,
+ [intlFallbackSymbol]: thisValue,
+ }), TypeError);
+ }
+
+ for (let thisValue of intlObjects(Intl.NumberFormat)) {
+ let obj = {
+ __proto__: Intl.NumberFormat.prototype,
+ [intlFallbackSymbol]: thisValue,
+ };
+ if (unwrap) {
+ numberFormatFunction.call(obj);
+ } else {
+ assertThrowsInstanceOf(() => numberFormatFunction.call(obj), TypeError);
+ }
+ }
+
+ // Ensure [[FallbackSymbol]] isn't retrieved for Intl.NumberFormat instances.
+ for (let thisValue of intlObjects(Intl.NumberFormat)) {
+ Object.defineProperty(thisValue, intlFallbackSymbol, {
+ get() { assertEq(false, true); }
+ });
+ numberFormatFunction.call(thisValue);
+ }
+
+ // Ensure [[FallbackSymbol]] is only retrieved for objects inheriting from Intl.NumberFormat.prototype.
+ for (let thisValue of thisValues(Intl.NumberFormat).filter(IsObject)) {
+ if (Intl.NumberFormat.prototype.isPrototypeOf(thisValue))
+ continue;
+ Object.defineProperty(thisValue, intlFallbackSymbol, {
+ get() { assertEq(false, true); }
+ });
+ assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError);
+ }
+
+ // Repeat the test from above, but also change Intl.NumberFormat[@@hasInstance]
+ // so it always returns |null|.
+ for (let thisValue of thisValues(Intl.NumberFormat).filter(IsObject)) {
+ let hasInstanceCalled = false, symbolGetterCalled = false;
+ Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
+ value() {
+ assertEq(hasInstanceCalled, false);
+ hasInstanceCalled = true;
+ return true;
+ }, configurable: true
+ });
+ Object.defineProperty(thisValue, intlFallbackSymbol, {
+ get() {
+ assertEq(symbolGetterCalled, false);
+ symbolGetterCalled = true;
+ return null;
+ }, configurable: true
+ });
+
+ assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError);
+
+ delete Intl.NumberFormat[Symbol.hasInstance];
+
+ assertEq(hasInstanceCalled, unwrap);
+ assertEq(symbolGetterCalled, unwrap);
+ }
+
+ // Test with primitive values.
+ for (let thisValue of thisValues(Intl.NumberFormat).filter(IsPrimitive)) {
+ // Ensure @@hasInstance is not called.
+ Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
+ value() { assertEq(true, false); }, configurable: true
+ });
+ let isUndefinedOrNull = thisValue === undefined || thisValue === null;
+ let symbolHolder;
+ if (!isUndefinedOrNull) {
+ // Ensure the fallback symbol isn't retrieved from the primitive wrapper prototype.
+ symbolHolder = Object.getPrototypeOf(thisValue);
+ Object.defineProperty(symbolHolder, intlFallbackSymbol, {
+ get() { assertEq(true, false); }, configurable: true
+ });
+ }
+
+ assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError);
+
+ delete Intl.NumberFormat[Symbol.hasInstance];
+ if (!isUndefinedOrNull)
+ delete symbolHolder[intlFallbackSymbol];
+ }
+}
+
+// Test format() returns the correct result for objects initialized as Intl.NumberFormat instances.
+{
+ // An actual Intl.NumberFormat instance.
+ let numberFormat = new Intl.NumberFormat();
+
+ // An object initialized as a NumberFormat instance.
+ let thisValue = Object.create(Intl.NumberFormat.prototype);
+ Intl.NumberFormat.call(thisValue);
+
+ // Object with [[FallbackSymbol]] set to NumberFormat instance.
+ let fakeObj = {
+ __proto__: Intl.NumberFormat.prototype,
+ [intlFallbackSymbol]: numberFormat,
+ };
+
+ for (let number of [0, 1, 1.5, Infinity, NaN]) {
+ let expected = numberFormat.format(number);
+ assertEq(thisValue.format(number), expected);
+ assertEq(thisValue[intlFallbackSymbol].format(number), expected);
+ assertEq(fakeObj.format(number), expected);
+ }
+}
+
+// Ensure formatToParts() doesn't use the fallback semantics.
+{
+ let formatToParts = Intl.NumberFormat.prototype.formatToParts;
+
+ // An object initialized as a NumberFormat instance.
+ let thisValue = Object.create(Intl.NumberFormat.prototype);
+ Intl.NumberFormat.call(thisValue);
+ assertThrowsInstanceOf(() => formatToParts.call(thisValue), TypeError);
+
+ // Object with [[FallbackSymbol]] set to NumberFormat instance.
+ let fakeObj = {
+ __proto__: Intl.NumberFormat.prototype,
+ [intlFallbackSymbol]: new Intl.NumberFormat(),
+ };
+ assertThrowsInstanceOf(() => formatToParts.call(fakeObj), TypeError);
+}
+
+// Test resolvedOptions() returns the same results.
+{
+ // An actual Intl.NumberFormat instance.
+ let numberFormat = new Intl.NumberFormat();
+
+ // An object initialized as a NumberFormat instance.
+ let thisValue = Object.create(Intl.NumberFormat.prototype);
+ Intl.NumberFormat.call(thisValue);
+
+ // Object with [[FallbackSymbol]] set to NumberFormat instance.
+ let fakeObj = {
+ __proto__: Intl.NumberFormat.prototype,
+ [intlFallbackSymbol]: numberFormat,
+ };
+
+ function assertEqOptions(actual, expected) {
+ actual = Object.entries(actual);
+ expected = Object.entries(expected);
+
+ assertEq(actual.length, expected.length, "options count mismatch");
+ for (var i = 0; i < expected.length; i++) {
+ assertEq(actual[i][0], expected[i][0], "key mismatch at " + i);
+ assertEq(actual[i][1], expected[i][1], "value mismatch at " + i);
+ }
+ }
+
+ let expected = numberFormat.resolvedOptions();
+ assertEqOptions(thisValue.resolvedOptions(), expected);
+ assertEqOptions(thisValue[intlFallbackSymbol].resolvedOptions(), expected);
+ assertEqOptions(fakeObj.resolvedOptions(), expected);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/PluralRules/browser.js b/js/src/tests/non262/Intl/PluralRules/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Intl/PluralRules/browser.js
diff --git a/js/src/tests/non262/Intl/PluralRules/call.js b/js/src/tests/non262/Intl/PluralRules/call.js
new file mode 100644
index 0000000000..9412fa44d0
--- /dev/null
+++ b/js/src/tests/non262/Intl/PluralRules/call.js
@@ -0,0 +1,53 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+function IsIntlService(c) {
+ return typeof c === "function" &&
+ c.hasOwnProperty("prototype") &&
+ c.prototype.hasOwnProperty("resolvedOptions");
+}
+
+function thisValues() {
+ const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsIntlService);
+
+ return [
+ // Primitive values.
+ ...[undefined, null, true, "abc", Symbol(), 123],
+
+ // Object values.
+ ...[{}, [], /(?:)/, function(){}, new Proxy({}, {})],
+
+ // Intl objects.
+ ...[].concat(...intlConstructors.map(ctor => {
+ let args = [];
+ if (ctor === Intl.DisplayNames) {
+ // Intl.DisplayNames can't be constructed without any arguments.
+ args = [undefined, {type: "language"}];
+ }
+
+ return [
+ // Instance of an Intl constructor.
+ new ctor(...args),
+
+ // Instance of a subclassed Intl constructor.
+ new class extends ctor {}(...args),
+
+ // Object inheriting from an Intl constructor prototype.
+ Object.create(ctor.prototype),
+
+ // Intl object not inheriting from its default prototype.
+ Object.setPrototypeOf(new ctor(...args), Object.prototype),
+ ];
+ })),
+ ];
+}
+
+// Intl.PluralRules cannot be invoked as a function.
+assertThrowsInstanceOf(() => Intl.PluralRules(), TypeError);
+
+// Also test with explicit this-value.
+for (let thisValue of thisValues()) {
+ assertThrowsInstanceOf(() => Intl.PluralRules.call(thisValue), TypeError);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/PluralRules/construct-newtarget.js b/js/src/tests/non262/Intl/PluralRules/construct-newtarget.js
new file mode 100644
index 0000000000..356c2dd221
--- /dev/null
+++ b/js/src/tests/non262/Intl/PluralRules/construct-newtarget.js
@@ -0,0 +1,76 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Test subclassing %Intl.PluralRules% works correctly.
+class MyPluralRules extends Intl.PluralRules {}
+
+var obj = new MyPluralRules();
+assertEq(obj instanceof MyPluralRules, true);
+assertEq(obj instanceof Intl.PluralRules, true);
+assertEq(Object.getPrototypeOf(obj), MyPluralRules.prototype);
+
+obj = Reflect.construct(MyPluralRules, []);
+assertEq(obj instanceof MyPluralRules, true);
+assertEq(obj instanceof Intl.PluralRules, true);
+assertEq(Object.getPrototypeOf(obj), MyPluralRules.prototype);
+
+obj = Reflect.construct(MyPluralRules, [], MyPluralRules);
+assertEq(obj instanceof MyPluralRules, true);
+assertEq(obj instanceof Intl.PluralRules, true);
+assertEq(Object.getPrototypeOf(obj), MyPluralRules.prototype);
+
+obj = Reflect.construct(MyPluralRules, [], Intl.PluralRules);
+assertEq(obj instanceof MyPluralRules, false);
+assertEq(obj instanceof Intl.PluralRules, true);
+assertEq(Object.getPrototypeOf(obj), Intl.PluralRules.prototype);
+
+
+// Set a different constructor as NewTarget.
+obj = Reflect.construct(MyPluralRules, [], Array);
+assertEq(obj instanceof MyPluralRules, false);
+assertEq(obj instanceof Intl.PluralRules, false);
+assertEq(obj instanceof Array, true);
+assertEq(Object.getPrototypeOf(obj), Array.prototype);
+
+obj = Reflect.construct(Intl.PluralRules, [], Array);
+assertEq(obj instanceof Intl.PluralRules, false);
+assertEq(obj instanceof Array, true);
+assertEq(Object.getPrototypeOf(obj), Array.prototype);
+
+
+// The prototype defaults to %PluralRulesPrototype% if null.
+function NewTargetNullPrototype() {}
+NewTargetNullPrototype.prototype = null;
+
+obj = Reflect.construct(Intl.PluralRules, [], NewTargetNullPrototype);
+assertEq(obj instanceof Intl.PluralRules, true);
+assertEq(Object.getPrototypeOf(obj), Intl.PluralRules.prototype);
+
+obj = Reflect.construct(MyPluralRules, [], NewTargetNullPrototype);
+assertEq(obj instanceof MyPluralRules, false);
+assertEq(obj instanceof Intl.PluralRules, true);
+assertEq(Object.getPrototypeOf(obj), Intl.PluralRules.prototype);
+
+
+// "prototype" property is retrieved exactly once.
+var trapLog = [], getLog = [];
+var ProxiedConstructor = new Proxy(Intl.PluralRules, new Proxy({
+ get(target, propertyKey, receiver) {
+ getLog.push(propertyKey);
+ return Reflect.get(target, propertyKey, receiver);
+ }
+}, {
+ get(target, propertyKey, receiver) {
+ trapLog.push(propertyKey);
+ return Reflect.get(target, propertyKey, receiver);
+ }
+}));
+
+obj = Reflect.construct(Intl.PluralRules, [], ProxiedConstructor);
+assertEqArray(trapLog, ["get"]);
+assertEqArray(getLog, ["prototype"]);
+assertEq(obj instanceof Intl.PluralRules, true);
+assertEq(Object.getPrototypeOf(obj), Intl.PluralRules.prototype);
+
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/PluralRules/cross-compartment.js b/js/src/tests/non262/Intl/PluralRules/cross-compartment.js
new file mode 100644
index 0000000000..ff59f35957
--- /dev/null
+++ b/js/src/tests/non262/Intl/PluralRules/cross-compartment.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+var otherGlobal = newGlobal();
+
+var pluralRules = new Intl.PluralRules();
+var ccwPluralRules = new otherGlobal.Intl.PluralRules();
+
+// Test Intl.PluralRules.prototype.select with a CCW object.
+var Intl_PluralRules_select = Intl.PluralRules.prototype.select;
+
+assertEq(Intl_PluralRules_select.call(ccwPluralRules, 0),
+ Intl_PluralRules_select.call(pluralRules, 0));
+
+// Test Intl.PluralRules.prototype.resolvedOptions with a CCW object.
+var Intl_PluralRules_resolvedOptions = Intl.PluralRules.prototype.resolvedOptions;
+
+assertEq(deepEqual(Intl_PluralRules_resolvedOptions.call(ccwPluralRules),
+ Intl_PluralRules_resolvedOptions.call(pluralRules)),
+ true);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/PluralRules/negativeZeroFractionDigits.js b/js/src/tests/non262/Intl/PluralRules/negativeZeroFractionDigits.js
new file mode 100644
index 0000000000..615e533466
--- /dev/null
+++ b/js/src/tests/non262/Intl/PluralRules/negativeZeroFractionDigits.js
@@ -0,0 +1,21 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const optionsList = [
+ {minimumFractionDigits: -0, maximumFractionDigits: -0},
+ {minimumFractionDigits: -0, maximumFractionDigits: +0},
+ {minimumFractionDigits: +0, maximumFractionDigits: -0},
+ {minimumFractionDigits: +0, maximumFractionDigits: +0},
+];
+
+for (let options of optionsList) {
+ let pluralRules = new Intl.PluralRules("en-US", options);
+
+ let {minimumFractionDigits, maximumFractionDigits} = pluralRules.resolvedOptions();
+ assertEq(minimumFractionDigits, +0);
+ assertEq(maximumFractionDigits, +0);
+
+ assertEq(pluralRules.select(123), "other");
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/PluralRules/pluralrules.js b/js/src/tests/non262/Intl/PluralRules/pluralrules.js
new file mode 100644
index 0000000000..20d6117b3b
--- /dev/null
+++ b/js/src/tests/non262/Intl/PluralRules/pluralrules.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Tests the format function with a diverse set of locales and options.
+
+var pr;
+
+pr = new Intl.PluralRules("en-us");
+assertEq(pr.resolvedOptions().locale, "en-US");
+assertEq(pr.resolvedOptions().type, "cardinal");
+assertEq(pr.resolvedOptions().pluralCategories.length, 2);
+
+pr = new Intl.PluralRules("de", {type: 'cardinal'});
+assertEq(pr.resolvedOptions().pluralCategories.length, 2);
+
+pr = new Intl.PluralRules("de", {type: 'ordinal'});
+assertEq(pr.resolvedOptions().pluralCategories.length, 1);
+
+reportCompare(0, 0, 'ok');
diff --git a/js/src/tests/non262/Intl/PluralRules/resolvedOptions-overridden-species.js b/js/src/tests/non262/Intl/PluralRules/resolvedOptions-overridden-species.js
new file mode 100644
index 0000000000..3591aba32b
--- /dev/null
+++ b/js/src/tests/non262/Intl/PluralRules/resolvedOptions-overridden-species.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Tests the PluralRules.resolvedOptions function for overriden Array[Symbol.species].
+
+var pl = new Intl.PluralRules("de");
+
+Object.defineProperty(Array, Symbol.species, {
+ value: function() {
+ return new Proxy(["?"], {
+ get(t, pk, r) {
+ return Reflect.get(t, pk, r);
+ },
+ defineProperty(t, pk) {
+ return true;
+ }
+ });
+ }
+});
+
+var pluralCategories = pl.resolvedOptions().pluralCategories;
+
+assertEqArray(pluralCategories, ["one", "other"]);
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/PluralRules/rounding.js b/js/src/tests/non262/Intl/PluralRules/rounding.js
new file mode 100644
index 0000000000..6f9b5f9936
--- /dev/null
+++ b/js/src/tests/non262/Intl/PluralRules/rounding.js
@@ -0,0 +1,17 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// The rounding mode defaults to half-up for both NumberFormat and PluralRules.
+
+var locale = "en";
+var options = {maximumFractionDigits: 0};
+
+assertEq(new Intl.NumberFormat(locale, options).format(0), "0");
+assertEq(new Intl.NumberFormat(locale, options).format(0.5), "1");
+assertEq(new Intl.NumberFormat(locale, options).format(1), "1");
+
+assertEq(new Intl.PluralRules(locale, options).select(0), "other");
+assertEq(new Intl.PluralRules(locale, options).select(0.5), "one");
+assertEq(new Intl.PluralRules(locale, options).select(1), "one");
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, 'ok');
diff --git a/js/src/tests/non262/Intl/PluralRules/select.js b/js/src/tests/non262/Intl/PluralRules/select.js
new file mode 100644
index 0000000000..dcc057ec3c
--- /dev/null
+++ b/js/src/tests/non262/Intl/PluralRules/select.js
@@ -0,0 +1,63 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl'))
+
+// Tests the format function with a diverse set of locales and options.
+
+var pr;
+
+pr = new Intl.PluralRules("en-us");
+assertEq(pr.select(0), "other");
+assertEq(pr.select(0.5), "other");
+assertEq(pr.select(1.2), "other");
+assertEq(pr.select(1.5), "other");
+assertEq(pr.select(1.7), "other");
+assertEq(pr.select(-1), "one");
+assertEq(pr.select(1), "one");
+assertEq(pr.select("1"), "one");
+assertEq(pr.select(123456789.123456789), "other");
+
+pr = new Intl.PluralRules("de", {type: "cardinal"});
+assertEq(pr.select(0), "other");
+assertEq(pr.select(0.5), "other");
+assertEq(pr.select(1.2), "other");
+assertEq(pr.select(1.5), "other");
+assertEq(pr.select(1.7), "other");
+assertEq(pr.select(-1), "one");
+
+pr = new Intl.PluralRules("de", {type: "ordinal"});
+assertEq(pr.select(0), "other");
+assertEq(pr.select(0.5), "other");
+assertEq(pr.select(1.2), "other");
+assertEq(pr.select(1.5), "other");
+assertEq(pr.select(1.7), "other");
+assertEq(pr.select(-1), "other");
+
+pr = new Intl.PluralRules("pl", {type: "cardinal"});
+assertEq(pr.select(0), "many");
+assertEq(pr.select(0.5), "other");
+assertEq(pr.select(1), "one");
+
+pr = new Intl.PluralRules("pl", {type: "cardinal", maximumFractionDigits: 0});
+assertEq(pr.select(1.1), "one");
+
+pr = new Intl.PluralRules("pl", {type: "cardinal", maximumFractionDigits: 1});
+assertEq(pr.select(1.1), "other");
+
+pr = new Intl.PluralRules("en", {type: "cardinal", minimumFractionDigits: 0});
+assertEq(pr.select(1), "one");
+
+pr = new Intl.PluralRules("en", {type: "cardinal", minimumFractionDigits: 2});
+assertEq(pr.select(1), "other");
+
+var weirdCases = [
+ NaN,
+ Infinity,
+ "word",
+ [0,2],
+ {},
+];
+
+for (let c of weirdCases) {
+ assertEq(pr.select(c), "other");
+};
+
+reportCompare(0, 0, 'ok');
diff --git a/js/src/tests/non262/Intl/PluralRules/shell.js b/js/src/tests/non262/Intl/PluralRules/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Intl/PluralRules/shell.js
diff --git a/js/src/tests/non262/Intl/PluralRules/supportedLocalesOf.js b/js/src/tests/non262/Intl/PluralRules/supportedLocalesOf.js
new file mode 100644
index 0000000000..7c6ebe8c45
--- /dev/null
+++ b/js/src/tests/non262/Intl/PluralRules/supportedLocalesOf.js
@@ -0,0 +1,369 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl')||xulRuntime.shell)
+// -- test in browser only that ICU has locale data for all Mozilla languages
+
+// This array contains the locales that ICU supports in
+// number formatting whose languages Mozilla localizes Firefox into.
+// Current as of ICU 50.1.2 and Firefox March 2013.
+var locales = [
+ "af",
+ "af-NA",
+ "af-ZA",
+ "ar",
+ "ar-001",
+ "ar-AE",
+ "ar-BH",
+ "ar-DJ",
+ "ar-DZ",
+ "ar-EG",
+ "ar-EH",
+ "ar-ER",
+ "ar-IL",
+ "ar-IQ",
+ "ar-JO",
+ "ar-KM",
+ "ar-KW",
+ "ar-LB",
+ "ar-LY",
+ "ar-MA",
+ "ar-MR",
+ "ar-OM",
+ "ar-PS",
+ "ar-QA",
+ "ar-SA",
+ "ar-SD",
+ "ar-SO",
+ "ar-SY",
+ "ar-TD",
+ "ar-TN",
+ "ar-YE",
+ "as",
+ "as-IN",
+ "be",
+ "be-BY",
+ "bg",
+ "bg-BG",
+ "bn",
+ "bn-BD",
+ "bn-IN",
+ "br",
+ "br-FR",
+ "bs",
+ "bs-Cyrl",
+ "bs-Cyrl-BA",
+ "bs-Latn",
+ "bs-Latn-BA",
+ "ca",
+ "ca-AD",
+ "ca-ES",
+ "cs",
+ "cs-CZ",
+ "cy",
+ "cy-GB",
+ "da",
+ "da-DK",
+ "de",
+ "de-AT",
+ "de-BE",
+ "de-CH",
+ "de-DE",
+ "de-LI",
+ "de-LU",
+ "el",
+ "el-CY",
+ "el-GR",
+ "en",
+ "en-150",
+ "en-AG",
+ "en-AS",
+ "en-AU",
+ "en-BB",
+ "en-BE",
+ "en-BM",
+ "en-BS",
+ "en-BW",
+ "en-BZ",
+ "en-CA",
+ "en-CM",
+ "en-DM",
+ "en-FJ",
+ "en-FM",
+ "en-GB",
+ "en-GD",
+ "en-GG",
+ "en-GH",
+ "en-GI",
+ "en-GM",
+ "en-GU",
+ "en-GY",
+ "en-HK",
+ "en-IE",
+ "en-IM",
+ "en-IN",
+ "en-JE",
+ "en-JM",
+ "en-KE",
+ "en-KI",
+ "en-KN",
+ "en-KY",
+ "en-LC",
+ "en-LR",
+ "en-LS",
+ "en-MG",
+ "en-MH",
+ "en-MP",
+ "en-MT",
+ "en-MU",
+ "en-MW",
+ "en-NA",
+ "en-NG",
+ "en-NZ",
+ "en-PG",
+ "en-PH",
+ "en-PK",
+ "en-PR",
+ "en-PW",
+ "en-SB",
+ "en-SC",
+ "en-SG",
+ "en-SL",
+ "en-SS",
+ "en-SZ",
+ "en-TC",
+ "en-TO",
+ "en-TT",
+ "en-TZ",
+ "en-UG",
+ "en-UM",
+ "en-US",
+ "en-US-posix",
+ "en-VC",
+ "en-VG",
+ "en-VI",
+ "en-VU",
+ "en-WS",
+ "en-ZA",
+ "en-ZM",
+ "en-ZW",
+ "eo",
+ "es",
+ "es-419",
+ "es-AR",
+ "es-BO",
+ "es-CL",
+ "es-CO",
+ "es-CR",
+ "es-CU",
+ "es-DO",
+ "es-EA",
+ "es-EC",
+ "es-ES",
+ "es-GQ",
+ "es-GT",
+ "es-HN",
+ "es-IC",
+ "es-MX",
+ "es-NI",
+ "es-PA",
+ "es-PE",
+ "es-PH",
+ "es-PR",
+ "es-PY",
+ "es-SV",
+ "es-US",
+ "es-UY",
+ "es-VE",
+ "et",
+ "et-EE",
+ "eu",
+ "eu-ES",
+ "fa",
+ "fa-AF",
+ "fa-IR",
+ "ff",
+ "ff-SN",
+ "fi",
+ "fi-FI",
+ "fr",
+ "fr-BE",
+ "fr-BF",
+ "fr-BI",
+ "fr-BJ",
+ "fr-BL",
+ "fr-CA",
+ "fr-CD",
+ "fr-CF",
+ "fr-CG",
+ "fr-CH",
+ "fr-CI",
+ "fr-CM",
+ "fr-DJ",
+ "fr-DZ",
+ "fr-FR",
+ "fr-GA",
+ "fr-GF",
+ "fr-GN",
+ "fr-GP",
+ "fr-GQ",
+ "fr-HT",
+ "fr-KM",
+ "fr-LU",
+ "fr-MA",
+ "fr-MC",
+ "fr-MF",
+ "fr-MG",
+ "fr-ML",
+ "fr-MQ",
+ "fr-MR",
+ "fr-MU",
+ "fr-NC",
+ "fr-NE",
+ "fr-PF",
+ "fr-RE",
+ "fr-RW",
+ "fr-SC",
+ "fr-SN",
+ "fr-SY",
+ "fr-TD",
+ "fr-TG",
+ "fr-TN",
+ "fr-VU",
+ "fr-YT",
+ "ga",
+ "ga-IE",
+ "gl",
+ "gl-ES",
+ "gu",
+ "gu-IN",
+ "he",
+ "he-IL",
+ "hi",
+ "hi-IN",
+ "hr",
+ "hr-BA",
+ "hr-HR",
+ "hu",
+ "hu-HU",
+ "hy",
+ "hy-AM",
+ "id",
+ "id-ID",
+ "is",
+ "is-IS",
+ "it",
+ "it-CH",
+ "it-IT",
+ "it-SM",
+ "ja",
+ "ja-JP",
+ "kk",
+ "kk-Cyrl",
+ "kk-Cyrl-KZ",
+ "km",
+ "km-KH",
+ "kn",
+ "kn-IN",
+ "ko",
+ "ko-KP",
+ "ko-KR",
+ "lt",
+ "lt-LT",
+ "lv",
+ "lv-LV",
+ "mk",
+ "mk-MK",
+ "ml",
+ "ml-IN",
+ "mr",
+ "mr-IN",
+ "nb",
+ "nb-NO",
+ "nl",
+ "nl-AW",
+ "nl-BE",
+ "nl-CW",
+ "nl-NL",
+ "nl-SR",
+ "nl-SX",
+ "nn",
+ "nn-NO",
+ "or",
+ "or-IN",
+ "pa",
+ "pa-Arab",
+ "pa-Arab-PK",
+ "pa-Guru",
+ "pa-Guru-IN",
+ "pl",
+ "pl-PL",
+ "pt",
+ "pt-AO",
+ "pt-BR",
+ "pt-CV",
+ "pt-GW",
+ "pt-MO",
+ "pt-MZ",
+ "pt-PT",
+ "pt-ST",
+ "pt-TL",
+ "rm",
+ "rm-CH",
+ "ro",
+ "ro-MD",
+ "ro-RO",
+ "ru",
+ "ru-BY",
+ "ru-KG",
+ "ru-KZ",
+ "ru-MD",
+ "ru-RU",
+ "ru-UA",
+ "si",
+ "si-LK",
+ "sk",
+ "sk-SK",
+ "sl",
+ "sl-SI",
+ "sq",
+ "sq-AL",
+ "sq-MK",
+ "sr",
+ "sr-Cyrl",
+ "sr-Cyrl-BA",
+ "sr-Cyrl-ME",
+ "sr-Cyrl-RS",
+ "sr-Latn",
+ "sr-Latn-BA",
+ "sr-Latn-ME",
+ "sr-Latn-RS",
+ "sv",
+ "sv-AX",
+ "sv-FI",
+ "sv-SE",
+ "te",
+ "te-IN",
+ "th",
+ "th-TH",
+ "tr",
+ "tr-CY",
+ "tr-TR",
+ "uk",
+ "uk-UA",
+ "vi",
+ "vi-VN",
+ "zh",
+ "zh-Hans",
+ "zh-Hans-CN",
+ "zh-Hans-HK",
+ "zh-Hans-MO",
+ "zh-Hans-SG",
+ "zh-Hant",
+ "zh-Hant-HK",
+ "zh-Hant-MO",
+ "zh-Hant-TW",
+];
+
+const result = Intl.PluralRules.supportedLocalesOf(locales);
+
+assertEqArray(locales, result);
+
+reportCompare(0, 0, 'ok');
diff --git a/js/src/tests/non262/Intl/README.txt b/js/src/tests/non262/Intl/README.txt
new file mode 100644
index 0000000000..797b49ab5c
--- /dev/null
+++ b/js/src/tests/non262/Intl/README.txt
@@ -0,0 +1,27 @@
+Integration Tests for ECMAScript Internationalization API
+=========================================================
+
+The tests in this directory test the integration of the ICU library
+(Internationalization Components for Unicode) into the implementation of the
+ECMAScript Internationalization API in SpiderMonkey.
+
+These integration tests are complementary to:
+
+- The Test402 test suite maintained by Ecma TC39, which tests conformance of
+ an implementation to standard ECMA-402, ECMAScript Internationalization API
+ Specification. Test402 is currently maintained as part of Test262, the overall
+ conformance test suite for ECMAScript; for more information, see
+ http://wiki.ecmascript.org/doku.php?id=test262:test262
+
+- The test suite of the ICU library, which tests the implementation of ICU
+ itself and correct interpretation of the locale data it obtains from CLDR
+ (Common Locale Data Repository). For information on ICU, see
+ http://site.icu-project.org
+
+The integration tests check for a variety of locales and options whether the
+results are localized in a way that indicates correct integration with ICU.
+Such tests are somewhat fragile because the underlying locale data reflects
+real world usage and is therefore subject to change. When the ICU library used
+by Mozilla is upgraded, it is likely that some of the integration tests will
+fail because of locale data changes; however, others might fail because of
+actual software bugs. Failures therefore have to be examined carefully.
diff --git a/js/src/tests/non262/Intl/RelativeTimeFormat/browser.js b/js/src/tests/non262/Intl/RelativeTimeFormat/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Intl/RelativeTimeFormat/browser.js
diff --git a/js/src/tests/non262/Intl/RelativeTimeFormat/construct-newtarget.js b/js/src/tests/non262/Intl/RelativeTimeFormat/construct-newtarget.js
new file mode 100644
index 0000000000..c20c07434e
--- /dev/null
+++ b/js/src/tests/non262/Intl/RelativeTimeFormat/construct-newtarget.js
@@ -0,0 +1,21 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var obj = new Intl.RelativeTimeFormat();
+
+// Test that new RTF produces an object with the right prototype.
+assertEq(Object.getPrototypeOf(obj), Intl.RelativeTimeFormat.prototype);
+
+// Test subclassing %Intl.RelativeTimeFormat% works correctly.
+class MyRelativeTimeFormat extends Intl.RelativeTimeFormat {}
+
+var obj = new MyRelativeTimeFormat();
+assertEq(obj instanceof MyRelativeTimeFormat, true);
+assertEq(obj instanceof Intl.RelativeTimeFormat, true);
+assertEq(Object.getPrototypeOf(obj), MyRelativeTimeFormat.prototype);
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/RelativeTimeFormat/cross-compartment.js b/js/src/tests/non262/Intl/RelativeTimeFormat/cross-compartment.js
new file mode 100644
index 0000000000..c826538f39
--- /dev/null
+++ b/js/src/tests/non262/Intl/RelativeTimeFormat/cross-compartment.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+var otherGlobal = newGlobal();
+
+var relativeTimeFormat = new Intl.RelativeTimeFormat();
+var ccwRelativeTimeFormat = new otherGlobal.Intl.RelativeTimeFormat();
+
+// Test Intl.RelativeTimeFormat.prototype.format with a CCW object.
+var Intl_RelativeTimeFormat_format = Intl.RelativeTimeFormat.prototype.format;
+
+assertEq(Intl_RelativeTimeFormat_format.call(ccwRelativeTimeFormat, 0, "hour"),
+ Intl_RelativeTimeFormat_format.call(relativeTimeFormat, 0, "hour"));
+
+// Test Intl.RelativeTimeFormat.prototype.resolvedOptions with a CCW object.
+var Intl_RelativeTimeFormat_resolvedOptions = Intl.RelativeTimeFormat.prototype.resolvedOptions;
+
+assertEq(deepEqual(Intl_RelativeTimeFormat_resolvedOptions.call(ccwRelativeTimeFormat),
+ Intl_RelativeTimeFormat_resolvedOptions.call(relativeTimeFormat)),
+ true);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/RelativeTimeFormat/format.js b/js/src/tests/non262/Intl/RelativeTimeFormat/format.js
new file mode 100644
index 0000000000..244dbb5c56
--- /dev/null
+++ b/js/src/tests/non262/Intl/RelativeTimeFormat/format.js
@@ -0,0 +1,145 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl'))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tests the format function with a diverse set of locales and options.
+
+var rtf;
+
+{
+ // Numeric format
+ rtf = new Intl.RelativeTimeFormat("en-US");
+ assertEq(rtf.format(0, "second"), "in 0 seconds");
+ assertEq(rtf.format(-0, "second"), "0 seconds ago");
+ assertEq(rtf.format(-1, "second"), "1 second ago");
+ assertEq(rtf.format(1, "second"), "in 1 second");
+
+ assertEq(rtf.format(0, "minute"), "in 0 minutes");
+ assertEq(rtf.format(-0, "minute"), "0 minutes ago");
+ assertEq(rtf.format(-1, "minute"), "1 minute ago");
+ assertEq(rtf.format(1, "minute"), "in 1 minute");
+
+ assertEq(rtf.format(0, "hour"), "in 0 hours");
+ assertEq(rtf.format(-0, "hour"), "0 hours ago");
+ assertEq(rtf.format(-1, "hour"), "1 hour ago");
+ assertEq(rtf.format(1, "hour"), "in 1 hour");
+
+ assertEq(rtf.format(0, "day"), "in 0 days");
+ assertEq(rtf.format(-0, "day"), "0 days ago");
+ assertEq(rtf.format(-1, "day"), "1 day ago");
+ assertEq(rtf.format(1, "day"), "in 1 day");
+
+ assertEq(rtf.format(0, "week"), "in 0 weeks");
+ assertEq(rtf.format(-0, "week"), "0 weeks ago");
+ assertEq(rtf.format(-1, "week"), "1 week ago");
+ assertEq(rtf.format(1, "week"), "in 1 week");
+
+ assertEq(rtf.format(0, "month"), "in 0 months");
+ assertEq(rtf.format(-0, "month"), "0 months ago");
+ assertEq(rtf.format(-1, "month"), "1 month ago");
+ assertEq(rtf.format(1, "month"), "in 1 month");
+
+ assertEq(rtf.format(0, "year"), "in 0 years");
+ assertEq(rtf.format(-0, "year"), "0 years ago");
+ assertEq(rtf.format(-1, "year"), "1 year ago");
+ assertEq(rtf.format(1, "year"), "in 1 year");
+}
+
+{
+ // Text format
+ rtf = new Intl.RelativeTimeFormat("en-US", {
+ numeric: "auto"
+ });
+ assertEq(rtf.format(0, "second"), "now");
+ assertEq(rtf.format(-0, "second"), "now");
+ assertEq(rtf.format(-1, "second"), "1 second ago");
+ assertEq(rtf.format(1, "second"), "in 1 second");
+
+ assertEq(rtf.format(0, "minute"), "this minute");
+ assertEq(rtf.format(-0, "minute"), "this minute");
+ assertEq(rtf.format(-1, "minute"), "1 minute ago");
+ assertEq(rtf.format(1, "minute"), "in 1 minute");
+
+ assertEq(rtf.format(0, "hour"), "this hour");
+ assertEq(rtf.format(-0, "hour"), "this hour");
+ assertEq(rtf.format(-1, "hour"), "1 hour ago");
+ assertEq(rtf.format(1, "hour"), "in 1 hour");
+
+ assertEq(rtf.format(0, "day"), "today");
+ assertEq(rtf.format(-0, "day"), "today");
+ assertEq(rtf.format(-1, "day"), "yesterday");
+ assertEq(rtf.format(1, "day"), "tomorrow");
+
+ assertEq(rtf.format(0, "week"), "this week");
+ assertEq(rtf.format(-0, "week"), "this week");
+ assertEq(rtf.format(-1, "week"), "last week");
+ assertEq(rtf.format(1, "week"), "next week");
+
+ assertEq(rtf.format(0, "month"), "this month");
+ assertEq(rtf.format(-0, "month"), "this month");
+ assertEq(rtf.format(-1, "month"), "last month");
+ assertEq(rtf.format(1, "month"), "next month");
+
+ assertEq(rtf.format(0, "year"), "this year");
+ assertEq(rtf.format(-0, "year"), "this year");
+ assertEq(rtf.format(-1, "year"), "last year");
+ assertEq(rtf.format(1, "year"), "next year");
+}
+
+{
+ // Plural specifier
+ rtf = new Intl.RelativeTimeFormat("en-US");
+ assertEq(rtf.format(1, "seconds"), "in 1 second");
+ assertEq(rtf.format(1, "minutes"), "in 1 minute");
+ assertEq(rtf.format(1, "hours"), "in 1 hour");
+ assertEq(rtf.format(1, "days"), "in 1 day");
+ assertEq(rtf.format(1, "weeks"), "in 1 week");
+ assertEq(rtf.format(1, "months"), "in 1 month");
+ assertEq(rtf.format(1, "years"), "in 1 year");
+}
+
+rtf = new Intl.RelativeTimeFormat("de", {numeric: "auto"});
+assertEq(rtf.format(-1, "day"), "gestern");
+assertEq(rtf.format(1, "day"), "morgen");
+
+rtf = new Intl.RelativeTimeFormat("ar", {numeric: "auto"});
+assertEq(rtf.format(-1, "day"), "أمس");
+assertEq(rtf.format(1, "day"), "غدًا");
+
+
+rtf = new Intl.RelativeTimeFormat("en-US");
+
+var weirdValueCases = [
+ Infinity,
+ -Infinity,
+ NaN,
+ "word",
+ [0,2],
+ {},
+];
+
+for (let c of weirdValueCases) {
+ assertThrowsInstanceOf(() => rtf.format(c, "year"), RangeError);
+};
+
+var weirdUnitCases = [
+ "test",
+ "SECOND",
+ "sEcOnD",
+ 1,
+ NaN,
+ undefined,
+ null,
+ {},
+];
+
+for (let u of weirdUnitCases) {
+ assertThrowsInstanceOf(function() {
+ var rtf = new Intl.RelativeTimeFormat("en-US");
+ rtf.format(1, u);
+ }, RangeError);
+};
+
+
+reportCompare(0, 0, 'ok');
diff --git a/js/src/tests/non262/Intl/RelativeTimeFormat/locale-fallback-handling.js b/js/src/tests/non262/Intl/RelativeTimeFormat/locale-fallback-handling.js
new file mode 100644
index 0000000000..76917c6fa0
--- /dev/null
+++ b/js/src/tests/non262/Intl/RelativeTimeFormat/locale-fallback-handling.js
@@ -0,0 +1,15 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// In locales that don't have a relative-date/time formatter -- and presently
+// "ak" is such a locale -- behavior is expected to fall back to the root-locale
+// formatter. This test verifies such fallback works as long as "ak" satisfies
+// these properties; "ak" may safely be changed to a different locale if that
+// ever changes. See bug 1504656.
+assertEq(new Intl.RelativeTimeFormat("ak").format(1, "second"),
+ "+1 s");
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, 'ok');
diff --git a/js/src/tests/non262/Intl/RelativeTimeFormat/numbering-system.js b/js/src/tests/non262/Intl/RelativeTimeFormat/numbering-system.js
new file mode 100644
index 0000000000..d9a860bb01
--- /dev/null
+++ b/js/src/tests/non262/Intl/RelativeTimeFormat/numbering-system.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Ensure passing the default numbering system leads to the same result as when
+// no explicit numbering system is present.
+//
+// This is a regression test for the ICU issue reported at
+// <https://unicode-org.atlassian.net/browse/ICU-20280>.
+
+for (var requestedLocale of [undefined, "en", "de", "fr"]) {
+ var rtf = new Intl.RelativeTimeFormat(requestedLocale);
+ var {locale, numberingSystem} = rtf.resolvedOptions();
+ var rtfNu = new Intl.RelativeTimeFormat(`${locale}-u-nu-${numberingSystem}`);
+
+ for (var unit of ["year", "quarter", "month", "week", "day", "hour", "minute", "second"]) {
+ for (var value of [-10, -3, -2, -1, 0, 1, 2, 3, 10]) {
+ assertEq(rtfNu.format(value, unit), rtf.format(value, unit));
+ }
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/RelativeTimeFormat/numberingSystem-option.js b/js/src/tests/non262/Intl/RelativeTimeFormat/numberingSystem-option.js
new file mode 100644
index 0000000000..bbde290dfa
--- /dev/null
+++ b/js/src/tests/non262/Intl/RelativeTimeFormat/numberingSystem-option.js
@@ -0,0 +1,60 @@
+const defaultLocale = "en";
+const defaultNumberingSystem = new Intl.RelativeTimeFormat(defaultLocale).resolvedOptions().numberingSystem;
+
+function createWithLocale(locale, numberingSystem) {
+ return new Intl.RelativeTimeFormat(locale, {numberingSystem});
+}
+
+function create(numberingSystem) {
+ return createWithLocale(defaultLocale, numberingSystem);
+}
+
+// Empty string should throw.
+assertThrowsInstanceOf(() => create(""), RangeError);
+
+// Trailing \0 should throw.
+assertThrowsInstanceOf(() => create("latn\0"), RangeError);
+
+// Too short or too long strings should throw.
+assertThrowsInstanceOf(() => create("a"), RangeError);
+assertThrowsInstanceOf(() => create("toolongstring"), RangeError);
+
+// Throw even when prefix is valid.
+assertThrowsInstanceOf(() => create("latn-toolongstring"), RangeError);
+
+// |numberingSystem| can be set to |undefined|.
+let nf = create(undefined);
+assertEq(nf.resolvedOptions().numberingSystem, defaultNumberingSystem);
+
+// Unsupported numbering systems are ignored.
+nf = create("xxxxxxxx");
+assertEq(nf.resolvedOptions().numberingSystem, defaultNumberingSystem);
+
+// Numbering system in options overwrite Unicode extension keyword.
+nf = createWithLocale(`${defaultLocale}-u-nu-thai`, "arab");
+assertEq(nf.resolvedOptions().locale, defaultLocale);
+assertEq(nf.resolvedOptions().numberingSystem, "arab");
+
+// |numberingSystem| option ignores case.
+nf = create("ARAB");
+assertEq(nf.resolvedOptions().locale, defaultLocale);
+assertEq(nf.resolvedOptions().numberingSystem, "arab");
+
+for (let [numberingSystem, {algorithmic}] of Object.entries(numberingSystems)) {
+ let nf1 = new Intl.RelativeTimeFormat(`${defaultLocale}-u-nu-${numberingSystem}`);
+ let nf2 = new Intl.RelativeTimeFormat(defaultLocale, {numberingSystem});
+
+ if (!algorithmic) {
+ assertEq(nf1.resolvedOptions().numberingSystem, numberingSystem);
+ assertEq(nf2.resolvedOptions().numberingSystem, numberingSystem);
+ } else {
+ // We don't yet support algorithmic numbering systems.
+ assertEq(nf1.resolvedOptions().numberingSystem, defaultNumberingSystem);
+ assertEq(nf2.resolvedOptions().numberingSystem, defaultNumberingSystem);
+ }
+
+ assertEq(nf2.format(0, "second"), nf1.format(0, "second"));
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/RelativeTimeFormat/relativetimeformat.js b/js/src/tests/non262/Intl/RelativeTimeFormat/relativetimeformat.js
new file mode 100644
index 0000000000..fb30e5a9ce
--- /dev/null
+++ b/js/src/tests/non262/Intl/RelativeTimeFormat/relativetimeformat.js
@@ -0,0 +1,15 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tests the format function with a diverse set of locales and options.
+
+var rtf;
+
+rtf = new Intl.RelativeTimeFormat("en-us");
+assertEq(rtf.resolvedOptions().locale, "en-US");
+assertEq(rtf.resolvedOptions().style, "long");
+assertEq(rtf.resolvedOptions().numeric, "always");
+
+reportCompare(0, 0, 'ok');
diff --git a/js/src/tests/non262/Intl/RelativeTimeFormat/shell.js b/js/src/tests/non262/Intl/RelativeTimeFormat/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Intl/RelativeTimeFormat/shell.js
diff --git a/js/src/tests/non262/Intl/RelativeTimeFormat/supportedLocalesOf.js b/js/src/tests/non262/Intl/RelativeTimeFormat/supportedLocalesOf.js
new file mode 100644
index 0000000000..214b12570e
--- /dev/null
+++ b/js/src/tests/non262/Intl/RelativeTimeFormat/supportedLocalesOf.js
@@ -0,0 +1,373 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl')||xulRuntime.shell)
+// -- test in browser only that ICU has locale data for all Mozilla languages
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This array contains the locales that ICU supports in
+// number formatting whose languages Mozilla localizes Firefox into.
+// Current as of ICU 50.1.2 and Firefox March 2013.
+var locales = [
+ "af",
+ "af-NA",
+ "af-ZA",
+ "ar",
+ "ar-001",
+ "ar-AE",
+ "ar-BH",
+ "ar-DJ",
+ "ar-DZ",
+ "ar-EG",
+ "ar-EH",
+ "ar-ER",
+ "ar-IL",
+ "ar-IQ",
+ "ar-JO",
+ "ar-KM",
+ "ar-KW",
+ "ar-LB",
+ "ar-LY",
+ "ar-MA",
+ "ar-MR",
+ "ar-OM",
+ "ar-PS",
+ "ar-QA",
+ "ar-SA",
+ "ar-SD",
+ "ar-SO",
+ "ar-SY",
+ "ar-TD",
+ "ar-TN",
+ "ar-YE",
+ "as",
+ "as-IN",
+ "be",
+ "be-BY",
+ "bg",
+ "bg-BG",
+ "bn",
+ "bn-BD",
+ "bn-IN",
+ "br",
+ "br-FR",
+ "bs",
+ "bs-Cyrl",
+ "bs-Cyrl-BA",
+ "bs-Latn",
+ "bs-Latn-BA",
+ "ca",
+ "ca-AD",
+ "ca-ES",
+ "cs",
+ "cs-CZ",
+ "cy",
+ "cy-GB",
+ "da",
+ "da-DK",
+ "de",
+ "de-AT",
+ "de-BE",
+ "de-CH",
+ "de-DE",
+ "de-LI",
+ "de-LU",
+ "el",
+ "el-CY",
+ "el-GR",
+ "en",
+ "en-150",
+ "en-AG",
+ "en-AS",
+ "en-AU",
+ "en-BB",
+ "en-BE",
+ "en-BM",
+ "en-BS",
+ "en-BW",
+ "en-BZ",
+ "en-CA",
+ "en-CM",
+ "en-DM",
+ "en-FJ",
+ "en-FM",
+ "en-GB",
+ "en-GD",
+ "en-GG",
+ "en-GH",
+ "en-GI",
+ "en-GM",
+ "en-GU",
+ "en-GY",
+ "en-HK",
+ "en-IE",
+ "en-IM",
+ "en-IN",
+ "en-JE",
+ "en-JM",
+ "en-KE",
+ "en-KI",
+ "en-KN",
+ "en-KY",
+ "en-LC",
+ "en-LR",
+ "en-LS",
+ "en-MG",
+ "en-MH",
+ "en-MP",
+ "en-MT",
+ "en-MU",
+ "en-MW",
+ "en-NA",
+ "en-NG",
+ "en-NZ",
+ "en-PG",
+ "en-PH",
+ "en-PK",
+ "en-PR",
+ "en-PW",
+ "en-SB",
+ "en-SC",
+ "en-SG",
+ "en-SL",
+ "en-SS",
+ "en-SZ",
+ "en-TC",
+ "en-TO",
+ "en-TT",
+ "en-TZ",
+ "en-UG",
+ "en-UM",
+ "en-US",
+ "en-US-posix",
+ "en-VC",
+ "en-VG",
+ "en-VI",
+ "en-VU",
+ "en-WS",
+ "en-ZA",
+ "en-ZM",
+ "en-ZW",
+ "eo",
+ "es",
+ "es-419",
+ "es-AR",
+ "es-BO",
+ "es-CL",
+ "es-CO",
+ "es-CR",
+ "es-CU",
+ "es-DO",
+ "es-EA",
+ "es-EC",
+ "es-ES",
+ "es-GQ",
+ "es-GT",
+ "es-HN",
+ "es-IC",
+ "es-MX",
+ "es-NI",
+ "es-PA",
+ "es-PE",
+ "es-PH",
+ "es-PR",
+ "es-PY",
+ "es-SV",
+ "es-US",
+ "es-UY",
+ "es-VE",
+ "et",
+ "et-EE",
+ "eu",
+ "eu-ES",
+ "fa",
+ "fa-AF",
+ "fa-IR",
+ "ff",
+ "ff-SN",
+ "fi",
+ "fi-FI",
+ "fr",
+ "fr-BE",
+ "fr-BF",
+ "fr-BI",
+ "fr-BJ",
+ "fr-BL",
+ "fr-CA",
+ "fr-CD",
+ "fr-CF",
+ "fr-CG",
+ "fr-CH",
+ "fr-CI",
+ "fr-CM",
+ "fr-DJ",
+ "fr-DZ",
+ "fr-FR",
+ "fr-GA",
+ "fr-GF",
+ "fr-GN",
+ "fr-GP",
+ "fr-GQ",
+ "fr-HT",
+ "fr-KM",
+ "fr-LU",
+ "fr-MA",
+ "fr-MC",
+ "fr-MF",
+ "fr-MG",
+ "fr-ML",
+ "fr-MQ",
+ "fr-MR",
+ "fr-MU",
+ "fr-NC",
+ "fr-NE",
+ "fr-PF",
+ "fr-RE",
+ "fr-RW",
+ "fr-SC",
+ "fr-SN",
+ "fr-SY",
+ "fr-TD",
+ "fr-TG",
+ "fr-TN",
+ "fr-VU",
+ "fr-YT",
+ "ga",
+ "ga-IE",
+ "gl",
+ "gl-ES",
+ "gu",
+ "gu-IN",
+ "he",
+ "he-IL",
+ "hi",
+ "hi-IN",
+ "hr",
+ "hr-BA",
+ "hr-HR",
+ "hu",
+ "hu-HU",
+ "hy",
+ "hy-AM",
+ "id",
+ "id-ID",
+ "is",
+ "is-IS",
+ "it",
+ "it-CH",
+ "it-IT",
+ "it-SM",
+ "ja",
+ "ja-JP",
+ "kk",
+ "kk-Cyrl",
+ "kk-Cyrl-KZ",
+ "km",
+ "km-KH",
+ "kn",
+ "kn-IN",
+ "ko",
+ "ko-KP",
+ "ko-KR",
+ "lt",
+ "lt-LT",
+ "lv",
+ "lv-LV",
+ "mk",
+ "mk-MK",
+ "ml",
+ "ml-IN",
+ "mr",
+ "mr-IN",
+ "nb",
+ "nb-NO",
+ "nl",
+ "nl-AW",
+ "nl-BE",
+ "nl-CW",
+ "nl-NL",
+ "nl-SR",
+ "nl-SX",
+ "nn",
+ "nn-NO",
+ "or",
+ "or-IN",
+ "pa",
+ "pa-Arab",
+ "pa-Arab-PK",
+ "pa-Guru",
+ "pa-Guru-IN",
+ "pl",
+ "pl-PL",
+ "pt",
+ "pt-AO",
+ "pt-BR",
+ "pt-CV",
+ "pt-GW",
+ "pt-MO",
+ "pt-MZ",
+ "pt-PT",
+ "pt-ST",
+ "pt-TL",
+ "rm",
+ "rm-CH",
+ "ro",
+ "ro-MD",
+ "ro-RO",
+ "ru",
+ "ru-BY",
+ "ru-KG",
+ "ru-KZ",
+ "ru-MD",
+ "ru-RU",
+ "ru-UA",
+ "si",
+ "si-LK",
+ "sk",
+ "sk-SK",
+ "sl",
+ "sl-SI",
+ "sq",
+ "sq-AL",
+ "sq-MK",
+ "sr",
+ "sr-Cyrl",
+ "sr-Cyrl-BA",
+ "sr-Cyrl-ME",
+ "sr-Cyrl-RS",
+ "sr-Latn",
+ "sr-Latn-BA",
+ "sr-Latn-ME",
+ "sr-Latn-RS",
+ "sv",
+ "sv-AX",
+ "sv-FI",
+ "sv-SE",
+ "te",
+ "te-IN",
+ "th",
+ "th-TH",
+ "tr",
+ "tr-CY",
+ "tr-TR",
+ "uk",
+ "uk-UA",
+ "vi",
+ "vi-VN",
+ "zh",
+ "zh-Hans",
+ "zh-Hans-CN",
+ "zh-Hans-HK",
+ "zh-Hans-MO",
+ "zh-Hans-SG",
+ "zh-Hant",
+ "zh-Hant-HK",
+ "zh-Hant-MO",
+ "zh-Hant-TW",
+];
+
+const result = Intl.RelativeTimeFormat.supportedLocalesOf(locales);
+
+assertEqArray(locales, result);
+
+reportCompare(0, 0, 'ok');
diff --git a/js/src/tests/non262/Intl/String/shell.js b/js/src/tests/non262/Intl/String/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Intl/String/shell.js
diff --git a/js/src/tests/non262/Intl/String/toLocaleLowerCase.js b/js/src/tests/non262/Intl/String/toLocaleLowerCase.js
new file mode 100644
index 0000000000..bd81717d22
--- /dev/null
+++ b/js/src/tests/non262/Intl/String/toLocaleLowerCase.js
@@ -0,0 +1,64 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Test language dependent special casing with different language tags.
+for (let locale of ["tr", "TR", "tr-TR", "tr-u-co-search", "tr-x-turkish"]) {
+ assertEq("\u0130".toLocaleLowerCase(locale), "i");
+ assertEq("\u0130".toLocaleLowerCase([locale]), "i");
+
+ // Additional language tags are ignored.
+ assertEq("\u0130".toLocaleLowerCase([locale, "und"]), "i");
+ assertEq("\u0130".toLocaleLowerCase(["und", locale]), "\u0069\u0307");
+}
+
+// Ensure "trl" (Traveller Scottish) isn't misrecognized as "tr", even though
+// both share the same prefix.
+assertEq("\u0130".toLocaleLowerCase("trl"), "\u0069\u0307");
+assertEq("\u0130".toLocaleLowerCase(["trl"]), "\u0069\u0307");
+
+// Language tag is always verified.
+for (let locale of ["no_locale", "tr-invalid_ext", ["no_locale"], ["en", "no_locale"]]) {
+ // Empty input string.
+ assertThrowsInstanceOf(() => "".toLocaleLowerCase(locale), RangeError);
+
+ // Non-empty input string.
+ assertThrowsInstanceOf(() => "x".toLocaleLowerCase(locale), RangeError);
+}
+
+// No locale argument, undefined as locale, and empty array or array-like all
+// return the same result. Testing with "a/A" because it has only simple case
+// mappings.
+assertEq("A".toLocaleLowerCase(), "a");
+assertEq("A".toLocaleLowerCase(undefined), "a");
+assertEq("A".toLocaleLowerCase([]), "a");
+assertEq("A".toLocaleLowerCase({}), "a");
+assertEq("A".toLocaleLowerCase({length: 0}), "a");
+assertEq("A".toLocaleLowerCase({length: -1}), "a");
+
+// Test with incorrect locale type.
+for (let locale of [null, 0, Math.PI, NaN, Infinity, true, false, Symbol()]) {
+ // Empty input string.
+ assertThrowsInstanceOf(() => "".toLocaleLowerCase([locale]), TypeError);
+
+ // Non-empty input string.
+ assertThrowsInstanceOf(() => "A".toLocaleLowerCase([locale]), TypeError);
+}
+
+// Primitives are converted with ToObject and then queried for .length property.
+for (let locale of [null]) {
+ // Empty input string.
+ assertThrowsInstanceOf(() => "".toLocaleLowerCase([locale]), TypeError);
+
+ // Non-empty input string.
+ assertThrowsInstanceOf(() => "A".toLocaleLowerCase([locale]), TypeError);
+}
+// ToLength(ToObject(<primitive>)) returns 0.
+for (let locale of [0, Math.PI, NaN, Infinity, true, false, Symbol()]) {
+ // Empty input string.
+ assertEq("".toLocaleLowerCase(locale), "");
+
+ // Non-empty input string.
+ assertEq("A".toLocaleLowerCase(locale), "a");
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/String/toLocaleUpperCase.js b/js/src/tests/non262/Intl/String/toLocaleUpperCase.js
new file mode 100644
index 0000000000..0a33320dc7
--- /dev/null
+++ b/js/src/tests/non262/Intl/String/toLocaleUpperCase.js
@@ -0,0 +1,64 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Test language dependent special casing with different language tags.
+for (let locale of ["lt", "LT", "lt-LT", "lt-u-co-phonebk", "lt-x-lietuva"]) {
+ assertEq("i\u0307".toLocaleUpperCase(locale), "I");
+ assertEq("i\u0307".toLocaleUpperCase([locale]), "I");
+
+ // Additional language tags are ignored.
+ assertEq("i\u0307".toLocaleUpperCase([locale, "und"]), "I");
+ assertEq("i\u0307".toLocaleUpperCase(["und", locale]), "I\u0307");
+}
+
+// Ensure "lti" (Leti) isn't misrecognized as "lt", even though both share the
+// same prefix.
+assertEq("i\u0307".toLocaleUpperCase("lti"), "I\u0307");
+assertEq("i\u0307".toLocaleUpperCase(["lti"]), "I\u0307");
+
+// Language tag is always verified.
+for (let locale of ["no_locale", "lt-invalid_ext", ["no_locale"], ["en", "no_locale"]]) {
+ // Empty input string.
+ assertThrowsInstanceOf(() => "".toLocaleUpperCase(locale), RangeError);
+
+ // Non-empty input string.
+ assertThrowsInstanceOf(() => "a".toLocaleUpperCase(locale), RangeError);
+}
+
+// No locale argument, undefined as locale, and empty array or array-like all
+// return the same result. Testing with "a/A" because it has only simple case
+// mappings.
+assertEq("a".toLocaleUpperCase(), "A");
+assertEq("a".toLocaleUpperCase(undefined), "A");
+assertEq("a".toLocaleUpperCase([]), "A");
+assertEq("a".toLocaleUpperCase({}), "A");
+assertEq("a".toLocaleUpperCase({length: 0}), "A");
+assertEq("a".toLocaleUpperCase({length: -1}), "A");
+
+// Test with incorrect locale type.
+for (let locale of [null, 0, Math.PI, NaN, Infinity, true, false, Symbol()]) {
+ // Empty input string.
+ assertThrowsInstanceOf(() => "".toLocaleUpperCase([locale]), TypeError);
+
+ // Non-empty input string.
+ assertThrowsInstanceOf(() => "a".toLocaleUpperCase([locale]), TypeError);
+}
+
+// Primitives are converted with ToObject and then queried for .length property.
+for (let locale of [null]) {
+ // Empty input string.
+ assertThrowsInstanceOf(() => "".toLocaleUpperCase([locale]), TypeError);
+
+ // Non-empty input string.
+ assertThrowsInstanceOf(() => "a".toLocaleUpperCase([locale]), TypeError);
+}
+// ToLength(ToObject(<primitive>)) returns 0.
+for (let locale of [0, Math.PI, NaN, Infinity, true, false, Symbol()]) {
+ // Empty input string.
+ assertEq("".toLocaleUpperCase(locale), "");
+
+ // Non-empty input string.
+ assertEq("a".toLocaleUpperCase(locale), "A");
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/TypedArray/shell.js b/js/src/tests/non262/Intl/TypedArray/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Intl/TypedArray/shell.js
diff --git a/js/src/tests/non262/Intl/TypedArray/toLocaleString.js b/js/src/tests/non262/Intl/TypedArray/toLocaleString.js
new file mode 100644
index 0000000000..7a5d0be30a
--- /dev/null
+++ b/js/src/tests/non262/Intl/TypedArray/toLocaleString.js
@@ -0,0 +1,103 @@
+if (typeof Intl === "object") {
+ const constructors = [
+ Int8Array,
+ Uint8Array,
+ Uint8ClampedArray,
+ Int16Array,
+ Uint16Array,
+ Int32Array,
+ Uint32Array,
+ Float32Array,
+ Float64Array,
+ ];
+
+ const localeSep = [,,].toLocaleString();
+
+ const originalNumberToLocaleString = Number.prototype.toLocaleString;
+
+ // Missing arguments are passed as |undefined|.
+ for (let constructor of constructors) {
+ Number.prototype.toLocaleString = function() {
+ assertEq(arguments.length, 2);
+ assertEq(arguments[0], undefined);
+ assertEq(arguments[1], undefined);
+ return "pass";
+ };
+
+ // Single element case.
+ assertEq(new constructor(1).toLocaleString(), "pass");
+
+ // More than one element.
+ assertEq(new constructor(2).toLocaleString(), "pass" + localeSep + "pass");
+ }
+ Number.prototype.toLocaleString = originalNumberToLocaleString;
+
+ // Missing options is passed as |undefined|.
+ for (let constructor of constructors) {
+ Number.prototype.toLocaleString = function() {
+ assertEq(arguments.length, 2);
+ assertEq(arguments[0], locales);
+ assertEq(arguments[1], undefined);
+ return "pass";
+ };
+ let locales = {};
+
+ // Single element case.
+ assertEq(new constructor(1).toLocaleString(locales), "pass");
+
+ // More than one element.
+ assertEq(new constructor(2).toLocaleString(locales), "pass" + localeSep + "pass");
+ }
+ Number.prototype.toLocaleString = originalNumberToLocaleString;
+
+ // Ensure "locales" and "options" arguments are passed to the array elements.
+ for (let constructor of constructors) {
+ Number.prototype.toLocaleString = function() {
+ assertEq(arguments.length, 2);
+ assertEq(arguments[0], locales);
+ assertEq(arguments[1], options);
+ return "pass";
+ };
+ let locales = {};
+ let options = {};
+
+ // Single element case.
+ assertEq(new constructor(1).toLocaleString(locales, options), "pass");
+
+ // More than one element.
+ assertEq(new constructor(2).toLocaleString(locales, options), "pass" + localeSep + "pass");
+ }
+ Number.prototype.toLocaleString = originalNumberToLocaleString;
+
+ assertEq(new Float32Array([NaN]).toLocaleString("ar"), "ليس رقم");
+ assertEq(new Float64Array([NaN]).toLocaleString(["zh-hant", "ar"]), "非數值");
+ assertEq(new Float32Array([Infinity]).toLocaleString(["dz"]), "གྲངས་མེད");
+ assertEq(new Float64Array([-Infinity]).toLocaleString(["fr", "en"]), "-∞");
+
+ const sampleValues = [-0, +0, -1, +1, -2, +2, -0.5, +0.5];
+ const sampleLocales = [
+ void 0,
+ "en",
+ "th-th-u-nu-thai",
+ ["tlh", "de"],
+ ];
+ const sampleOptions = [
+ void 0,
+ {},
+ {style: "percent"},
+ {style: "currency", currency: "USD", minimumIntegerDigits: 4},
+ ];
+ for (let locale of sampleLocales) {
+ for (let options of sampleOptions) {
+ let nf = new Intl.NumberFormat(locale, options);
+ for (let constructor of constructors) {
+ let typedArray = new constructor(sampleValues);
+ let expected = [].map.call(typedArray, nf.format).join(localeSep);
+ assertEq(typedArray.toLocaleString(locale, options), expected);
+ }
+ }
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/best-available-locale-from-default-locale.js b/js/src/tests/non262/Intl/best-available-locale-from-default-locale.js
new file mode 100644
index 0000000000..481d4ed7f1
--- /dev/null
+++ b/js/src/tests/non262/Intl/best-available-locale-from-default-locale.js
@@ -0,0 +1,107 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl'))
+
+if (typeof getDefaultLocale === "undefined") {
+ var getDefaultLocale = SpecialPowers.Cu.getJSTestingFunctions().getDefaultLocale;
+}
+if (typeof setDefaultLocale === "undefined") {
+ var setDefaultLocale = SpecialPowers.Cu.getJSTestingFunctions().setDefaultLocale;
+}
+
+let defaultLocale = null;
+
+function withLocale(locale, fn) {
+ if (defaultLocale === null)
+ defaultLocale = getDefaultLocale();
+
+ setDefaultLocale(locale);
+ try {
+ fn();
+ } finally {
+ setDefaultLocale(defaultLocale);
+ }
+}
+
+// This test assumes German ("de") is a supported locale.
+const supported = Intl.Collator.supportedLocalesOf("de");
+assertEq(supported.length, 1);
+assertEq(supported[0], "de");
+
+withLocale("de", () => {
+ // Ensure the new default locale is now active.
+ assertEq(new Intl.Collator().resolvedOptions().locale, "de");
+
+ // "de" is the active default locale, so explicitly requesting "de" should succeed.
+ assertEq(new Intl.Collator("de").resolvedOptions().locale, "de");
+
+ // ICU doesn't provide a specialised "de-ZA" locale, so we fallback to "de".
+ assertEq(new Intl.Collator("de-ZA").resolvedOptions().locale, "de");
+
+ // ICU doesn't provide a specialised "de-ZA" locale, so we fallback to "de".
+ assertEq(new Intl.Collator("de-ZA-x-private").resolvedOptions().locale, "de");
+});
+
+// As demonstrated above, "de-ZA-x-private" normally isn't a supported Intl.Collator locale. But
+// when used as the default locale, it gets promoted to being supported, because its parent locale
+// "de" is supported and can act as a fallback.
+//
+// This works as follows:
+// We accept any default locale as long as it can be supported either explicitly or implicitly
+// through a fallback. But when we claim a default locale is supported, we also need to make sure
+// we report any parent locale as being supported. So when "de-ZA-x-private" is accepted as the
+// default locale, we also need to report its parent locale "de-ZA" as a supported locale.
+//
+// The reason we're doing this, is to make sure we aren't limiting the supported default locale to
+// the intersection of the sets of supported locales for each Intl service constructor. Also see
+// the requirements in <https://tc39.es/ecma402/#sec-internal-slots>, which state that the default
+// locale must be a member of [[AvailableLocales]] for every Intl service constructor.
+//
+// So the following statement must hold:
+//
+// ∀ Constructor ∈ IntlConstructors: DefaultLocale ∈ Constructor.[[AvailableLocales]]
+//
+// This can trivially be achieved when we restrict the default locale to:
+//
+// { RequestedLocale if RequestedLocale ∈ (∩ C.[[AvailableLocales]])
+// { C ∈ IntlConstructors
+// {
+// DefaultLocale = { Fallback(RequestedLocale) if Fallback(RequestedLocale) ∈ (∩ C.[[AvailableLocales]])
+// { C ∈ IntlConstructors
+// {
+// { LastDitchLocale otherwise
+//
+// But that severely restricts the possible default locales. For example, "de-CH" is supported by
+// all Intl constructors except Intl.Collator. Intl.Collator itself only provides explicit support
+// for the parent locale "de". So with the trivial solution we'd need to mark "de-CH" as an invalid
+// default locale and instead use its fallback locale "de".
+//
+// So instead of that we're using the following approach:
+//
+// { RequestedLocale if RequestedLocale ∈ (∩ C.[[AvailableLocales]])
+// { C ∈ IntlConstructors
+// {
+// DefaultLocale = { RequestedLocale if Fallback(RequestedLocale) ∈ (∩ C.[[AvailableLocales]])
+// { C ∈ IntlConstructors
+// {
+// { LastDitchLocale otherwise
+//
+// So even when the requested default locale is only implicitly supported through a fallback, we
+// still accept it as a valid default locale.
+withLocale("de-ZA-x-private", () => {
+ // Ensure the new default locale is now active.
+ assertEq(new Intl.Collator().resolvedOptions().locale, "de-ZA-x-private");
+
+ // "de-ZA-x-private" is the active default locale, so explicitly requesting the parent locale
+ // "de" should succeed.
+ assertEq(new Intl.Collator("de").resolvedOptions().locale, "de");
+
+ // "de-ZA-x-private" is the active default locale, so explicitly requesting the parent locale
+ // "de-ZA" should succeed.
+ assertEq(new Intl.Collator("de-ZA").resolvedOptions().locale, "de-ZA");
+
+ // "de-ZA-x-private" is the active default locale, so explicitly requesting "de-ZA-x-private"
+ // should succeed.
+ assertEq(new Intl.Collator("de-ZA-x-private").resolvedOptions().locale, "de-ZA-x-private");
+});
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/browser.js b/js/src/tests/non262/Intl/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Intl/browser.js
diff --git a/js/src/tests/non262/Intl/default-locale-shell.js b/js/src/tests/non262/Intl/default-locale-shell.js
new file mode 100644
index 0000000000..ea3f6d17db
--- /dev/null
+++ b/js/src/tests/non262/Intl/default-locale-shell.js
@@ -0,0 +1,16 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||!xulRuntime.shell)
+
+// js/src/tests/lib/tests.py sets the default locale to "en-US" for shell tests.
+// Ensure it's correctly set in the runtime and for the Intl service constructors.
+const defaultLocale = "en-US";
+
+assertEq(getDefaultLocale(), defaultLocale);
+
+assertEq(new Intl.Collator().resolvedOptions().locale, defaultLocale);
+assertEq(new Intl.DateTimeFormat().resolvedOptions().locale, defaultLocale);
+assertEq(new Intl.NumberFormat().resolvedOptions().locale, defaultLocale);
+assertEq(new Intl.PluralRules().resolvedOptions().locale, defaultLocale);
+assertEq(new Intl.RelativeTimeFormat().resolvedOptions().locale, defaultLocale);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/duplicate-variants.js b/js/src/tests/non262/Intl/duplicate-variants.js
new file mode 100644
index 0000000000..5d07a9b109
--- /dev/null
+++ b/js/src/tests/non262/Intl/duplicate-variants.js
@@ -0,0 +1,46 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// RFC 5646 section 2.1
+// variant = 5*8alphanum ; registered variants
+// / (DIGIT 3alphanum)
+
+// Duplicate variants are forbidden.
+assertEqArray(Intl.getCanonicalLocales("de-1996"), ["de-1996"]);
+assertThrowsInstanceOf(() => Intl.getCanonicalLocales("de-1996-1996"), RangeError);
+
+// Multiple different variants are allowed.
+assertEqArray(Intl.getCanonicalLocales("sl-rozaj-solba"), ["sl-rozaj-solba"]);
+
+// Variants can have the same prefix.
+assertEqArray(Intl.getCanonicalLocales("zh-Latn-pinyin-pinyin2"), ["zh-Latn-pinyin-pinyin2"]);
+
+// Values in extension sequences are not counted as variants.
+assertEqArray(Intl.getCanonicalLocales("en-u-kf-false-kn-false"), ["en-u-kf-false-kn-false"]);
+
+// Also test duplicates in Unicode extension keywords and attributes.
+// From https://tools.ietf.org/html/rfc6067#section-2.1
+//
+// An 'attribute' is a subtag with a length of three to eight
+// characters following the singleton and preceding any 'keyword'
+// sequences. No attributes were defined at the time of this
+// document's publication.
+//
+// A 'keyword' is a sequence of subtags consisting of a 'key' subtag,
+// followed by zero or more 'type' subtags (so a 'key' might appear
+// alone and not be accompanied by a 'type' subtag). A 'key' MUST
+// NOT appear more than once in a language tag's extension string.
+//
+// ...
+//
+// Only the first occurrence of an attribute or key conveys meaning in a
+// language tag. When interpreting tags containing the Unicode locale
+// extension, duplicate attributes or keywords are ignored in the
+// following way: ignore any attribute that has already appeared in the
+// tag and ignore any keyword whose key has already occurred in the tag.
+//
+// The duplicates itself are removed in CanonicalizeUnicodeLocaleId, step 2-3.
+assertEqArray(Intl.getCanonicalLocales("en-u-kn-false-kn-false"), ["en-u-kn-false"]);
+assertEqArray(Intl.getCanonicalLocales("en-u-attr1-attr2-attr2"), ["en-u-attr1-attr2"]);
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/extensions/browser.js b/js/src/tests/non262/Intl/extensions/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Intl/extensions/browser.js
diff --git a/js/src/tests/non262/Intl/extensions/options-value-emulates-undefined.js b/js/src/tests/non262/Intl/extensions/options-value-emulates-undefined.js
new file mode 100644
index 0000000000..5b2ee124aa
--- /dev/null
+++ b/js/src/tests/non262/Intl/extensions/options-value-emulates-undefined.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!xulRuntime.shell||!this.hasOwnProperty('Intl'))
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 843004;
+var summary =
+ "Use of an object that emulates |undefined| as the sole option must " +
+ "preclude imputing default values";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+var opt = createIsHTMLDDA();
+opt.toString = function() { return "long"; };
+
+var str = new Date(2013, 12 - 1, 14).toLocaleString("en-US", { weekday: opt });
+
+// Because "weekday" was present and not undefined (stringifying to "long"),
+// this must be a string like "Saturday" (in this implementation, that is).
+assertEq(str, "Saturday");
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
+
+print("Tests complete");
diff --git a/js/src/tests/non262/Intl/extensions/shell.js b/js/src/tests/non262/Intl/extensions/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Intl/extensions/shell.js
diff --git a/js/src/tests/non262/Intl/extensions/unicode-extension-sequences.js b/js/src/tests/non262/Intl/extensions/unicode-extension-sequences.js
new file mode 100644
index 0000000000..ead69a4632
--- /dev/null
+++ b/js/src/tests/non262/Intl/extensions/unicode-extension-sequences.js
@@ -0,0 +1,74 @@
+// |reftest| skip-if(!this.getSelfHostedValue)
+
+const startOfUnicodeExtensions = getSelfHostedValue("startOfUnicodeExtensions");
+const endOfUnicodeExtensions = getSelfHostedValue("endOfUnicodeExtensions");
+
+const testcases = [
+ // Language tag without Unicode extension.
+ { locale: "en", start: -1, end: 0 },
+ { locale: "en-Latn", start: -1, end: 0 },
+ { locale: "en-x-y", start: -1, end: 0 },
+ { locale: "en-x-yz", start: -1, end: 0 },
+ { locale: "en-x-u-kf", start: -1, end: 0 },
+
+ // Unicode extension sequence starts with key subtag.
+ // - no suceeding key or type subtags.
+ { locale: "en-u-ab", start: 2, end: 7 },
+ { locale: "en-u-ab-x-y", start: 2, end: 7 },
+ { locale: "en-u-ab-x-yz", start: 2, end: 7 },
+ { locale: "en-u-ab-x-u-kn", start: 2, end: 7 },
+ // - followed by key subtag.
+ { locale: "en-u-ab-cd", start: 2, end: 10 },
+ { locale: "en-u-ab-cd-x-y", start: 2, end: 10 },
+ { locale: "en-u-ab-cd-x-yz", start: 2, end: 10 },
+ { locale: "en-u-ab-cd-x-u-kn", start: 2, end: 10 },
+ // - followed by type subtag.
+ { locale: "en-u-ab-cdef", start: 2, end: 12 },
+ { locale: "en-u-ab-cdef-x-y", start: 2, end: 12 },
+ { locale: "en-u-ab-cdef-x-yz", start: 2, end: 12 },
+ { locale: "en-u-ab-cdef-x-y-u-kn", start: 2, end: 12 },
+
+ // Unicode extension sequence starts with attribute subtag.
+ // - no suceeding attribute or key subtags.
+ { locale: "en-u-abc", start: 2, end: 8 },
+ { locale: "en-u-abc-x-y", start: 2, end: 8 },
+ { locale: "en-u-abc-x-yz", start: 2, end: 8 },
+ { locale: "en-u-abc-x-y-u-kn", start: 2, end: 8 },
+ // - followed by attribute subtag.
+ { locale: "en-u-abc-def", start: 2, end: 12 },
+ { locale: "en-u-abc-def-x-y", start: 2, end: 12 },
+ { locale: "en-u-abc-def-x-yz", start: 2, end: 12 },
+ { locale: "en-u-abc-def-x-y-u-kn", start: 2, end: 12 },
+ // - followed by key subtag.
+ { locale: "en-u-abc-de", start: 2, end: 11 },
+ { locale: "en-u-abc-de-x-y", start: 2, end: 11 },
+ { locale: "en-u-abc-de-x-yz", start: 2, end: 11 },
+ { locale: "en-u-abc-de-x-y-u-kn", start: 2, end: 11 },
+ // - followed by two key subtags.
+ { locale: "en-u-abc-de-fg", start: 2, end: 14 },
+ { locale: "en-u-abc-de-fg-x-y", start: 2, end: 14 },
+ { locale: "en-u-abc-de-fg-x-yz", start: 2, end: 14 },
+ { locale: "en-u-abc-de-fg-x-y-u-kn", start: 2, end: 14 },
+ // - followed by key and type subtag.
+ { locale: "en-u-abc-de-fgh", start: 2, end: 15 },
+ { locale: "en-u-abc-de-fgh-x-y", start: 2, end: 15 },
+ { locale: "en-u-abc-de-fgh-x-yz", start: 2, end: 15 },
+ { locale: "en-u-abc-de-fgh-x-y-u-kn", start: 2, end: 15 },
+
+ // Also test when the Unicode extension doesn't start at index 2.
+ { locale: "en-Latn-u-kf", start: 7, end: 12 },
+ { locale: "und-u-kf", start: 3, end: 8 },
+];
+
+for (const {locale, start, end} of testcases) {
+ // Ensure the input is a valid language tag.
+ assertEqArray(Intl.getCanonicalLocales(locale), [locale]);
+
+ assertEq(startOfUnicodeExtensions(locale), start);
+
+ if (start >= 0)
+ assertEq(endOfUnicodeExtensions(locale, start), end);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/fallback-symbol.js b/js/src/tests/non262/Intl/fallback-symbol.js
new file mode 100644
index 0000000000..87943257ce
--- /dev/null
+++ b/js/src/tests/non262/Intl/fallback-symbol.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl'))
+
+function IntlFallbackSymbol(constructor) {
+ return Object.getOwnPropertySymbols(constructor.call(Object.create(constructor.prototype)))[0];
+}
+
+const dateTimeFormatIntlFallbackSymbol = IntlFallbackSymbol(Intl.DateTimeFormat);
+const numberFormatIntlFallbackSymbol = IntlFallbackSymbol(Intl.NumberFormat);
+
+// Intl.DateTimeFormat and Intl.NumberFormat both use the same fallback symbol.
+assertEq(dateTimeFormatIntlFallbackSymbol, numberFormatIntlFallbackSymbol);
+
+const intlFallbackSymbol = dateTimeFormatIntlFallbackSymbol;
+
+// The fallback symbol is a Symbol value.
+assertEq(typeof intlFallbackSymbol, "symbol");
+
+// Test the description of the fallback symbol.
+assertEq(Symbol.prototype.toString.call(intlFallbackSymbol), "Symbol(IntlLegacyConstructedSymbol)");
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/four-letter-language-codes.js b/js/src/tests/non262/Intl/four-letter-language-codes.js
new file mode 100644
index 0000000000..394efacf29
--- /dev/null
+++ b/js/src/tests/non262/Intl/four-letter-language-codes.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Four letter language subtags are not allowed.
+const languageTags = [
+ "root", // Special meaning in Unicode CLDR locale identifiers.
+ "Latn", // Unicode CLDR locale identifiers can start with a script subtag.
+ "Flob", // And now some non-sense input.
+ "ZORK",
+ "Blah-latn",
+ "QuuX-latn-us",
+ "SPAM-gb-x-Sausages-BACON-eggs",
+];
+
+for (let tag of languageTags) {
+ assertThrowsInstanceOf(() => Intl.getCanonicalLocales(tag), RangeError);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/getCalendarInfo.js b/js/src/tests/non262/Intl/getCalendarInfo.js
new file mode 100644
index 0000000000..951fd820ea
--- /dev/null
+++ b/js/src/tests/non262/Intl/getCalendarInfo.js
@@ -0,0 +1,83 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||!this.hasOwnProperty("addIntlExtras"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tests the getCalendarInfo function with a diverse set of arguments.
+
+function checkCalendarInfo(info, expected)
+{
+ assertEq(Object.getPrototypeOf(info), Object.prototype);
+
+ assertEq(info.firstDayOfWeek, expected.firstDayOfWeek);
+ assertEq(info.minDays, expected.minDays);
+ assertEq(info.weekendStart, expected.weekendStart);
+ assertEq(info.weekendEnd, expected.weekendEnd);
+ assertEq(info.calendar, expected.calendar);
+ assertEq(info.locale, expected.locale);
+}
+
+addIntlExtras(Intl);
+
+let gCI = Intl.getCalendarInfo;
+
+assertEq(gCI.length, 1);
+
+checkCalendarInfo(gCI('en-US'), {
+ firstDayOfWeek: 1,
+ minDays: 1,
+ weekendStart: 7,
+ weekendEnd: 1,
+ calendar: "gregory",
+ locale: "en-US"
+});
+
+checkCalendarInfo(gCI('en-IL'), {
+ firstDayOfWeek: 1,
+ minDays: 1,
+ weekendStart: 6,
+ weekendEnd: 7,
+ calendar: "gregory",
+ locale: "en-IL"
+});
+
+
+checkCalendarInfo(gCI('en-GB'), {
+ firstDayOfWeek: 2,
+ minDays: 4,
+ weekendStart: 7,
+ weekendEnd: 1,
+ calendar: "gregory",
+ locale: "en-GB"
+});
+
+
+checkCalendarInfo(gCI('pl'), {
+ firstDayOfWeek: 2,
+ minDays: 4,
+ weekendStart: 7,
+ weekendEnd: 1,
+ calendar: "gregory",
+ locale: "pl"
+});
+
+checkCalendarInfo(gCI('ar-IQ'), {
+ firstDayOfWeek: 7,
+ minDays: 1,
+ weekendStart: 6,
+ weekendEnd: 7,
+ calendar: "gregory",
+ locale: "ar-IQ"
+});
+
+checkCalendarInfo(gCI('fa-IR'), {
+ firstDayOfWeek: 7,
+ minDays: 1,
+ weekendStart: 6,
+ weekendEnd: 6,
+ calendar: "persian",
+ locale: "fa-IR"
+});
+
+if (typeof reportCompare === 'function')
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/getCanonicalLocales-overridden-arg-length.js b/js/src/tests/non262/Intl/getCanonicalLocales-overridden-arg-length.js
new file mode 100644
index 0000000000..f0cb3d4ee3
--- /dev/null
+++ b/js/src/tests/non262/Intl/getCanonicalLocales-overridden-arg-length.js
@@ -0,0 +1,20 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tests the getCanonicalLocales function for overridden argument's length.
+
+var count = 0;
+var locs = { get length() { if (count++ > 0) throw 42; return 0; } };
+var locales = Intl.getCanonicalLocales(locs); // shouldn't throw 42
+assertEq(locales.length, 0);
+
+
+var obj = { get 0() { throw new Error("must not be gotten!"); },
+ length: -Math.pow(2, 32) + 1 };
+
+assertEq(Intl.getCanonicalLocales(obj).length, 0);
+
+if (typeof reportCompare === 'function')
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/getCanonicalLocales-overridden-push.js b/js/src/tests/non262/Intl/getCanonicalLocales-overridden-push.js
new file mode 100644
index 0000000000..36f53ebf06
--- /dev/null
+++ b/js/src/tests/non262/Intl/getCanonicalLocales-overridden-push.js
@@ -0,0 +1,16 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tests the getCanonicalLocales function for overriden Array.push.
+
+Array.prototype.push = () => { throw 42; };
+
+// must not throw 42, might if push is used
+var arr = Intl.getCanonicalLocales(["en-US"]);
+
+assertEqArray(arr, ["en-US"]);
+
+if (typeof reportCompare === 'function')
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/getCanonicalLocales-overridden-set.js b/js/src/tests/non262/Intl/getCanonicalLocales-overridden-set.js
new file mode 100644
index 0000000000..396f3a9483
--- /dev/null
+++ b/js/src/tests/non262/Intl/getCanonicalLocales-overridden-set.js
@@ -0,0 +1,16 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tests the getCanonicalLocales function for overridden set().
+
+Object.defineProperty(Array.prototype, 0, { set() { throw 42; } });
+
+// must not throw 42, might if push is used
+var arr = Intl.getCanonicalLocales(["en-US"]);
+
+assertEqArray(arr, ["en-US"]);
+
+if (typeof reportCompare === 'function')
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/getCanonicalLocales-overridden-species.js b/js/src/tests/non262/Intl/getCanonicalLocales-overridden-species.js
new file mode 100644
index 0000000000..858735b581
--- /dev/null
+++ b/js/src/tests/non262/Intl/getCanonicalLocales-overridden-species.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Tests the getCanonicalLocales function for overriden Array[Symbol.species].
+
+Object.defineProperty(Array, Symbol.species, {
+ value: function() {
+ return new Proxy(["?"], {
+ get(t, pk, r) {
+ return Reflect.get(t, pk, r);
+ },
+ defineProperty(t, pk) {
+ return true;
+ }
+ });
+ }
+});
+
+var arr = Intl.getCanonicalLocales("de-x-private");
+
+assertEqArray(arr, ["de-x-private"]);
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/getCanonicalLocales-weird-cases.js b/js/src/tests/non262/Intl/getCanonicalLocales-weird-cases.js
new file mode 100644
index 0000000000..d67fda355f
--- /dev/null
+++ b/js/src/tests/non262/Intl/getCanonicalLocales-weird-cases.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Locale processing is supposed to internally remove any Unicode extension
+// sequences in the locale. Test that various weird testcases invoking
+// algorithmic edge cases don't assert or throw exceptions.
+
+var weirdCases =
+ [
+ "en-x-u-foo",
+ "en-a-bar-x-u-foo",
+ "en-x-u-foo-a-bar",
+ "en-a-bar-u-baz-x-u-foo",
+ ];
+
+for (let weird of weirdCases)
+ assertEqArray(Intl.getCanonicalLocales(weird), [weird]);
+
+assertThrowsInstanceOf(() => Intl.getCanonicalLocales("x-u-foo"), RangeError);
+
+if (typeof reportCompare === 'function')
+ reportCompare(0, 0);
+
diff --git a/js/src/tests/non262/Intl/getCanonicalLocales-with-duplicates.js b/js/src/tests/non262/Intl/getCanonicalLocales-with-duplicates.js
new file mode 100644
index 0000000000..c029569482
--- /dev/null
+++ b/js/src/tests/non262/Intl/getCanonicalLocales-with-duplicates.js
@@ -0,0 +1,15 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tests the getCanonicalLocales function for duplicate locales scenario.
+
+assertEqArray(Intl.getCanonicalLocales(['ab-cd', 'ff', 'de-rt', 'ab-Cd']),
+ ['ab-CD', 'ff', 'de-RT']);
+
+var locales = Intl.getCanonicalLocales(["en-US", "en-US"]);
+assertEqArray(locales, ['en-US']);
+
+if (typeof reportCompare === 'function')
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/getCanonicalLocales.js b/js/src/tests/non262/Intl/getCanonicalLocales.js
new file mode 100644
index 0000000000..66e9b87c5c
--- /dev/null
+++ b/js/src/tests/non262/Intl/getCanonicalLocales.js
@@ -0,0 +1,37 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tests the getCanonicalLocales function with a diverse set of arguments.
+
+let gCL = Intl.getCanonicalLocales;
+
+assertEq(Intl.getCanonicalLocales.length, 1);
+
+assertEqArray(gCL(), []);
+
+assertEqArray(gCL('ab-cd'), ['ab-CD']);
+
+assertEqArray(gCL(['ab-cd']), ['ab-CD']);
+
+assertEqArray(gCL(['ab-cd', 'FF']), ['ab-CD', 'ff']);
+
+assertEqArray(gCL({'a': 0}), []);
+
+assertEqArray(gCL({}), []);
+
+assertEqArray(gCL(['ar-ma-u-ca-islamicc']), ['ar-MA-u-ca-islamic-civil']);
+
+assertEqArray(gCL(['th-th-u-nu-thai']), ['th-TH-u-nu-thai']);
+
+assertEqArray(gCL(NaN), []);
+
+assertEqArray(gCL(1), []);
+
+Number.prototype[0] = "en-US";
+Number.prototype.length = 1;
+assertEqArray(gCL(NaN), ["en-US"]);
+
+if (typeof reportCompare === 'function')
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/getDisplayNames.js b/js/src/tests/non262/Intl/getDisplayNames.js
new file mode 100644
index 0000000000..853effb630
--- /dev/null
+++ b/js/src/tests/non262/Intl/getDisplayNames.js
@@ -0,0 +1,246 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl')||!this.hasOwnProperty('addIntlExtras'))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tests the getCalendarInfo function with a diverse set of arguments.
+
+/*
+ * Test if getDisplayNames return value matches expected values.
+ */
+function checkDisplayNames(names, expected)
+{
+ assertEq(Object.getPrototypeOf(names), Object.prototype);
+
+ assertEq(names.locale, expected.locale);
+ assertEq(names.style, expected.style);
+
+ const nameValues = names.values;
+ const expectedValues = expected.values;
+
+ const nameValuesKeys = Object.getOwnPropertyNames(nameValues).sort();
+ const expectedValuesKeys = Object.getOwnPropertyNames(expectedValues).sort();
+
+ assertEqArray(nameValuesKeys, expectedValuesKeys);
+
+ for (let key of expectedValuesKeys)
+ assertEq(nameValues[key], expectedValues[key]);
+}
+
+addIntlExtras(Intl);
+
+let gDN = Intl.getDisplayNames;
+
+assertEq(gDN.length, 2);
+
+checkDisplayNames(gDN('en-US', {
+}), {
+ locale: 'en-US',
+ style: 'long',
+ values: {}
+});
+
+checkDisplayNames(gDN('en-US', {
+ keys: [
+ 'dates/gregorian/weekdays/wednesday'
+ ],
+ style: 'narrow'
+}), {
+ locale: 'en-US',
+ style: 'narrow',
+ values: {
+ 'dates/gregorian/weekdays/wednesday': 'W'
+ }
+});
+
+checkDisplayNames(gDN('en-US', {
+ keys: [
+ 'dates/fields/year',
+ 'dates/fields/month',
+ 'dates/fields/week',
+ 'dates/fields/day',
+ 'dates/gregorian/months/january',
+ 'dates/gregorian/months/february',
+ 'dates/gregorian/months/march',
+ 'dates/gregorian/weekdays/tuesday'
+ ]
+}), {
+ locale: 'en-US',
+ style: 'long',
+ values: {
+ 'dates/fields/year': 'year',
+ 'dates/fields/month': 'month',
+ 'dates/fields/week': 'week',
+ 'dates/fields/day': 'day',
+ 'dates/gregorian/months/january': 'January',
+ 'dates/gregorian/months/february': 'February',
+ 'dates/gregorian/months/march': 'March',
+ 'dates/gregorian/weekdays/tuesday': 'Tuesday',
+ }
+});
+
+checkDisplayNames(gDN('fr', {
+ keys: [
+ 'dates/fields/year',
+ 'dates/fields/day',
+ 'dates/gregorian/months/october',
+ 'dates/gregorian/weekdays/saturday',
+ 'dates/gregorian/dayperiods/pm'
+ ]
+}), {
+ locale: 'fr',
+ style: 'long',
+ values: {
+ 'dates/fields/year': 'année',
+ 'dates/fields/day': 'jour',
+ 'dates/gregorian/months/october': 'octobre',
+ 'dates/gregorian/weekdays/saturday': 'samedi',
+ 'dates/gregorian/dayperiods/pm': 'PM'
+ }
+});
+
+checkDisplayNames(gDN('it', {
+ style: 'short',
+ keys: [
+ 'dates/gregorian/weekdays/thursday',
+ 'dates/gregorian/months/august',
+ 'dates/gregorian/dayperiods/am',
+ 'dates/fields/month',
+ ]
+}), {
+ locale: 'it',
+ style: 'short',
+ values: {
+ 'dates/gregorian/weekdays/thursday': 'gio',
+ 'dates/gregorian/months/august': 'ago',
+ 'dates/gregorian/dayperiods/am': 'AM',
+ 'dates/fields/month': 'mese'
+ }
+});
+
+checkDisplayNames(gDN('ar', {
+ style: 'long',
+ keys: [
+ 'dates/gregorian/weekdays/thursday',
+ 'dates/gregorian/months/august',
+ 'dates/gregorian/dayperiods/am',
+ 'dates/fields/month',
+ ]
+}), {
+ locale: 'ar',
+ style: 'long',
+ values: {
+ 'dates/gregorian/weekdays/thursday': 'الخميس',
+ 'dates/gregorian/months/august': 'أغسطس',
+ 'dates/gregorian/dayperiods/am': 'ص',
+ 'dates/fields/month': 'الشهر'
+ }
+});
+
+/* Invalid input */
+
+assertThrowsInstanceOf(() => {
+ gDN('en-US', {
+ style: '',
+ keys: [
+ 'dates/gregorian/weekdays/thursday',
+ ]
+ });
+}, RangeError);
+
+assertThrowsInstanceOf(() => {
+ gDN('en-US', {
+ style: 'bogus',
+ keys: [
+ 'dates/gregorian/weekdays/thursday',
+ ]
+ });
+}, RangeError);
+
+assertThrowsInstanceOf(() => {
+ gDN('foo-X', {
+ keys: [
+ 'dates/gregorian/weekdays/thursday',
+ ]
+ });
+}, RangeError);
+
+const typeErrorKeys = [
+ null,
+ 'string',
+ Symbol.iterator,
+ 15,
+ 1,
+ 3.7,
+ NaN,
+ Infinity
+];
+
+for (let keys of typeErrorKeys) {
+ assertThrowsInstanceOf(() => {
+ gDN('en-US', {
+ keys
+ });
+ }, TypeError);
+}
+
+const rangeErrorKeys = [
+ [''],
+ ['foo'],
+ ['dates/foo'],
+ ['/dates/foo'],
+ ['dates/foo/foo'],
+ ['dates/fields'],
+ ['dates/fields/'],
+ ['dates/fields/foo'],
+ ['dates/fields/foo/month'],
+ ['/dates/foo/faa/bar/baz'],
+ ['dates///bar/baz'],
+ ['dates/gregorian'],
+ ['dates/gregorian/'],
+ ['dates/gregorian/foo'],
+ ['dates/gregorian/months'],
+ ['dates/gregorian/months/foo'],
+ ['dates/gregorian/weekdays'],
+ ['dates/gregorian/weekdays/foo'],
+ ['dates/gregorian/dayperiods'],
+ ['dates/gregorian/dayperiods/foo'],
+ ['dates/gregorian/months/الشهر'],
+ [3],
+ [null],
+ ['d', 'a', 't', 'e', 's'],
+ ['datesEXTRA'],
+ ['dates/fieldsEXTRA'],
+ ['dates/gregorianEXTRA'],
+ ['dates/gregorian/monthsEXTRA'],
+ ['dates/gregorian/weekdaysEXTRA'],
+ ['dates/fields/dayperiods/amEXTRA'],
+ ['dates/gregori\u1161n/months/january'],
+ ["dates/fields/year/"],
+ ["dates/fields/year\0"],
+ ["dates/fields/month/"],
+ ["dates/fields/month\0"],
+ ["dates/fields/week/"],
+ ["dates/fields/week\0"],
+ ["dates/fields/day/"],
+ ["dates/fields/day\0"],
+ ["dates/gregorian/months/january/"],
+ ["dates/gregorian/months/january\0"],
+ ["dates/gregorian/weekdays/saturday/"],
+ ["dates/gregorian/weekdays/saturday\0"],
+ ["dates/gregorian/dayperiods/am/"],
+ ["dates/gregorian/dayperiods/am\0"],
+ ["dates/fields/months/january/"],
+ ["dates/fields/months/january\0"],
+];
+
+for (let keys of rangeErrorKeys) {
+ assertThrowsInstanceOf(() => {
+ gDN('en-US', {
+ keys
+ });
+ }, RangeError);
+}
+
+if (typeof reportCompare === 'function')
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/getLocaleInfo.js b/js/src/tests/non262/Intl/getLocaleInfo.js
new file mode 100644
index 0000000000..22d4f53799
--- /dev/null
+++ b/js/src/tests/non262/Intl/getLocaleInfo.js
@@ -0,0 +1,38 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||!this.hasOwnProperty("addIntlExtras"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tests the getCalendarInfo function with a diverse set of arguments.
+
+function checkLocaleInfo(info, expected)
+{
+ assertEq(Object.getPrototypeOf(info), Object.prototype);
+
+ assertEq(info.direction, expected.direction);
+ assertEq(info.locale, expected.locale);
+}
+
+addIntlExtras(Intl);
+
+let gLI = Intl.getLocaleInfo;
+
+assertEq(gLI.length, 1);
+
+checkLocaleInfo(gLI('en-US'), {
+ direction: "ltr",
+ locale: "en-US"
+});
+
+checkLocaleInfo(gLI('fr'), {
+ direction: "ltr",
+ locale: "fr"
+});
+
+checkLocaleInfo(gLI('ar'), {
+ direction: "rtl",
+ locale: "ar"
+});
+
+if (typeof reportCompare === 'function')
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/resolved-locale-sorted-unicode-extension-keys.js b/js/src/tests/non262/Intl/resolved-locale-sorted-unicode-extension-keys.js
new file mode 100644
index 0000000000..400f7f0f63
--- /dev/null
+++ b/js/src/tests/non262/Intl/resolved-locale-sorted-unicode-extension-keys.js
@@ -0,0 +1,109 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+function IsIntlService(c) {
+ return typeof c === "function" &&
+ c.hasOwnProperty("prototype") &&
+ c.prototype.hasOwnProperty("resolvedOptions");
+}
+
+const IntlServices = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsIntlService);
+
+// Examples for all possible Unicode extension keys (with the exception of deprecated "vt").
+const unicodeExtensions = [
+ // calendar
+ "ca-gregory",
+ "fw-mon",
+ "hc-h23",
+
+ // collation
+ "co-phonebk",
+ "ka-noignore",
+ "kb-false",
+ "kc-false",
+ "kf-false",
+ "kh-false",
+ "kk-false",
+ "kn-false",
+ "kr-space",
+ "ks-level1",
+ "kv-space",
+
+ // currency
+ "cf-standard",
+ "cu-eur",
+
+ // measure
+ "ms-metric",
+
+ // number
+ "nu-latn",
+
+ // segmentation
+ "lb-strict",
+ "lw-normal",
+ "ss-none",
+
+ // timezone
+ "tz-atvie",
+
+ // variant
+ "em-default",
+ "rg-atzzzz",
+ "sd-atat1",
+ "va-posix",
+];
+
+function reverse(a, b) {
+ if (a < b) {
+ return 1;
+ }
+ if (a > b) {
+ return -1;
+ }
+ return 0;
+}
+
+function findUnicodeExtensionKeys(locale) {
+ // Find the Unicode extension key subtag.
+ var extension = locale.match(/.*-u-(.*)/);
+ if (extension === null) {
+ return [];
+ }
+
+ // Replace all types in the Unicode extension keywords.
+ return extension[1].replace(/-\w{3,}/g, "").split("-");
+}
+
+// Test all Intl service constructors and ensure the Unicode extension keys in
+// the resolved locale are sorted alphabetically.
+
+for (let IntlService of IntlServices) {
+ let options = undefined;
+ if (IntlService === Intl.DisplayNames) {
+ // Intl.DisplayNames requires the "type" option to be set.
+ options = {type: "language"};
+ }
+
+ // sort() modifies the input array, so create a copy.
+ let ext = unicodeExtensions.slice(0);
+
+ let locale, keys;
+
+ // Input keys unsorted.
+ locale = new IntlService(`de-u-${ext.join("-")}`, options).resolvedOptions().locale;
+ keys = findUnicodeExtensionKeys(locale);
+ assertEqArray(keys, keys.slice(0).sort());
+
+ // Input keys sorted alphabetically.
+ locale = new IntlService(`de-u-${ext.sort().join("-")}`, options).resolvedOptions().locale;
+ keys = findUnicodeExtensionKeys(locale);
+ assertEqArray(keys, keys.slice(0).sort());
+
+ // Input keys sorted alphabetically in reverse order.
+ locale = new IntlService(`de-u-${ext.sort(reverse).join("-")}`, options).resolvedOptions().locale;
+ keys = findUnicodeExtensionKeys(locale);
+ assertEqArray(keys, keys.slice(0).sort());
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/shell.js b/js/src/tests/non262/Intl/shell.js
new file mode 100644
index 0000000000..25755ee827
--- /dev/null
+++ b/js/src/tests/non262/Intl/shell.js
@@ -0,0 +1,328 @@
+// Generated by make_intl_data.py. DO NOT EDIT.
+
+// source: CLDR file common/bcp47/number.xml; version CLDR 37.
+// https://github.com/unicode-org/cldr/blob/master/common/bcp47/number.xml
+// https://github.com/unicode-org/cldr/blob/master/common/supplemental/numberingSystems.xml
+const numberingSystems = {
+ "adlm": {
+ "algorithmic": false,
+ "digits": "𞥐𞥑𞥒𞥓𞥔𞥕𞥖𞥗𞥘𞥙"
+ },
+ "ahom": {
+ "algorithmic": false,
+ "digits": "𑜰𑜱𑜲𑜳𑜴𑜵𑜶𑜷𑜸𑜹"
+ },
+ "arab": {
+ "algorithmic": false,
+ "digits": "٠١٢٣٤٥٦٧٨٩"
+ },
+ "arabext": {
+ "algorithmic": false,
+ "digits": "۰۱۲۳۴۵۶۷۸۹"
+ },
+ "armn": {
+ "algorithmic": true
+ },
+ "armnlow": {
+ "algorithmic": true
+ },
+ "bali": {
+ "algorithmic": false,
+ "digits": "᭐᭑᭒᭓᭔᭕᭖᭗᭘᭙"
+ },
+ "beng": {
+ "algorithmic": false,
+ "digits": "০১২৩৪৫৬৭৮৯"
+ },
+ "bhks": {
+ "algorithmic": false,
+ "digits": "𑱐𑱑𑱒𑱓𑱔𑱕𑱖𑱗𑱘𑱙"
+ },
+ "brah": {
+ "algorithmic": false,
+ "digits": "𑁦𑁧𑁨𑁩𑁪𑁫𑁬𑁭𑁮𑁯"
+ },
+ "cakm": {
+ "algorithmic": false,
+ "digits": "𑄶𑄷𑄸𑄹𑄺𑄻𑄼𑄽𑄾𑄿"
+ },
+ "cham": {
+ "algorithmic": false,
+ "digits": "꩐꩑꩒꩓꩔꩕꩖꩗꩘꩙"
+ },
+ "cyrl": {
+ "algorithmic": true
+ },
+ "deva": {
+ "algorithmic": false,
+ "digits": "०१२३४५६७८९"
+ },
+ "diak": {
+ "algorithmic": false,
+ "digits": "𑥐𑥑𑥒𑥓𑥔𑥕𑥖𑥗𑥘𑥙"
+ },
+ "ethi": {
+ "algorithmic": true
+ },
+ "fullwide": {
+ "algorithmic": false,
+ "digits": "0123456789"
+ },
+ "geor": {
+ "algorithmic": true
+ },
+ "gong": {
+ "algorithmic": false,
+ "digits": "𑶠𑶡𑶢𑶣𑶤𑶥𑶦𑶧𑶨𑶩"
+ },
+ "gonm": {
+ "algorithmic": false,
+ "digits": "𑵐𑵑𑵒𑵓𑵔𑵕𑵖𑵗𑵘𑵙"
+ },
+ "grek": {
+ "algorithmic": true
+ },
+ "greklow": {
+ "algorithmic": true
+ },
+ "gujr": {
+ "algorithmic": false,
+ "digits": "૦૧૨૩૪૫૬૭૮૯"
+ },
+ "guru": {
+ "algorithmic": false,
+ "digits": "੦੧੨੩੪੫੬੭੮੯"
+ },
+ "hanidays": {
+ "algorithmic": true
+ },
+ "hanidec": {
+ "algorithmic": false,
+ "digits": "〇一二三四五六七八九"
+ },
+ "hans": {
+ "algorithmic": true
+ },
+ "hansfin": {
+ "algorithmic": true
+ },
+ "hant": {
+ "algorithmic": true
+ },
+ "hantfin": {
+ "algorithmic": true
+ },
+ "hebr": {
+ "algorithmic": true
+ },
+ "hmng": {
+ "algorithmic": false,
+ "digits": "𖭐𖭑𖭒𖭓𖭔𖭕𖭖𖭗𖭘𖭙"
+ },
+ "hmnp": {
+ "algorithmic": false,
+ "digits": "𞅀𞅁𞅂𞅃𞅄𞅅𞅆𞅇𞅈𞅉"
+ },
+ "java": {
+ "algorithmic": false,
+ "digits": "꧐꧑꧒꧓꧔꧕꧖꧗꧘꧙"
+ },
+ "jpan": {
+ "algorithmic": true
+ },
+ "jpanfin": {
+ "algorithmic": true
+ },
+ "jpanyear": {
+ "algorithmic": true
+ },
+ "kali": {
+ "algorithmic": false,
+ "digits": "꤀꤁꤂꤃꤄꤅꤆꤇꤈꤉"
+ },
+ "khmr": {
+ "algorithmic": false,
+ "digits": "០១២៣៤៥៦៧៨៩"
+ },
+ "knda": {
+ "algorithmic": false,
+ "digits": "೦೧೨೩೪೫೬೭೮೯"
+ },
+ "lana": {
+ "algorithmic": false,
+ "digits": "᪀᪁᪂᪃᪄᪅᪆᪇᪈᪉"
+ },
+ "lanatham": {
+ "algorithmic": false,
+ "digits": "᪐᪑᪒᪓᪔᪕᪖᪗᪘᪙"
+ },
+ "laoo": {
+ "algorithmic": false,
+ "digits": "໐໑໒໓໔໕໖໗໘໙"
+ },
+ "latn": {
+ "algorithmic": false,
+ "digits": "0123456789"
+ },
+ "lepc": {
+ "algorithmic": false,
+ "digits": "᱀᱁᱂᱃᱄᱅᱆᱇᱈᱉"
+ },
+ "limb": {
+ "algorithmic": false,
+ "digits": "᥆᥇᥈᥉᥊᥋᥌᥍᥎᥏"
+ },
+ "mathbold": {
+ "algorithmic": false,
+ "digits": "𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗"
+ },
+ "mathdbl": {
+ "algorithmic": false,
+ "digits": "𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡"
+ },
+ "mathmono": {
+ "algorithmic": false,
+ "digits": "𝟶𝟷𝟸𝟹𝟺𝟻𝟼𝟽𝟾𝟿"
+ },
+ "mathsanb": {
+ "algorithmic": false,
+ "digits": "𝟬𝟭𝟮𝟯𝟰𝟱𝟲𝟳𝟴𝟵"
+ },
+ "mathsans": {
+ "algorithmic": false,
+ "digits": "𝟢𝟣𝟤𝟥𝟦𝟧𝟨𝟩𝟪𝟫"
+ },
+ "mlym": {
+ "algorithmic": false,
+ "digits": "൦൧൨൩൪൫൬൭൮൯"
+ },
+ "modi": {
+ "algorithmic": false,
+ "digits": "𑙐𑙑𑙒𑙓𑙔𑙕𑙖𑙗𑙘𑙙"
+ },
+ "mong": {
+ "algorithmic": false,
+ "digits": "᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙"
+ },
+ "mroo": {
+ "algorithmic": false,
+ "digits": "𖩠𖩡𖩢𖩣𖩤𖩥𖩦𖩧𖩨𖩩"
+ },
+ "mtei": {
+ "algorithmic": false,
+ "digits": "꯰꯱꯲꯳꯴꯵꯶꯷꯸꯹"
+ },
+ "mymr": {
+ "algorithmic": false,
+ "digits": "၀၁၂၃၄၅၆၇၈၉"
+ },
+ "mymrshan": {
+ "algorithmic": false,
+ "digits": "႐႑႒႓႔႕႖႗႘႙"
+ },
+ "mymrtlng": {
+ "algorithmic": false,
+ "digits": "꧰꧱꧲꧳꧴꧵꧶꧷꧸꧹"
+ },
+ "newa": {
+ "algorithmic": false,
+ "digits": "𑑐𑑑𑑒𑑓𑑔𑑕𑑖𑑗𑑘𑑙"
+ },
+ "nkoo": {
+ "algorithmic": false,
+ "digits": "߀߁߂߃߄߅߆߇߈߉"
+ },
+ "olck": {
+ "algorithmic": false,
+ "digits": "᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙"
+ },
+ "orya": {
+ "algorithmic": false,
+ "digits": "୦୧୨୩୪୫୬୭୮୯"
+ },
+ "osma": {
+ "algorithmic": false,
+ "digits": "𐒠𐒡𐒢𐒣𐒤𐒥𐒦𐒧𐒨𐒩"
+ },
+ "rohg": {
+ "algorithmic": false,
+ "digits": "𐴰𐴱𐴲𐴳𐴴𐴵𐴶𐴷𐴸𐴹"
+ },
+ "roman": {
+ "algorithmic": true
+ },
+ "romanlow": {
+ "algorithmic": true
+ },
+ "saur": {
+ "algorithmic": false,
+ "digits": "꣐꣑꣒꣓꣔꣕꣖꣗꣘꣙"
+ },
+ "segment": {
+ "algorithmic": false,
+ "digits": "🯰🯱🯲🯳🯴🯵🯶🯷🯸🯹"
+ },
+ "shrd": {
+ "algorithmic": false,
+ "digits": "𑇐𑇑𑇒𑇓𑇔𑇕𑇖𑇗𑇘𑇙"
+ },
+ "sind": {
+ "algorithmic": false,
+ "digits": "𑋰𑋱𑋲𑋳𑋴𑋵𑋶𑋷𑋸𑋹"
+ },
+ "sinh": {
+ "algorithmic": false,
+ "digits": "෦෧෨෩෪෫෬෭෮෯"
+ },
+ "sora": {
+ "algorithmic": false,
+ "digits": "𑃰𑃱𑃲𑃳𑃴𑃵𑃶𑃷𑃸𑃹"
+ },
+ "sund": {
+ "algorithmic": false,
+ "digits": "᮰᮱᮲᮳᮴᮵᮶᮷᮸᮹"
+ },
+ "takr": {
+ "algorithmic": false,
+ "digits": "𑛀𑛁𑛂𑛃𑛄𑛅𑛆𑛇𑛈𑛉"
+ },
+ "talu": {
+ "algorithmic": false,
+ "digits": "᧐᧑᧒᧓᧔᧕᧖᧗᧘᧙"
+ },
+ "taml": {
+ "algorithmic": true
+ },
+ "tamldec": {
+ "algorithmic": false,
+ "digits": "௦௧௨௩௪௫௬௭௮௯"
+ },
+ "telu": {
+ "algorithmic": false,
+ "digits": "౦౧౨౩౪౫౬౭౮౯"
+ },
+ "thai": {
+ "algorithmic": false,
+ "digits": "๐๑๒๓๔๕๖๗๘๙"
+ },
+ "tibt": {
+ "algorithmic": false,
+ "digits": "༠༡༢༣༤༥༦༧༨༩"
+ },
+ "tirh": {
+ "algorithmic": false,
+ "digits": "𑓐𑓑𑓒𑓓𑓔𑓕𑓖𑓗𑓘𑓙"
+ },
+ "vaii": {
+ "algorithmic": false,
+ "digits": "꘠꘡꘢꘣꘤꘥꘦꘧꘨꘩"
+ },
+ "wara": {
+ "algorithmic": false,
+ "digits": "𑣠𑣡𑣢𑣣𑣤𑣥𑣦𑣧𑣨𑣩"
+ },
+ "wcho": {
+ "algorithmic": false,
+ "digits": "𞋰𞋱𞋲𞋳𞋴𞋵𞋶𞋷𞋸𞋹"
+ }
+};
diff --git a/js/src/tests/non262/Intl/tolower-ascii-equivalent.js b/js/src/tests/non262/Intl/tolower-ascii-equivalent.js
new file mode 100644
index 0000000000..ac3cbfb7d3
--- /dev/null
+++ b/js/src/tests/non262/Intl/tolower-ascii-equivalent.js
@@ -0,0 +1,47 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Language tags are processed case-insensitive, but unconditionally calling
+// the built-in String.prototype.toLowerCase() or toUpperCase() function
+// before parsing a language tag can map non-ASCII characters into the ASCII
+// range.
+//
+// Validate the Unicode BCP 47 locale identifier parser handles this case
+// (pun intended) correctly by passing language tags which contain
+// U+212A (KELVIN SIGN) and U+0131 (LATIN SMALL LETTER DOTLESS I) to
+// Intl.getCanonicalLocales().
+
+// The lower-case form of "i-ha\u212A" is "i-hak".
+assertEq("i-hak", "i-ha\u212A".toLowerCase());
+
+// The upper-case form of "\u0131-hak" is "I-HAK".
+assertEq("I-HAK", "\u0131-hak".toUpperCase());
+
+// "i-hak" is not a valid Unicode BCP 47 locale identifier.
+assertThrowsInstanceOf(() => Intl.getCanonicalLocales("i-hak"), RangeError);
+
+// And neither is "i-ha\u212A".
+assertThrowsInstanceOf(() => Intl.getCanonicalLocales("i-ha\u212A"), RangeError);
+
+// And also "\u0131-hak" isn't valid.
+assertThrowsInstanceOf(() => Intl.getCanonicalLocales("\u0131-hak"), RangeError);
+
+// The lower-case form of "zh-ha\u212A\u212Aa" is "zh-hakka".
+assertEq("zh-hakka", "zh-ha\u212A\u212Aa".toLowerCase());
+
+// "zh-hakka" is a valid Unicode BCP 47 locale identifier.
+assertEqArray(Intl.getCanonicalLocales("zh-hakka"), ["hak"]);
+
+// But "zh-ha\u212A\u212Aa" is not a valid locale identifier.
+assertThrowsInstanceOf(() => Intl.getCanonicalLocales("zh-ha\u212A\u212Aa"), RangeError);
+
+// The lower-case form of "zh-x\u0131ang" is "ZH-XIANG".
+assertEq("ZH-XIANG", "zh-x\u0131ang".toUpperCase());
+
+// "zh-xiang" is a valid Unicode BCP 47 locale identifier.
+assertEqArray(Intl.getCanonicalLocales("zh-xiang"), ["hsn"]);
+
+// But "zh-x\u0131ang" is not a valid locale identifier.
+assertThrowsInstanceOf(() => Intl.getCanonicalLocales("zh-x\u0131ang"), RangeError);
+
+if (typeof reportCompare === 'function')
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-extlangs.js b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-extlangs.js
new file mode 100644
index 0000000000..7e9aae5519
--- /dev/null
+++ b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-extlangs.js
@@ -0,0 +1,21 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Unicode BCP 47 locale identifiers don't support extlang subtags.
+const invalid = [
+ // Two letter language code followed by extlang subtags.
+ "en-abc",
+ "en-abc-def",
+ "en-abc-def-ghi",
+
+ // Three letter language code followed by extlang subtags.
+ "und-abc",
+ "und-abc-def",
+ "und-abc-def-ghi",
+];
+
+for (let locale of invalid) {
+ assertThrowsInstanceOf(() => Intl.getCanonicalLocales(locale), RangeError);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-grandfathered.js b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-grandfathered.js
new file mode 100644
index 0000000000..028127f110
--- /dev/null
+++ b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-grandfathered.js
@@ -0,0 +1,52 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Unicode BCP 47 locale identifiers don't support irregular grandfathered tags.
+var irregularGrandfathered = [
+ "en-gb-oed",
+ "i-ami",
+ "i-bnn",
+ "i-default",
+ "i-enochian",
+ "i-hak",
+ "i-klingon",
+ "i-lux",
+ "i-mingo",
+ "i-navajo",
+ "i-pwn",
+ "i-tao",
+ "i-tay",
+ "i-tsu",
+ "sgn-be-fr",
+ "sgn-be-nl",
+ "sgn-ch-de",
+];
+
+// Unicode BCP 47 locale identifiers don't support regular grandfathered tags
+// which contain an extlang-like subtag.
+var regularGrandfatheredWithExtlangLike = [
+ "no-bok",
+ "no-nyn",
+ "zh-min",
+ "zh-min-nan",
+];
+
+// Unicode BCP 47 locale identifiers do support regular grandfathered tags
+// which contain a variant-like subtag.
+var regularGrandfatheredWithVariantLike = {
+ "art-lojban": "jbo",
+ "cel-gaulish": "xtg-x-cel-gaulish",
+ "zh-guoyu": "zh",
+ "zh-hakka": "hak",
+ "zh-xiang": "hsn",
+};
+
+for (let locale of [...irregularGrandfathered, ...regularGrandfatheredWithExtlangLike]) {
+ assertThrowsInstanceOf(() => Intl.getCanonicalLocales(locale), RangeError);
+}
+
+for (let [locale, canonical] of Object.entries(regularGrandfatheredWithVariantLike)) {
+ assertEq(Intl.getCanonicalLocales(locale)[0], canonical);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-language-mappings.js b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-language-mappings.js
new file mode 100644
index 0000000000..a37cd603a0
--- /dev/null
+++ b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-language-mappings.js
@@ -0,0 +1,39 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// For the most part the mappings from IANA are a subset of the CLDR mappings.
+// So there are mappings which are consistent across both databases.
+assertEq(Intl.getCanonicalLocales("iw")[0], "he");
+
+// But some languages are mapped differently.
+//
+// From the IANA language data registry:
+// Type: language
+// Subtag: drh
+// Description: Darkhat
+// Added: 2009-07-29
+// Deprecated: 2010-03-11
+// Preferred-Value: khk
+//
+// From CLDR:
+// <languageAlias type="drh" replacement="mn" reason="deprecated"/>
+//
+// because CLDR also maps macro-languages:
+// <languageAlias type="khk" replacement="mn" reason="macrolanguage"/>
+assertEq(Intl.getCanonicalLocales("drh")[0], "mn");
+
+// CLDR maps macro-languages:
+// <languageAlias type="cmn" replacement="zh" reason="macrolanguage"/>
+assertEq(Intl.getCanonicalLocales("cmn")[0], "zh");
+
+// CLDR also contains mappings from ISO-639-2 (B/T) to 639-1 codes:
+// <languageAlias type="dut" replacement="nl" reason="bibliographic"/>
+// <languageAlias type="nld" replacement="nl" reason="overlong"/>
+assertEq(Intl.getCanonicalLocales("dut")[0], "nl");
+assertEq(Intl.getCanonicalLocales("nld")[0], "nl");
+
+// CLDR has additional mappings for legacy language codes.
+// <languageAlias type="no" replacement="nb" reason="legacy"/>
+assertEq(Intl.getCanonicalLocales("no")[0], "nb");
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-languages-mappings-complex.js b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-languages-mappings-complex.js
new file mode 100644
index 0000000000..320e1b02c0
--- /dev/null
+++ b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-languages-mappings-complex.js
@@ -0,0 +1,21 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// CLDR contains language mappings where in addition to the language subtag also
+// the script or region subtag is modified, unless they're already present.
+
+// <languageAlias type="sh" replacement="sr_Latn" reason="legacy"/>
+assertEq(Intl.getCanonicalLocales("sh")[0], "sr-Latn");
+assertEq(Intl.getCanonicalLocales("sh-RS")[0], "sr-Latn-RS");
+assertEq(Intl.getCanonicalLocales("sh-Cyrl")[0], "sr-Cyrl");
+
+// <languageAlias type="cnr" replacement="sr_ME" reason="legacy"/>
+assertEq(Intl.getCanonicalLocales("cnr")[0], "sr-ME");
+assertEq(Intl.getCanonicalLocales("cnr-Latn")[0], "sr-Latn-ME");
+assertEq(Intl.getCanonicalLocales("cnr-RS")[0], "sr-RS");
+
+// Aliases where more than just a language subtag are present are ignored.
+// <languageAlias type="sr_RS" replacement="sr_Cyrl_RS" reason="legacy"/>
+assertEq(Intl.getCanonicalLocales("sr-RS")[0], "sr-RS");
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-region-mappings-complex.js b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-region-mappings-complex.js
new file mode 100644
index 0000000000..22dbd17a1d
--- /dev/null
+++ b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-region-mappings-complex.js
@@ -0,0 +1,57 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// CLDR contains region mappings where the replacement region depends on the
+// likely subtags from the language and script subtags.
+//
+// For example, the breakup of the Soviet Union ("SU") means that the region of
+// the Soviet Union ("SU") is replaced by Russia ("RU"), Armenia ("AM"), or
+// many others -- depending on the specified (or merely likely) language and
+// script subtags:
+//
+// <territoryAlias type="SU" replacement="RU AM AZ BY EE GE KZ KG LV LT MD TJ TM UA UZ" reason="deprecated"/>
+//
+// Armenia can be the preferred region when the language is "hy" (Armenian) or
+// the script is "Armn" (Armenian).
+//
+// <likelySubtag from="hy" to="hy_Armn_AM"/>
+// <likelySubtag from="und_Armn" to="hy_Armn_AM"/>
+assertEq(Intl.getCanonicalLocales("ru-SU")[0], "ru-RU");
+assertEq(Intl.getCanonicalLocales("en-SU")[0], "en-RU");
+assertEq(Intl.getCanonicalLocales("und-SU")[0], "und-RU");
+assertEq(Intl.getCanonicalLocales("und-Latn-SU")[0], "und-Latn-RU");
+assertEq(Intl.getCanonicalLocales("hy-SU")[0], "hy-AM");
+assertEq(Intl.getCanonicalLocales("und-Armn-SU")[0], "und-Armn-AM");
+
+// <territoryAlias type="CS" replacement="RS ME" reason="deprecated"/>
+//
+// The following likely-subtags entries contain "RS" and "ME":
+//
+// <likelySubtag from="sr" to="sr_Cyrl_RS"/>
+// <likelySubtag from="sr_ME" to="sr_Latn_ME"/>
+// <likelySubtag from="und_RS" to="sr_Cyrl_RS"/>
+// <likelySubtag from="und_ME" to="sr_Latn_ME"/>
+//
+// In this case there is no language/script combination (without a region
+// subtag) where "ME" is ever chosen, so the replacement is always "RS".
+assertEq(Intl.getCanonicalLocales("sr-CS")[0], "sr-RS");
+assertEq(Intl.getCanonicalLocales("sr-Latn-CS")[0], "sr-Latn-RS");
+assertEq(Intl.getCanonicalLocales("sr-Cyrl-CS")[0], "sr-Cyrl-RS");
+
+// The existing region in the source locale identifier is ignored when selecting
+// the likely replacement region. For example take "az-NT", which is Azerbaijani
+// spoken in the Neutral Zone. The replacement region for "NT" is either
+// "SA" (Saudi-Arabia) or "IQ" (Iraq), and there is also a likely subtags entry
+// for "az-IQ". But when only looking at the language subtag in "az-NT", "az" is
+// always resolved to "az-Latn-AZ", and because "AZ" is not in the list ["SA",
+// "IQ"], the final replacement region is the default for "NT", namely "SA".
+// That means "az-NT" will be canonicalised to "az-SA" and not "az-IQ", even
+// though the latter may be a more sensible candidate based on the actual usage
+// of the target locales.
+//
+// <territoryAlias type="NT" replacement="SA IQ" reason="deprecated"/>
+// <likelySubtag from="az_IQ" to="az_Arab_IQ"/>
+// <likelySubtag from="az" to="az_Latn_AZ"/>
+assertEq(Intl.getCanonicalLocales("az-NT")[0], "az-SA");
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-region-mappings.js b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-region-mappings.js
new file mode 100644
index 0000000000..affebb33b8
--- /dev/null
+++ b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-region-mappings.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// For the most part the mappings from IANA are a subset of the CLDR mappings.
+// So there are mappings which are consistent across both databases.
+assertEq(Intl.getCanonicalLocales("de-DD")[0], "de-DE");
+
+// CLDR contains additional mappings:
+// <territoryAlias type="QU" replacement="EU" reason="deprecated"/>
+// <territoryAlias type="UK" replacement="GB" reason="deprecated"/>
+assertEq(Intl.getCanonicalLocales("und-QU")[0], "und-EU");
+assertEq(Intl.getCanonicalLocales("en-UK")[0], "en-GB");
+
+// CLDR additional maps ISO 3166-1 numeric to ISO 3166-1 alpha-2 codes:
+// <territoryAlias type="280" replacement="DE" reason="deprecated"/>
+// <territoryAlias type="278" replacement="DE" reason="overlong"/>
+// <territoryAlias type="276" replacement="DE" reason="overlong"/>
+assertEq(Intl.getCanonicalLocales("de-280")[0], "de-DE");
+assertEq(Intl.getCanonicalLocales("de-278")[0], "de-DE");
+assertEq(Intl.getCanonicalLocales("de-276")[0], "de-DE");
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-sign-languages.js b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-sign-languages.js
new file mode 100644
index 0000000000..9ed09a23f1
--- /dev/null
+++ b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-sign-languages.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// The IANA language subtag registry contains replacements for sign language
+// codes marked as redundant. For example:
+//
+// Type: redundant
+// Tag: sgn-DE
+// Description: German Sign Language
+// Added: 2001-11-11
+// Deprecated: 2009-07-29
+// Preferred-Value: gsg
+//
+// CLDR doesn't contain these mappings. Make sure we follow CLDR instead of IANA.
+
+assertEq(Intl.getCanonicalLocales("sgn-DE")[0], "sgn-DE");
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-transformed-ext.js b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-transformed-ext.js
new file mode 100644
index 0000000000..50ac20e58b
--- /dev/null
+++ b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-transformed-ext.js
@@ -0,0 +1,71 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const invalid = [
+ // empty
+ "en-t",
+ "en-t-a",
+ "en-t-x",
+ "en-t-0",
+
+ // incomplete
+ "en-t-",
+ "en-t-en-",
+ "en-t-0x-",
+
+ // tlang: unicode_language_subtag must be 2-3 or 5-8 characters and mustn't
+ // contain extlang subtags.
+ "en-t-root",
+ "en-t-abcdefghi",
+ "en-t-ar-aao",
+
+ // tlang: unicode_script_subtag must be 4 alphabetical characters, can't
+ // be repeated.
+ "en-t-en-lat0",
+ "en-t-en-latn-latn",
+
+ // tlang: unicode_region_subtag must either be 2 alpha characters or a three
+ // digit code.
+ "en-t-en-0",
+ "en-t-en-00",
+ "en-t-en-0x",
+ "en-t-en-x0",
+ "en-t-en-latn-0",
+ "en-t-en-latn-00",
+ "en-t-en-latn-xyz",
+
+ // tlang: unicode_variant_subtag is either 5-8 alphanum characters or 4
+ // characters starting with a digit.
+ "en-t-en-abcdefghi",
+ "en-t-en-latn-gb-ab",
+ "en-t-en-latn-gb-abc",
+ "en-t-en-latn-gb-abcd",
+ "en-t-en-latn-gb-abcdefghi",
+];
+
+// Canonicalisation also applies for the transformation extension. But also
+// see <https://github.com/tc39/ecma402/issues/330>.
+const valid = [
+ {locale: "en-t-en", canonical: "en-t-en"},
+ {locale: "en-t-en-latn", canonical: "en-t-en-latn"},
+ {locale: "en-t-en-ca", canonical: "en-t-en-ca"},
+ {locale: "en-t-en-latn-ca", canonical: "en-t-en-latn-ca"},
+ {locale: "en-t-en-emodeng", canonical: "en-t-en-emodeng"},
+ {locale: "en-t-en-latn-emodeng", canonical: "en-t-en-latn-emodeng"},
+ {locale: "en-t-en-latn-ca-emodeng", canonical: "en-t-en-latn-ca-emodeng"},
+ {locale: "sl-t-sl-rozaj-biske-1994", canonical: "sl-t-sl-1994-biske-rozaj"},
+ {locale: "DE-T-M0-DIN-K0-QWERTZ", canonical: "de-t-k0-qwertz-m0-din"},
+ {locale: "en-t-m0-true", canonical: "en-t-m0-true"},
+ {locale: "en-t-iw", canonical: "en-t-he"},
+ {locale: "und-Latn-t-und-hani-m0-names", canonical: "und-Latn-t-und-hani-m0-prprname"},
+];
+
+for (let locale of invalid) {
+ assertThrowsInstanceOf(() => Intl.getCanonicalLocales(locale), RangeError);
+}
+
+for (let {locale, canonical} of valid) {
+ assertEq(Intl.getCanonicalLocales(locale)[0], canonical);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-unicode-ext.js b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-unicode-ext.js
new file mode 100644
index 0000000000..a46eba475e
--- /dev/null
+++ b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-unicode-ext.js
@@ -0,0 +1,12 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Unicode locale extension sequences don't allow keys with a digit as their
+// second character.
+assertThrowsInstanceOf(() => Intl.getCanonicalLocales("en-u-c0"), RangeError);
+assertThrowsInstanceOf(() => Intl.getCanonicalLocales("en-u-00"), RangeError);
+
+// The first character is allowed to be a digit.
+assertEq(Intl.getCanonicalLocales("en-u-0c")[0], "en-u-0c");
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-variants-legacy-mappings.js b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-variants-legacy-mappings.js
new file mode 100644
index 0000000000..49e31c6786
--- /dev/null
+++ b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-variants-legacy-mappings.js
@@ -0,0 +1,15 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// ECMA-402 includes mapping of legacy variants, as long as they're also present
+// in <variantAlias> in CLDR's supplementalMetadata.xml
+// <https://www.unicode.org/reports/tr35/#Legacy_Variants>
+
+assertEq(Intl.getCanonicalLocales("sv-AALAND")[0], "sv-AX");
+assertEq(Intl.getCanonicalLocales("no-BOKMAL")[0], "nb-bokmal");
+assertEq(Intl.getCanonicalLocales("no-NYNORSK")[0], "nb-nynorsk");
+assertEq(Intl.getCanonicalLocales("en-POSIX")[0], "en-posix");
+assertEq(Intl.getCanonicalLocales("el-POLYTONI")[0], "el-polyton");
+assertEq(Intl.getCanonicalLocales("aa-SAAHO")[0], "aa-saaho");
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-variants-sorted.js b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-variants-sorted.js
new file mode 100644
index 0000000000..e50436a83c
--- /dev/null
+++ b/js/src/tests/non262/Intl/unicode-bcp47-locale-ids-variants-sorted.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// UTS 35, 3.2.1 Canonical Unicode Locale Identifiers:
+// - Any variants are in alphabetical order.
+
+assertEq(Intl.getCanonicalLocales("en-scouse-fonipa")[0], "en-fonipa-scouse");
+
+// Sorting in alphabetical order may turn a valid BCP 47 language tag into a
+// BCP 47 language tag which is only well-formed, but no longer valid. This
+// means there are potential compatibility issues when converting between
+// Unicode BCP 47 locale identifiers and BCP 47 language tags.
+//
+// Spec: https://tools.ietf.org/html/rfc5646#section-2.2.9
+
+// <https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry>
+//
+// Type: variant
+// Subtag: 1994
+// Description: Standardized Resian orthography
+// Added: 2007-07-28
+// Prefix: sl-rozaj
+// Prefix: sl-rozaj-biske
+// Prefix: sl-rozaj-njiva
+// Prefix: sl-rozaj-osojs
+// Prefix: sl-rozaj-solba
+// Comments: For standardized Resian an orthography was published in 1994.
+
+assertEq(Intl.getCanonicalLocales("sl-rozaj-biske-1994")[0], "sl-1994-biske-rozaj");
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Intl/variant-with-preferred-value.js b/js/src/tests/non262/Intl/variant-with-preferred-value.js
new file mode 100644
index 0000000000..c0c342f0e0
--- /dev/null
+++ b/js/src/tests/non262/Intl/variant-with-preferred-value.js
@@ -0,0 +1,58 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Per UTS 35, computing the canonical form for Unicode BCP 47 locale identifiers
+// includes replacing deprecated variant mappings. The other UTS 35 canonicalisation
+// algorithm ("BCP 47 Language Tag to Unicode BCP 47 Locale Identifier") doesn't
+// support deprecated variant mappings.
+// https://github.com/tc39/ecma402/issues/330
+
+const languageTags = {
+ // The preferred value of "hy-arevela" is "hy" per CLDR.
+ "hy-arevela": "hy",
+ "hy-Armn-arevela": "hy-Armn",
+ "hy-AM-arevela": "hy-AM",
+ "hy-arevela-fonipa": "hy-fonipa",
+ "hy-fonipa-arevela": "hy-fonipa",
+
+ // The preferred value of "hy-arevmda" is "hyw" per CLDR.
+ "hy-arevmda": "hyw",
+ "hy-Armn-arevmda": "hyw-Armn",
+ "hy-AM-arevmda": "hyw-AM",
+ "hy-arevmda-fonipa": "hyw-fonipa",
+ "hy-fonipa-arevmda": "hyw-fonipa",
+
+ // The preferred value of "ja-Latn-hepburn-heploc" is "ja-Latn-alalc97-hepburn" per CLDR.
+ // But: The preferred value of "ja-Latn-hepburn-heploc" is "ja-Latn-alalc97" per IANA!
+ "ja-Latn-hepburn-heploc": "ja-Latn-alalc97-hepburn",
+ "ja-Latn-JP-hepburn-heploc": "ja-Latn-JP-alalc97-hepburn",
+
+ // Variant subtag replacements not present in IANA.
+ "sv-aaland": "sv-AX",
+ "el-polytoni": "el-polyton",
+
+ // Additional cases when more variant subtags are present.
+
+ // 1. The preferred variant is already present.
+ "ja-Latn-alalc97-hepburn-heploc": "ja-Latn-alalc97-hepburn",
+ "ja-Latn-hepburn-alalc97-heploc": "ja-Latn-alalc97-hepburn",
+ "ja-Latn-hepburn-heploc-alalc97": "ja-Latn-alalc97-hepburn",
+
+ // 2. The variant subtags aren't in the expected order per IANA. (CLDR doesn't care
+ // about the order of variant subtags.)
+ "ja-Latn-heploc-hepburn": "ja-Latn-alalc97-hepburn",
+
+ // 3. IANA expects both variant subtags to be present, CLDR only requires "heploc".
+ "ja-Latn-heploc": "ja-Latn-alalc97",
+
+ // 4. Test for cases when the same variant subtag position needs to be checked more
+ // than once when replacing deprecated variant subtags.
+ "ja-Latn-aaland-heploc": "ja-Latn-AX-alalc97",
+ "ja-Latn-heploc-polytoni": "ja-Latn-alalc97-polyton",
+};
+
+for (let [tag, canonical] of Object.entries(languageTags)) {
+ assertEq(Intl.getCanonicalLocales(tag)[0], canonical);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);