diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /js/src/tests/non262/Intl/DateTimeFormat | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/tests/non262/Intl/DateTimeFormat')
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); |