diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/tests/non262/Intl | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/tests/non262/Intl')
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); |