summaryrefslogtreecommitdiffstats
path: root/js/src/tests/non262/Intl/DateTimeFormat
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/tests/non262/Intl/DateTimeFormat')
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/browser.js3
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/calendar-aliases.js35
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/calendar-option.js67
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/call.js184
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/construct-newtarget.js81
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/cross-compartment.js70
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/dateTimeStyle.js57
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/day-period-hour-cycle.js102
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/day-period-standalone.js160
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/day-period.js40
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/era.js245
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/extended-time-zone-names.js124
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/field-widths.js225
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/format.js47
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/formatRange-gregorian-proleptic.js28
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/formatRange-hour-cycle.js450
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/formatRange-matches-format-output.js58
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/formatRange-original-skeleton.js18
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/formatToParts.js96
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/format_timeZone-non-meta.js62
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/format_timeZone.js104
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/fractional-second-digits-append-item.js58
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/fractional-second-digits.js43
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/hourCycle.js142
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/implied-script-has-consistent-output.js59
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/islamic.js89
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/japanese-gannen-year.js54
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/mozExtensions.js18
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/numberingSystem-option.js60
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/options-property-accesses.js77
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/related-year.js185
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/shell.js34
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/standalone-month.js11
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/supportedLocalesOf.js371
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/timeZone.js139
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/timeZone_backward_links.js156
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/timeZone_backzone.js153
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/timeZone_backzone_links.js48
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/timeZone_notbackward_links.js31
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/timeZone_version.js17
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/toStringTag.js29
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/tz-environment-variable.js67
-rw-r--r--js/src/tests/non262/Intl/DateTimeFormat/unwrapping.js248
43 files changed, 4345 insertions, 0 deletions
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..752bda9c02
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/call.js
@@ -0,0 +1,184 @@
+// |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 isPrototypeOf = Intl.DateTimeFormat.prototype.isPrototypeOf(thisValue);
+ 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), isPrototypeOf);
+ assertEq(hasInstanceCalled, false);
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), isPrototypeOf ? [intlFallbackSymbol] : []);
+}
+// - Test when InstanceofOperator(thisValue, %DateTimeFormat%) returns false.
+for (let thisValue of thisValues().filter(IsObject)) {
+ let isPrototypeOf = Intl.DateTimeFormat.prototype.isPrototypeOf(thisValue);
+ 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), isPrototypeOf);
+ assertEq(obj instanceof Intl.DateTimeFormat, true);
+ assertEq(hasInstanceCalled, false);
+ assertEqArray(Object.getOwnPropertySymbols(thisValue), isPrototypeOf ? [intlFallbackSymbol] : []);
+}
+// - 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..6bfbf94df3
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/day-period-hour-cycle.js
@@ -0,0 +1,102 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+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..203ba03766
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/day-period-standalone.js
@@ -0,0 +1,160 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// 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..486970e519
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/day-period.js
@@ -0,0 +1,40 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+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/era.js b/js/src/tests/non262/Intl/DateTimeFormat/era.js
new file mode 100644
index 0000000000..716fa2cf0d
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/era.js
@@ -0,0 +1,245 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const {
+ Era, Year, Month, Day, Literal
+} = DateTimeFormatParts;
+
+const tests = {
+ "en": [
+ {
+ options: {
+ day: "numeric",
+ month: "numeric",
+ year: "numeric",
+ era: "short",
+ timeZone: "UTC",
+ },
+ dates: [
+ {
+ date: new Date("1970-01-01T00:00:00.000Z"),
+ parts: [
+ Month("1"), Literal("/"), Day("1"), Literal("/"), Year("1970"), Literal(" "), Era("AD")
+ ],
+ },
+ {
+ date: new Date("-001970-01-01T00:00:00.000Z"),
+ parts: [
+ Month("1"), Literal("/"), Day("1"), Literal("/"), Year("1971"), Literal(" "), Era("BC")
+ ],
+ },
+ ],
+ },
+ ],
+ "en-001": [
+ {
+ options: {
+ day: "numeric",
+ month: "numeric",
+ year: "numeric",
+ era: "short",
+ timeZone: "UTC",
+ },
+ dates: [
+ {
+ date: new Date("1970-01-01T00:00:00.000Z"),
+ parts: [
+ Day("1"), Literal("/"), Month("1"), Literal("/"), Year("1970"), Literal(" "), Era("AD")
+ ],
+ },
+ {
+ date: new Date("-001970-01-01T00:00:00.000Z"),
+ parts: [
+ Day("1"), Literal("/"), Month("1"), Literal("/"), Year("1971"), Literal(" "), Era("BC")
+ ],
+ },
+ ],
+ },
+ ],
+ "de": [
+ {
+ options: {
+ day: "numeric",
+ month: "numeric",
+ year: "numeric",
+ era: "short",
+ timeZone: "UTC",
+ },
+ dates: [
+ {
+ date: new Date("1970-01-01T00:00:00.000Z"),
+ parts: [
+ Day("01"), Literal("."), Month("01"), Literal("."), Year("1970"), Literal(" "), Era("n. Chr.")
+ ],
+ },
+ {
+ date: new Date("-001970-01-01T00:00:00.000Z"),
+ parts: [
+ Day("01"), Literal("."), Month("01"), Literal("."), Year("1971"), Literal(" "), Era("v. Chr.")
+ ],
+ },
+ ],
+ },
+ ],
+ "fr": [
+ {
+ options: {
+ day: "numeric",
+ month: "numeric",
+ year: "numeric",
+ era: "short",
+ timeZone: "UTC",
+ },
+ dates: [
+ {
+ date: new Date("1970-01-01T00:00:00.000Z"),
+ parts: [
+ Day("01"), Literal("/"), Month("01"), Literal("/"), Year("1970"), Literal(" "), Era("ap. J.-C.")
+ ],
+ },
+ {
+ date: new Date("-001970-01-01T00:00:00.000Z"),
+ parts: [
+ Day("01"), Literal("/"), Month("01"), Literal("/"), Year("1971"), Literal(" "), Era("av. J.-C.")
+ ],
+ },
+ ],
+ },
+ ],
+ "es": [
+ {
+ options: {
+ day: "numeric",
+ month: "numeric",
+ year: "numeric",
+ era: "short",
+ timeZone: "UTC",
+ },
+ dates: [
+ {
+ date: new Date("1970-01-01T00:00:00.000Z"),
+ parts: [
+ Day("1"), Literal("/"), Month("1"), Literal("/"), Year("1970"), Literal(" "), Era("d. C.")
+ ],
+ },
+ {
+ date: new Date("-001970-01-01T00:00:00.000Z"),
+ parts: [
+ Day("1"), Literal("/"), Month("1"), Literal("/"), Year("1971"), Literal(" "), Era("a. C.")
+ ],
+ },
+ ],
+ },
+ ],
+ "nl": [
+ {
+ options: {
+ day: "numeric",
+ month: "numeric",
+ year: "numeric",
+ era: "short",
+ timeZone: "UTC",
+ },
+ dates: [
+ {
+ date: new Date("1970-01-01T00:00:00.000Z"),
+ parts: [
+ Day("1"), Literal("/"), Month("1"), Literal("/"), Year("1970"), Literal(" "), Era("n.Chr.")
+ ],
+ },
+ {
+ date: new Date("-001970-01-01T00:00:00.000Z"),
+ parts: [
+ Day("1"), Literal("/"), Month("1"), Literal("/"), Year("1971"), Literal(" "), Era("v.Chr.")
+ ],
+ },
+ ],
+ },
+ ],
+ "ja": [
+ {
+ options: {
+ day: "numeric",
+ month: "numeric",
+ year: "numeric",
+ era: "short",
+ timeZone: "UTC",
+ },
+ dates: [
+ {
+ date: new Date("1970-01-01T00:00:00.000Z"),
+ parts: [
+ Era("西暦"), Year("1970"), Literal("/"), Month("1"), Literal("/"), Day("1")
+ ],
+ },
+ {
+ date: new Date("-001970-01-01T00:00:00.000Z"),
+ parts: [
+ Era("紀元前"), Year("1971"), Literal("/"), Month("1"), Literal("/"), Day("1")
+ ],
+ },
+ ],
+ },
+ ],
+ "zh": [
+ {
+ options: {
+ day: "numeric",
+ month: "numeric",
+ year: "numeric",
+ era: "short",
+ timeZone: "UTC",
+ },
+ dates: [
+ {
+ date: new Date("1970-01-01T00:00:00.000Z"),
+ parts: [
+ Era("公元"), Literal(" "), Year("1970"), Literal("-"), Month("01"), Literal("-"), Day("01")
+ ],
+ },
+ {
+ date: new Date("-001970-01-01T00:00:00.000Z"),
+ parts: [
+ Era("公元前"), Literal(" "), Year("1971"), Literal("-"), Month("01"), Literal("-"), Day("01")
+ ],
+ },
+ ],
+ },
+ ],
+ "ar": [
+ {
+ options: {
+ day: "numeric",
+ month: "numeric",
+ year: "numeric",
+ era: "short",
+ timeZone: "UTC",
+ },
+ dates: [
+ {
+ date: new Date("1970-01-01T00:00:00.000Z"),
+ parts: [
+ Day("٠١"), Literal("-"), Month("٠١"), Literal("-"), Year("١٩٧٠"), Literal(" "), Era("م")
+ ],
+ },
+ {
+ date: new Date("-001970-01-01T00:00:00.000Z"),
+ parts: [
+ Day("٠١"), Literal("-"), Month("٠١"), Literal("-"), Year("١٩٧١"), Literal(" "), Era("ق.م")
+ ],
+ },
+ ],
+ },
+ ],
+};
+
+for (let [locale, inputs] of Object.entries(tests)) {
+ for (let {options, dates} of inputs) {
+ let dtf = new Intl.DateTimeFormat(locale, options);
+ for (let {date, parts} of dates) {
+ assertParts(dtf, date, parts);
+ }
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/extended-time-zone-names.js b/js/src/tests/non262/Intl/DateTimeFormat/extended-time-zone-names.js
new file mode 100644
index 0000000000..12090c4119
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/extended-time-zone-names.js
@@ -0,0 +1,124 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const tests = {
+ "America/Los_Angeles": {
+ date: Date.UTC(2021, 5-1, 20, 12, 0, 0),
+ timeZoneName: "longGeneric",
+ locales: {
+ "en": "5/20/2021, Pacific Time",
+ "de": "20.5.2021, Nordamerikanische Westküstenzeit",
+ "fr": "20/05/2021 à heure du Pacifique nord-américain",
+ "ar": "٢٠‏/٥‏/٢٠٢١ توقيت المحيط الهادي",
+ "th": "20/5/2564 เวลาแปซิฟิกในอเมริกาเหนือ",
+ "zh": "2021/5/20 北美太平洋时间",
+ "ja": "2021/5/20 アメリカ太平洋時間",
+ }
+ },
+ "America/Los_Angeles": {
+ date: Date.UTC(2021, 5-1, 20, 12, 0, 0),
+ timeZoneName: "shortGeneric",
+ locales: {
+ "en": "5/20/2021, PT",
+ "de": "20.5.2021, Los Angeles Zeit",
+ "fr": "20/05/2021 à heure : Los Angeles",
+ "ar": "٢٠‏/٥‏/٢٠٢١ توقيت Los Angeles",
+ "th": "20/5/2564 เวลาLos Angeles",
+ "zh": "2021/5/20 Los Angeles时间",
+ "ja": "2021/5/20 Los Angeles時間",
+ }
+ },
+ "America/Los_Angeles": {
+ date: Date.UTC(2021, 5-1, 20, 12, 0, 0),
+ timeZoneName: "longOffset",
+ locales: {
+ "en": "5/20/2021, GMT-07:00",
+ "de": "20.5.2021, GMT-07:00",
+ "fr": "20/05/2021 à UTC−07:00",
+ "ar": "٢٠‏/٥‏/٢٠٢١ غرينتش-٠٧:٠٠",
+ "th": "20/5/2564 GMT-07:00",
+ "zh": "2021/5/20 GMT-07:00",
+ "ja": "2021/5/20 GMT-07:00",
+ }
+ },
+ "America/Los_Angeles": {
+ date: Date.UTC(2021, 5-1, 20, 12, 0, 0),
+ timeZoneName: "shortOffset",
+ locales: {
+ "en": "5/20/2021, GMT-7",
+ "de": "20.5.2021, GMT-7",
+ "fr": "20/05/2021 UTC−7",
+ "ar": "٢٠‏/٥‏/٢٠٢١، غرينتش-٧",
+ "th": "20/5/2564 GMT-7",
+ "zh": "2021/5/20 GMT-7",
+ "ja": "2021/5/20 GMT-7",
+ }
+ },
+ "Europe/Berlin": {
+ date: Date.UTC(2021, 5-1, 20, 12, 0, 0),
+ timeZoneName: "longGeneric",
+ locales: {
+ "en": "5/20/2021, Central European Time",
+ "de": "20.5.2021, Mitteleuropäische Zeit",
+ "fr": "20/05/2021 à heure d’Europe centrale",
+ "ar": "٢٠‏/٥‏/٢٠٢١ توقيت وسط أوروبا",
+ "th": "20/5/2564 เวลายุโรปกลาง",
+ "zh": "2021/5/20 中欧时间",
+ "ja": "2021/5/20 中央ヨーロッパ時間",
+ }
+ },
+ "Europe/Berlin": {
+ date: Date.UTC(2021, 5-1, 20, 12, 0, 0),
+ timeZoneName: "shortGeneric",
+ locales: {
+ "en": "5/20/2021, Germany Time",
+ "de": "20.5.2021, MEZ",
+ "fr": "20/05/2021 heure : Allemagne",
+ "ar": "٢٠‏/٥‏/٢٠٢١، توقيت ألمانيا",
+ "th": "20/5/2564 เวลาเยอรมนี",
+ "zh": "2021/5/20 德国时间",
+ "ja": "2021/5/20 ドイツ時間",
+ }
+ },
+ "Africa/Monrovia": {
+ date: Date.UTC(1971, 12-1, 6, 12, 0, 0),
+ timeZoneName: "longOffset",
+ locales: {
+ "en": "12/6/1971, GMT-00:44:30",
+ "de": "6.12.1971, GMT-00:44:30",
+ "fr": "06/12/1971 UTC−00:44:30",
+ "ar": "٦‏/١٢‏/١٩٧١ غرينتش-٠٠:٤٤:٣٠",
+ "th": "6/12/2514 GMT-00:44:30",
+ "zh": "1971/12/6 GMT-00:44:30",
+ "ja": "1971/12/6 GMT-00:44:30",
+ }
+ },
+ "Africa/Monrovia": {
+ date: Date.UTC(1971, 12-1, 6, 12, 0, 0),
+ timeZoneName: "shortOffset",
+ locales: {
+ "en": "12/6/1971, GMT-0:44:30",
+ "de": "6.12.1971, GMT-0:44:30",
+ "fr": "06/12/1971 UTC−0:44:30",
+ "ar": "٦‏/١٢‏/١٩٧١، غرينتش-٠:٤٤:٣٠",
+ "th": "6/12/2514 GMT-0:44:30",
+ "zh": "1971/12/6 GMT-0:44:30",
+ "ja": "1971/12/6 GMT-0:44:30",
+ }
+ },
+};
+
+for (let [timeZone, {timeZoneName, date, locales}] of Object.entries(tests)) {
+ for (let [locale, expected] of Object.entries(locales)) {
+ let dtf = new Intl.DateTimeFormat(locale, {timeZone, timeZoneName});
+ assertEq(dtf.format(date), expected);
+
+ let parts = dtf.formatToParts(date);
+ assertEq(parts.map(p => p.value).join(""), expected);
+ assertEq(parts.filter(p => p.type === "timeZoneName").length, 1);
+
+ assertEq(dtf.resolvedOptions().timeZoneName, timeZoneName);
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Intl/DateTimeFormat/field-widths.js b/js/src/tests/non262/Intl/DateTimeFormat/field-widths.js
new file mode 100644
index 0000000000..789d0fdaa6
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/field-widths.js
@@ -0,0 +1,225 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const date = new Date(Date.UTC(2021, 1-1, 2, 3, 4, 5, 678));
+
+const tests = [
+ // Standalone 'hour' field.
+ {
+ options: {
+ hour: "numeric",
+ },
+ locales: {
+ "en": "3 AM",
+ "de": "03 Uhr",
+ },
+ },
+ {
+ options: {
+ hour: "2-digit",
+ },
+ locales: {
+ "en": "03 AM",
+ "de": "03 Uhr",
+ },
+ },
+
+ // Standalone 'minute' field.
+ {
+ options: {
+ minute: "numeric",
+ },
+ locales: {
+ "en": "4",
+ "de": "4",
+ },
+ },
+ {
+ options: {
+ minute: "2-digit",
+ },
+ locales: {
+ "en": "04",
+ "de": "04",
+ },
+ },
+
+ // Standalone 'second' field.
+ {
+ options: {
+ second: "numeric",
+ },
+ locales: {
+ "en": "5",
+ "de": "5",
+ },
+ },
+ {
+ options: {
+ second: "2-digit",
+ },
+ locales: {
+ "en": "05",
+ "de": "05",
+ },
+ },
+
+ // 'hour' and 'minute' fields with all possible field width combinations.
+ {
+ options: {
+ hour: "numeric",
+ minute: "numeric",
+ },
+ locales: {
+ "en": "3:04 AM",
+ "de": "03:04",
+ },
+ },
+ {
+ options: {
+ hour: "numeric",
+ minute: "2-digit",
+ },
+ locales: {
+ "en": "3:04 AM",
+ "de": "03:04",
+ },
+ },
+ {
+ options: {
+ hour: "2-digit",
+ minute: "numeric",
+ },
+ locales: {
+ "en": "03:04 AM",
+ "de": "03:04",
+ },
+ },
+ {
+ options: {
+ hour: "2-digit",
+ minute: "2-digit",
+ },
+ locales: {
+ "en": "03:04 AM",
+ "de": "03:04",
+ },
+ },
+
+ // 'minute' and 'second' fields with all possible field width combinations.
+ {
+ options: {
+ minute: "numeric",
+ second: "numeric",
+ },
+ locales: {
+ "en": "04:05",
+ "de": "04:05",
+ },
+ },
+ {
+ options: {
+ minute: "numeric",
+ second: "2-digit",
+ },
+ locales: {
+ "en": "04:05",
+ "de": "04:05",
+ },
+ },
+ {
+ options: {
+ minute: "2-digit",
+ second: "numeric",
+ },
+ locales: {
+ "en": "04:05",
+ "de": "04:05",
+ },
+ },
+ {
+ options: {
+ minute: "2-digit",
+ second: "2-digit",
+ },
+ locales: {
+ "en": "04:05",
+ "de": "04:05",
+ },
+ },
+
+ // Test 'hour' and 'minute' with 'hourCycle=h12'.
+ {
+ options: {
+ hour: "numeric",
+ minute: "numeric",
+ hourCycle: "h12",
+ },
+ locales: {
+ "en": "3:04 AM",
+ "de": "3:04 AM",
+ },
+ },
+ {
+ options: {
+ hour: "2-digit",
+ minute: "2-digit",
+ hourCycle: "h12",
+ },
+ locales: {
+ "en": "03:04 AM",
+ "de": "03:04 AM",
+ },
+ },
+
+ // Test 'hour' and 'minute' with 'hourCycle=h23'.
+ {
+ options: {
+ hour: "numeric",
+ minute: "numeric",
+ hourCycle: "h23",
+ },
+ locales: {
+ "en": "03:04",
+ "de": "03:04",
+ },
+ },
+ {
+ options: {
+ hour: "2-digit",
+ minute: "2-digit",
+ hourCycle: "h23",
+ },
+ locales: {
+ "en": "03:04",
+ "de": "03:04",
+ },
+ },
+];
+
+for (let {options, locales} of tests) {
+ for (let [locale, expected] of Object.entries(locales)) {
+ let dtf = new Intl.DateTimeFormat(locale, {timeZone: "UTC", ...options});
+ assertEq(dtf.format(date), expected);
+ }
+}
+
+const toLocaleTests = {
+ "en": "1/2/2021, 3:04:05 AM",
+ "de": "2.1.2021, 03:04:05",
+};
+
+for (let [locale, expected] of Object.entries(toLocaleTests)) {
+ assertEq(date.toLocaleString(locale, {timeZone: "UTC"}), expected);
+}
+
+const toLocaleTimeTests = {
+ "en": "3:04:05 AM",
+ "de": "03:04:05",
+};
+
+for (let [locale, expected] of Object.entries(toLocaleTimeTests)) {
+ assertEq(date.toLocaleTimeString(locale, {timeZone: "UTC"}), expected);
+}
+
+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..224d19cfa8
--- /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..9356577b47
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/formatRange-gregorian-proleptic.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// 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..7921f502cc
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/formatRange-hour-cycle.js
@@ -0,0 +1,450 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// 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
+// https://unicode-org.atlassian.net/browse/ICU-21342
+// https://unicode-org.atlassian.net/browse/ICU-21343
+
+// 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 – 10 AM",
+ "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 – 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 – 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 – 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 – 10 Uhr AM",
+ "12 – 10 Uhr AM",
+ "00–10 Uhr",
+ "24–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–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–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 – 10 Uhr AM",
+ "1 – 10 Uhr AM",
+ "01–10 Uhr",
+ "01–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–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–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 – 10 Uhr PM",
+ "12 – 10 Uhr PM",
+ "12–22 Uhr",
+ "12–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 – 11 Uhr PM",
+ "10 – 11 Uhr PM",
+ "22–23 Uhr",
+ "22–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..67cebd1843
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/formatRange-matches-format-output.js
@@ -0,0 +1,58 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// 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/formatRange-original-skeleton.js b/js/src/tests/non262/Intl/DateTimeFormat/formatRange-original-skeleton.js
new file mode 100644
index 0000000000..c24598794e
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/formatRange-original-skeleton.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Test that the interval formatting uses the original skeleton, not the skeleton
+// derived from the resolved pattern.
+{
+ let dtf = new Intl.DateTimeFormat("zh-Hans-CN", {
+ formatMatcher: "best fit",
+ month: "narrow",
+ day: "2-digit",
+ timeZone: "UTC"
+ });
+
+ assertEq(dtf.format(Date.UTC(2016, 7, 1)), "8月01日");
+ assertEq(dtf.formatRange(Date.UTC(2016, 7, 1), Date.UTC(2016, 7, 2)), "8月1日至2日");
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
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..5cc3bf10cf
--- /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..0ef5540179
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/fractional-second-digits-append-item.js
@@ -0,0 +1,58 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+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: [
+ Minute("٠٠"),
+ Literal(":"),
+ Second("٠٠"),
+ Literal("٫"),
+ FractionalSecond("١٢٣"),
+ ]
+ },
+
+ // https://unicode-org.atlassian.net/browse/ICU-20992
+ {
+ 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/implied-script-has-consistent-output.js b/js/src/tests/non262/Intl/DateTimeFormat/implied-script-has-consistent-output.js
new file mode 100644
index 0000000000..6c0b4d2f59
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/implied-script-has-consistent-output.js
@@ -0,0 +1,59 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+if (typeof getAvailableLocalesOf === "undefined") {
+ var getAvailableLocalesOf = SpecialPowers.Cu.getJSTestingFunctions().getAvailableLocalesOf;
+}
+
+// Retrieve all available locales of Intl.DateTimeFormat.
+const available = getAvailableLocalesOf("DateTimeFormat");
+
+const options = [
+ // Include "hour" to catch hour cycle differences.
+ //
+ // For example "ff-Latn-GH" (Fulah as spoken in Ghana) uses a 12-hour clock,
+ // whereas "ff" (Fulah) uses a 24-hour clock. When the user creates the
+ // formatter for "ff-GH", it should use the same formatter data as "ff-Latn-GH"
+ // and it shouldn't fallback to "ff".
+ {hour: "2-digit", minute: "2-digit", timeZone: "UTC"},
+
+ // Include "timeZoneName" to catch script differences, e.g traditional or
+ // simplified Chinese characters.
+ {timeZoneName: "long", timeZone: "America/Los_Angeles"},
+];
+
+// Pick a date after 12 pm to catch any hour cycle differences.
+const date = Date.UTC(2021, 6-1, 7, 15, 0);
+
+available.map(x => {
+ return new Intl.Locale(x);
+}).filter(loc => {
+ // Find all locales which have both a script and a region subtag.
+ return loc.script && loc.region;
+}).filter(loc => {
+ // Skip "sd-Deva-IN" and Fulah because of <https://unicode-org.atlassian.net/browse/ICU-21974>.
+ return !((loc.language === "sd" && loc.script === "Deva" && loc.region === "IN") ||
+ (loc.language === "ff" && (loc.script === "Adlm" || loc.script === "Latn") &&
+ (loc.region === "GH" || loc.region === "GM" || loc.region === "LR" || loc.region === "SL"))
+ );
+}).forEach(loc => {
+ // Remove the script subtag from the locale.
+ let noScript = new Intl.Locale(`${loc.language}-${loc.region}`);
+
+ // Add the likely script subtag.
+ let maximized = noScript.maximize();
+
+ for (let opt of options) {
+ // Formatter for the locale without a script subtag.
+ let df1 = new Intl.DateTimeFormat(noScript, opt);
+
+ // Formatter for the locale with the likely script subtag added.
+ let df2 = new Intl.DateTimeFormat(maximized, opt);
+
+ // The output for the locale without a script subtag should match the output
+ // with the likely script subtag added.
+ assertEq(df1.format(date), df2.format(date), `Mismatch for locale "${noScript}" (${maximized})`);
+ }
+});
+
+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..e972d2e55a
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/japanese-gannen-year.js
@@ -0,0 +1,54 @@
+// |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/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, "1");
+
+var dtf = new Intl.DateTimeFormat("ja-u-ca-japanese", {
+ era: "short",
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ timeZone: "Asia/Tokyo",
+});
+
+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), "昭和٦٤/١/٧");
+assertEq(dtf.format(startHeisei), "平成١/١/٨");
+
+var dtf = new Intl.DateTimeFormat("ja-u-ca-japanese-nu-arab", {
+ era: "short",
+ year: "numeric",
+ month: "numeric",
+ 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..e9a13c1534
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/options-property-accesses.js
@@ -0,0 +1,77 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+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 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..296de04883
--- /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")],
+ "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")],
+ "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("04"), 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..234616bfd9
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/timeZone_backward_links.js
@@ -0,0 +1,156 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Generated by make_intl_data.py. DO NOT EDIT.
+// tzdata version = 2023c
+
+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/Kralendijk": "America/Curacao",
+ "America/Louisville": "America/Kentucky/Louisville",
+ "America/Lower_Princes": "America/Curacao",
+ "America/Marigot": "America/Port_of_Spain",
+ "America/Mendoza": "America/Argentina/Mendoza",
+ "America/Porto_Acre": "America/Rio_Branco",
+ "America/Santa_Isabel": "America/Tijuana",
+ "America/Shiprock": "America/Denver",
+ "America/St_Barthelemy": "America/Port_of_Spain",
+ "America/Virgin": "America/St_Thomas",
+ "Antarctica/South_Pole": "Antarctica/McMurdo",
+ "Arctic/Longyearbyen": "Europe/Oslo",
+ "Asia/Ashkhabad": "Asia/Ashgabat",
+ "Asia/Calcutta": "Asia/Kolkata",
+ "Asia/Chungking": "Asia/Chongqing",
+ "Asia/Dacca": "Asia/Dhaka",
+ "Asia/Istanbul": "Europe/Istanbul",
+ "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/GMT+0": "Etc/GMT",
+ "Etc/GMT-0": "Etc/GMT",
+ "Etc/GMT0": "Etc/GMT",
+ "Etc/Greenwich": "Etc/GMT",
+ "Etc/UCT": "Etc/UTC",
+ "Etc/Universal": "Etc/UTC",
+ "Etc/Zulu": "Etc/UTC",
+ "Europe/Bratislava": "Europe/Prague",
+ "Europe/Busingen": "Europe/Zurich",
+ "Europe/Kiev": "Europe/Kyiv",
+ "Europe/Mariehamn": "Europe/Helsinki",
+ "Europe/Nicosia": "Asia/Nicosia",
+ "Europe/Podgorica": "Europe/Belgrade",
+ "Europe/San_Marino": "Europe/Rome",
+ "Europe/Vatican": "Europe/Rome",
+ "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..0c5882deca
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/timeZone_backzone.js
@@ -0,0 +1,153 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Generated by make_intl_data.py. DO NOT EDIT.
+// tzdata version = 2023c
+
+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/Accra": "Africa/Accra",
+ "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/Atikokan": "America/Atikokan",
+ "America/Blanc-Sablon": "America/Blanc-Sablon",
+ "America/Cayman": "America/Cayman",
+ "America/Coral_Harbour": "America/Coral_Harbour",
+ "America/Creston": "America/Creston",
+ "America/Curacao": "America/Curacao",
+ "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/Nassau": "America/Nassau",
+ "America/Nipigon": "America/Nipigon",
+ "America/Pangnirtung": "America/Pangnirtung",
+ "America/Port_of_Spain": "America/Port_of_Spain",
+ "America/Rainy_River": "America/Rainy_River",
+ "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/Thunder_Bay": "America/Thunder_Bay",
+ "America/Tortola": "America/Tortola",
+ "America/Yellowknife": "America/Yellowknife",
+ "Antarctica/DumontDUrville": "Antarctica/DumontDUrville",
+ "Antarctica/McMurdo": "Antarctica/McMurdo",
+ "Antarctica/Syowa": "Antarctica/Syowa",
+ "Antarctica/Vostok": "Antarctica/Vostok",
+ "Asia/Aden": "Asia/Aden",
+ "Asia/Bahrain": "Asia/Bahrain",
+ "Asia/Brunei": "Asia/Brunei",
+ "Asia/Chongqing": "Asia/Chongqing",
+ "Asia/Harbin": "Asia/Harbin",
+ "Asia/Kashgar": "Asia/Kashgar",
+ "Asia/Kuala_Lumpur": "Asia/Kuala_Lumpur",
+ "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/Reykjavik": "Atlantic/Reykjavik",
+ "Atlantic/St_Helena": "Atlantic/St_Helena",
+ "Australia/Currie": "Australia/Currie",
+ "Europe/Amsterdam": "Europe/Amsterdam",
+ "Europe/Belfast": "Europe/Belfast",
+ "Europe/Copenhagen": "Europe/Copenhagen",
+ "Europe/Guernsey": "Europe/Guernsey",
+ "Europe/Isle_of_Man": "Europe/Isle_of_Man",
+ "Europe/Jersey": "Europe/Jersey",
+ "Europe/Ljubljana": "Europe/Ljubljana",
+ "Europe/Luxembourg": "Europe/Luxembourg",
+ "Europe/Monaco": "Europe/Monaco",
+ "Europe/Oslo": "Europe/Oslo",
+ "Europe/Sarajevo": "Europe/Sarajevo",
+ "Europe/Skopje": "Europe/Skopje",
+ "Europe/Stockholm": "Europe/Stockholm",
+ "Europe/Tiraspol": "Europe/Tiraspol",
+ "Europe/Uzhgorod": "Europe/Uzhgorod",
+ "Europe/Vaduz": "Europe/Vaduz",
+ "Europe/Zagreb": "Europe/Zagreb",
+ "Europe/Zaporozhye": "Europe/Zaporozhye",
+ "Indian/Antananarivo": "Indian/Antananarivo",
+ "Indian/Christmas": "Indian/Christmas",
+ "Indian/Cocos": "Indian/Cocos",
+ "Indian/Comoro": "Indian/Comoro",
+ "Indian/Kerguelen": "Indian/Kerguelen",
+ "Indian/Mahe": "Indian/Mahe",
+ "Indian/Mayotte": "Indian/Mayotte",
+ "Indian/Reunion": "Indian/Reunion",
+ "Pacific/Chuuk": "Pacific/Chuuk",
+ "Pacific/Enderbury": "Pacific/Enderbury",
+ "Pacific/Funafuti": "Pacific/Funafuti",
+ "Pacific/Johnston": "Pacific/Johnston",
+ "Pacific/Majuro": "Pacific/Majuro",
+ "Pacific/Midway": "Pacific/Midway",
+ "Pacific/Pohnpei": "Pacific/Pohnpei",
+ "Pacific/Saipan": "Pacific/Saipan",
+ "Pacific/Wake": "Pacific/Wake",
+ "Pacific/Wallis": "Pacific/Wallis",
+};
+
+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..15806dd42c
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/timeZone_backzone_links.js
@@ -0,0 +1,48 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Generated by make_intl_data.py. DO NOT EDIT.
+// tzdata version = 2023c
+
+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",
+ "America/Kralendijk": "America/Curacao",
+ "America/Lower_Princes": "America/Curacao",
+ "America/Marigot": "America/Port_of_Spain",
+ "America/St_Barthelemy": "America/Port_of_Spain",
+ "America/Virgin": "America/St_Thomas",
+ "Antarctica/South_Pole": "Antarctica/McMurdo",
+ "Arctic/Longyearbyen": "Europe/Oslo",
+ "Asia/Chungking": "Asia/Chongqing",
+ "Iceland": "Atlantic/Reykjavik",
+ "Pacific/Ponape": "Pacific/Pohnpei",
+ "Pacific/Truk": "Pacific/Chuuk",
+ "Pacific/Yap": "Pacific/Chuuk",
+};
+
+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..7dbd456c2e
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/timeZone_notbackward_links.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Generated by make_intl_data.py. DO NOT EDIT.
+// tzdata version = 2023c
+
+const tzMapper = [
+ x => x,
+ x => x.toUpperCase(),
+ x => x.toLowerCase(),
+];
+
+// Link names derived from IANA Time Zone Database, excluding backward file.
+const links = {
+ "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..15c7553dd8
--- /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 = 2023c
+const tzdata = "2023c";
+
+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..d2fc991cb4
--- /dev/null
+++ b/js/src/tests/non262/Intl/DateTimeFormat/unwrapping.js
@@ -0,0 +1,248 @@
+// |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 |true|.
+ for (let thisValue of thisValues(Intl.DateTimeFormat).filter(IsObject)) {
+ let isPrototypeOf = Intl.DateTimeFormat.prototype.isPrototypeOf(thisValue);
+ 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, false);
+ assertEq(symbolGetterCalled, unwrap && isPrototypeOf);
+ }
+
+ // 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);