summaryrefslogtreecommitdiffstats
path: root/js/src/tests/test262/intl402/Temporal/ZonedDateTime
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/tests/test262/intl402/Temporal/ZonedDateTime
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/tests/test262/intl402/Temporal/ZonedDateTime')
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/infinity-throws-rangeerror.js31
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/shell.js1990
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/argument-propertybag-timezone-string-datetime.js29
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/infinity-throws-rangeerror.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/shell.js1990
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-timezone-string-datetime.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/shell.js1990
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/branding.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/prop-desc.js17
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/shell.js1990
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-non-integer.js18
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-not-callable.js22
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-out-of-range.js18
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-wrong-type.js27
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/validate-calendar-value.js54
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/branding.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/prop-desc.js17
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/shell.js1990
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-non-integer.js18
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-not-callable.js22
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-out-of-range.js18
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-wrong-type.js27
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/validate-calendar-value.js54
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/argument-propertybag-timezone-string-datetime.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/shell.js1990
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/locales-undefined.js21
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-conflict.js51
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-undefined.js21
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/argument-propertybag-timezone-string-datetime.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/shell.js1990
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/calendar-case-insensitive.js17
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/shell.js1990
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/shell.js0
52 files changed, 16648 insertions, 0 deletions
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..a1565a6ac0
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/infinity-throws-rangeerror.js
@@ -0,0 +1,31 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if any value in a property bag for either argument is Infinity or -Infinity
+esid: sec-temporal.zoneddatetime.compare
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const other = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", "gregory");
+const base = { era: "ad", month: 5, day: 2, hour: 15, timeZone: "UTC", calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => Temporal.ZonedDateTime.compare({ ...base, eraYear: inf }, other), `eraYear property cannot be ${inf}`);
+
+ assert.throws(RangeError, () => Temporal.ZonedDateTime.compare(other, { ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls1 = [];
+ const obj1 = TemporalHelpers.toPrimitiveObserver(calls1, inf, "eraYear");
+ assert.throws(RangeError, () => Temporal.ZonedDateTime.compare({ ...base, eraYear: obj1 }, other));
+ assert.compareArray(calls1, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+
+ const calls2 = [];
+ const obj2 = TemporalHelpers.toPrimitiveObserver(calls2, inf, "eraYear");
+ assert.throws(RangeError, () => Temporal.ZonedDateTime.compare(other, { ...base, eraYear: obj2 }));
+ assert.compareArray(calls2, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/shell.js
new file mode 100644
index 0000000000..728dc1717d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/shell.js
@@ -0,0 +1,1990 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "number":
+ return `${objectName}[${propertyKey}]`;
+ default:
+ // TODO: check if propertyKey is an integer index.
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+}
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ assert(duration instanceof Temporal.Duration, `${description} instanceof`);
+ assert.sameValue(duration.years, years, `${description} years result`);
+ assert.sameValue(duration.months, months, `${description} months result`);
+ assert.sameValue(duration.weeks, weeks, `${description} weeks result`);
+ assert.sameValue(duration.days, days, `${description} days result`);
+ assert.sameValue(duration.hours, hours, `${description} hours result`);
+ assert.sameValue(duration.minutes, minutes, `${description} minutes result`);
+ assert.sameValue(duration.seconds, seconds, `${description} seconds result`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${description} milliseconds result`);
+ assert.sameValue(duration.microseconds, microseconds, `${description} microseconds result`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${description} nanoseconds result`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.Duration, `${description} expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.Instant, `${description} expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * result of date.calendar.toString().)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ assert(date instanceof Temporal.PlainDate, `${description} instanceof`);
+ assert.sameValue(date.era, era, `${description} era result`);
+ assert.sameValue(date.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(date.year, year, `${description} year result`);
+ assert.sameValue(date.month, month, `${description} month result`);
+ assert.sameValue(date.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(date.day, day, `${description} day result`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of datetime.calendar.toString().)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ assert(datetime instanceof Temporal.PlainDateTime, `${description} instanceof`);
+ assert.sameValue(datetime.era, era, `${description} era result`);
+ assert.sameValue(datetime.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(datetime.year, year, `${description} year result`);
+ assert.sameValue(datetime.month, month, `${description} month result`);
+ assert.sameValue(datetime.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(datetime.day, day, `${description} day result`);
+ assert.sameValue(datetime.hour, hour, `${description} hour result`);
+ assert.sameValue(datetime.minute, minute, `${description} minute result`);
+ assert.sameValue(datetime.second, second, `${description} second result`);
+ assert.sameValue(datetime.millisecond, millisecond, `${description} millisecond result`);
+ assert.sameValue(datetime.microsecond, microsecond, `${description} microsecond result`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${description} nanosecond result`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendars are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.PlainDateTime, `${description} expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ assert.sameValue(actual.calendar, expected.calendar, `${description} calendar same value`);
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of monthDay.calendar.toString().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${description} instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(monthDay.day, day, `${description} day result`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${description} referenceISOYear result`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ assert(time instanceof Temporal.PlainTime, `${description} instanceof`);
+ assert.sameValue(time.hour, hour, `${description} hour result`);
+ assert.sameValue(time.minute, minute, `${description} minute result`);
+ assert.sameValue(time.second, second, `${description} second result`);
+ assert.sameValue(time.millisecond, millisecond, `${description} millisecond result`);
+ assert.sameValue(time.microsecond, microsecond, `${description} microsecond result`);
+ assert.sameValue(time.nanosecond, nanosecond, `${description} nanosecond result`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.PlainTime, `${description} expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of yearMonth.calendar.toString().)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${description} instanceof`);
+ assert.sameValue(yearMonth.era, era, `${description} era result`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(yearMonth.year, year, `${description} year result`);
+ assert.sameValue(yearMonth.month, month, `${description} month result`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${description} referenceISODay result`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendars are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.ZonedDateTime, `${description} expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${description} time zone same value`);
+ assert.sameValue(actual.calendar, expected.calendar, `${description} calendar same value`);
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns @returnValue from its dateUntil() method,
+ * recording the call in @calls.
+ */
+ calendarDateUntilObservable(calls, returnValue) {
+ class CalendarDateUntilObservable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil() {
+ calls.push("call dateUntil");
+ return returnValue;
+ }
+ }
+
+ return new CalendarDateUntilObservable();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = Temporal.PlainDate.compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ }
+ };
+ // Automatically generate the other methods that don't need any custom code
+ ["toString", "dateUntil", "era", "eraYear", "year", "month", "monthCode", "day", "daysInMonth", "fields", "mergeFields"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString());
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor() {
+ return [];
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {};
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/argument-propertybag-timezone-string-datetime.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/argument-propertybag-timezone-string-datetime.js
new file mode 100644
index 0000000000..3e7757c023
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/argument-propertybag-timezone-string-datetime.js
@@ -0,0 +1,29 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.from
+description: Conversion of ISO date-time strings to Temporal.TimeZone instances (with Intl time zones)
+features: [Temporal]
+---*/
+
+let timeZone = "2021-08-19T17:30[America/Vancouver]";
+const result1 = Temporal.ZonedDateTime.from({ year: 2000, month: 5, day: 2, timeZone });
+assert.sameValue(result1.timeZone.id, "America/Vancouver", "date-time + IANA annotation is the IANA time zone");
+const result2 = Temporal.ZonedDateTime.from({ year: 2000, month: 5, day: 2, timeZone: { timeZone } });
+assert.sameValue(result2.timeZone.id, "America/Vancouver", "date-time + IANA annotation is the IANA time zone (string in property bag)");
+
+timeZone = "2021-08-19T17:30Z[America/Vancouver]";
+const result3 = Temporal.ZonedDateTime.from({ year: 2000, month: 5, day: 2, timeZone });
+assert.sameValue(result3.timeZone.id, "America/Vancouver", "date-time + Z + IANA annotation is the IANA time zone");
+const result4 = Temporal.ZonedDateTime.from({ year: 2000, month: 5, day: 2, timeZone: { timeZone } });
+assert.sameValue(result4.timeZone.id, "America/Vancouver", "date-time + Z + IANA annotation is the IANA time zone (string in property bag)");
+
+timeZone = "2021-08-19T17:30-07:00[America/Vancouver]";
+const result5 = Temporal.ZonedDateTime.from({ year: 2000, month: 5, day: 2, timeZone });
+assert.sameValue(result5.timeZone.id, "America/Vancouver", "date-time + offset + IANA annotation is the IANA time zone");
+const result6 = Temporal.ZonedDateTime.from({ year: 2000, month: 5, day: 2, timeZone: { timeZone } });
+assert.sameValue(result6.timeZone.id, "America/Vancouver", "date-time + offset + IANA annotation is the IANA time zone (string in property bag)");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..d8fd2d6134
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/infinity-throws-rangeerror.js
@@ -0,0 +1,25 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.zoneddatetime.from
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const base = { era: "ad", month: 5, day: 2, hour: 15, timeZone: "UTC", calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ ["constrain", "reject"].forEach((overflow) => {
+ assert.throws(RangeError, () => Temporal.ZonedDateTime.from({ ...base, eraYear: inf }, { overflow }), `eraYear property cannot be ${inf} (overflow ${overflow}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => Temporal.ZonedDateTime.from({ ...base, eraYear: obj }, { overflow }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/shell.js
new file mode 100644
index 0000000000..728dc1717d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/shell.js
@@ -0,0 +1,1990 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "number":
+ return `${objectName}[${propertyKey}]`;
+ default:
+ // TODO: check if propertyKey is an integer index.
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+}
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ assert(duration instanceof Temporal.Duration, `${description} instanceof`);
+ assert.sameValue(duration.years, years, `${description} years result`);
+ assert.sameValue(duration.months, months, `${description} months result`);
+ assert.sameValue(duration.weeks, weeks, `${description} weeks result`);
+ assert.sameValue(duration.days, days, `${description} days result`);
+ assert.sameValue(duration.hours, hours, `${description} hours result`);
+ assert.sameValue(duration.minutes, minutes, `${description} minutes result`);
+ assert.sameValue(duration.seconds, seconds, `${description} seconds result`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${description} milliseconds result`);
+ assert.sameValue(duration.microseconds, microseconds, `${description} microseconds result`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${description} nanoseconds result`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.Duration, `${description} expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.Instant, `${description} expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * result of date.calendar.toString().)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ assert(date instanceof Temporal.PlainDate, `${description} instanceof`);
+ assert.sameValue(date.era, era, `${description} era result`);
+ assert.sameValue(date.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(date.year, year, `${description} year result`);
+ assert.sameValue(date.month, month, `${description} month result`);
+ assert.sameValue(date.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(date.day, day, `${description} day result`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of datetime.calendar.toString().)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ assert(datetime instanceof Temporal.PlainDateTime, `${description} instanceof`);
+ assert.sameValue(datetime.era, era, `${description} era result`);
+ assert.sameValue(datetime.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(datetime.year, year, `${description} year result`);
+ assert.sameValue(datetime.month, month, `${description} month result`);
+ assert.sameValue(datetime.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(datetime.day, day, `${description} day result`);
+ assert.sameValue(datetime.hour, hour, `${description} hour result`);
+ assert.sameValue(datetime.minute, minute, `${description} minute result`);
+ assert.sameValue(datetime.second, second, `${description} second result`);
+ assert.sameValue(datetime.millisecond, millisecond, `${description} millisecond result`);
+ assert.sameValue(datetime.microsecond, microsecond, `${description} microsecond result`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${description} nanosecond result`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendars are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.PlainDateTime, `${description} expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ assert.sameValue(actual.calendar, expected.calendar, `${description} calendar same value`);
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of monthDay.calendar.toString().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${description} instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(monthDay.day, day, `${description} day result`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${description} referenceISOYear result`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ assert(time instanceof Temporal.PlainTime, `${description} instanceof`);
+ assert.sameValue(time.hour, hour, `${description} hour result`);
+ assert.sameValue(time.minute, minute, `${description} minute result`);
+ assert.sameValue(time.second, second, `${description} second result`);
+ assert.sameValue(time.millisecond, millisecond, `${description} millisecond result`);
+ assert.sameValue(time.microsecond, microsecond, `${description} microsecond result`);
+ assert.sameValue(time.nanosecond, nanosecond, `${description} nanosecond result`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.PlainTime, `${description} expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of yearMonth.calendar.toString().)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${description} instanceof`);
+ assert.sameValue(yearMonth.era, era, `${description} era result`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(yearMonth.year, year, `${description} year result`);
+ assert.sameValue(yearMonth.month, month, `${description} month result`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${description} referenceISODay result`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendars are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.ZonedDateTime, `${description} expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${description} time zone same value`);
+ assert.sameValue(actual.calendar, expected.calendar, `${description} calendar same value`);
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns @returnValue from its dateUntil() method,
+ * recording the call in @calls.
+ */
+ calendarDateUntilObservable(calls, returnValue) {
+ class CalendarDateUntilObservable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil() {
+ calls.push("call dateUntil");
+ return returnValue;
+ }
+ }
+
+ return new CalendarDateUntilObservable();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = Temporal.PlainDate.compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ }
+ };
+ // Automatically generate the other methods that don't need any custom code
+ ["toString", "dateUntil", "era", "eraYear", "year", "month", "monthCode", "day", "daysInMonth", "fields", "mergeFields"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString());
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor() {
+ return [];
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {};
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-timezone-string-datetime.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-timezone-string-datetime.js
new file mode 100644
index 0000000000..51a14fdea0
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-timezone-string-datetime.js
@@ -0,0 +1,25 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.equals
+description: Conversion of ISO date-time strings to Temporal.TimeZone instances (with Intl time zones)
+features: [Temporal]
+---*/
+
+const expectedTimeZone = "America/Vancouver";
+const instance = new Temporal.ZonedDateTime(0n, expectedTimeZone);
+let timeZone = "2021-08-19T17:30[America/Vancouver]";
+assert(instance.equals({ year: 1969, month: 12, day: 31, hour: 16, timeZone }), "date-time + IANA annotation is the IANA time zone");
+assert(instance.equals({ year: 1969, month: 12, day: 31, hour: 16, timeZone: { timeZone } }), "date-time + IANA annotation is the IANA time zone (string in property bag)");
+
+timeZone = "2021-08-19T17:30Z[America/Vancouver]";
+assert(instance.equals({ year: 1969, month: 12, day: 31, hour: 16, timeZone }), "date-time + Z + IANA annotation is the IANA time zone");
+assert(instance.equals({ year: 1969, month: 12, day: 31, hour: 16, timeZone: { timeZone } }), "date-time + Z + IANA annotation is the IANA time zone (string in property bag)");
+
+timeZone = "2021-08-19T17:30-07:00[America/Vancouver]";
+assert(instance.equals({ year: 1969, month: 12, day: 31, hour: 16, timeZone }), "date-time + offset + IANA annotation is the IANA time zone");
+assert(instance.equals({ year: 1969, month: 12, day: 31, hour: 16, timeZone: { timeZone } }), "date-time + offset + IANA annotation is the IANA time zone (string in property bag)");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..cb117f6e52
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.zoneddatetime.prototype.equals
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", "gregory");
+const base = { era: "ad", month: 5, day: 2, hour: 15, timeZone: "UTC", calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.equals({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.equals({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/shell.js
new file mode 100644
index 0000000000..728dc1717d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/shell.js
@@ -0,0 +1,1990 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "number":
+ return `${objectName}[${propertyKey}]`;
+ default:
+ // TODO: check if propertyKey is an integer index.
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+}
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ assert(duration instanceof Temporal.Duration, `${description} instanceof`);
+ assert.sameValue(duration.years, years, `${description} years result`);
+ assert.sameValue(duration.months, months, `${description} months result`);
+ assert.sameValue(duration.weeks, weeks, `${description} weeks result`);
+ assert.sameValue(duration.days, days, `${description} days result`);
+ assert.sameValue(duration.hours, hours, `${description} hours result`);
+ assert.sameValue(duration.minutes, minutes, `${description} minutes result`);
+ assert.sameValue(duration.seconds, seconds, `${description} seconds result`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${description} milliseconds result`);
+ assert.sameValue(duration.microseconds, microseconds, `${description} microseconds result`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${description} nanoseconds result`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.Duration, `${description} expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.Instant, `${description} expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * result of date.calendar.toString().)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ assert(date instanceof Temporal.PlainDate, `${description} instanceof`);
+ assert.sameValue(date.era, era, `${description} era result`);
+ assert.sameValue(date.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(date.year, year, `${description} year result`);
+ assert.sameValue(date.month, month, `${description} month result`);
+ assert.sameValue(date.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(date.day, day, `${description} day result`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of datetime.calendar.toString().)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ assert(datetime instanceof Temporal.PlainDateTime, `${description} instanceof`);
+ assert.sameValue(datetime.era, era, `${description} era result`);
+ assert.sameValue(datetime.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(datetime.year, year, `${description} year result`);
+ assert.sameValue(datetime.month, month, `${description} month result`);
+ assert.sameValue(datetime.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(datetime.day, day, `${description} day result`);
+ assert.sameValue(datetime.hour, hour, `${description} hour result`);
+ assert.sameValue(datetime.minute, minute, `${description} minute result`);
+ assert.sameValue(datetime.second, second, `${description} second result`);
+ assert.sameValue(datetime.millisecond, millisecond, `${description} millisecond result`);
+ assert.sameValue(datetime.microsecond, microsecond, `${description} microsecond result`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${description} nanosecond result`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendars are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.PlainDateTime, `${description} expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ assert.sameValue(actual.calendar, expected.calendar, `${description} calendar same value`);
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of monthDay.calendar.toString().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${description} instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(monthDay.day, day, `${description} day result`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${description} referenceISOYear result`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ assert(time instanceof Temporal.PlainTime, `${description} instanceof`);
+ assert.sameValue(time.hour, hour, `${description} hour result`);
+ assert.sameValue(time.minute, minute, `${description} minute result`);
+ assert.sameValue(time.second, second, `${description} second result`);
+ assert.sameValue(time.millisecond, millisecond, `${description} millisecond result`);
+ assert.sameValue(time.microsecond, microsecond, `${description} microsecond result`);
+ assert.sameValue(time.nanosecond, nanosecond, `${description} nanosecond result`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.PlainTime, `${description} expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of yearMonth.calendar.toString().)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${description} instanceof`);
+ assert.sameValue(yearMonth.era, era, `${description} era result`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(yearMonth.year, year, `${description} year result`);
+ assert.sameValue(yearMonth.month, month, `${description} month result`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${description} referenceISODay result`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendars are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.ZonedDateTime, `${description} expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${description} time zone same value`);
+ assert.sameValue(actual.calendar, expected.calendar, `${description} calendar same value`);
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns @returnValue from its dateUntil() method,
+ * recording the call in @calls.
+ */
+ calendarDateUntilObservable(calls, returnValue) {
+ class CalendarDateUntilObservable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil() {
+ calls.push("call dateUntil");
+ return returnValue;
+ }
+ }
+
+ return new CalendarDateUntilObservable();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = Temporal.PlainDate.compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ }
+ };
+ // Automatically generate the other methods that don't need any custom code
+ ["toString", "dateUntil", "era", "eraYear", "year", "month", "monthCode", "day", "daysInMonth", "fields", "mergeFields"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString());
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor() {
+ return [];
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {};
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/branding.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/branding.js
new file mode 100644
index 0000000000..2f4d35a3b3
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/branding.js
@@ -0,0 +1,25 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.era
+description: Throw a TypeError if the receiver is invalid
+features: [Symbol, Temporal]
+---*/
+
+const era = Object.getOwnPropertyDescriptor(Temporal.ZonedDateTime.prototype, "era").get;
+
+assert.sameValue(typeof era, "function");
+
+assert.throws(TypeError, () => era.call(undefined), "undefined");
+assert.throws(TypeError, () => era.call(null), "null");
+assert.throws(TypeError, () => era.call(true), "true");
+assert.throws(TypeError, () => era.call(""), "empty string");
+assert.throws(TypeError, () => era.call(Symbol()), "symbol");
+assert.throws(TypeError, () => era.call(1), "1");
+assert.throws(TypeError, () => era.call({}), "plain object");
+assert.throws(TypeError, () => era.call(Temporal.ZonedDateTime), "Temporal.ZonedDateTime");
+assert.throws(TypeError, () => era.call(Temporal.ZonedDateTime.prototype), "Temporal.ZonedDateTime.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/prop-desc.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/prop-desc.js
new file mode 100644
index 0000000000..af51869f2d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/prop-desc.js
@@ -0,0 +1,17 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.era
+description: The "era" property of Temporal.ZonedDateTime.prototype
+features: [Temporal]
+---*/
+
+const descriptor = Object.getOwnPropertyDescriptor(Temporal.ZonedDateTime.prototype, "era");
+assert.sameValue(typeof descriptor.get, "function");
+assert.sameValue(descriptor.set, undefined);
+assert.sameValue(descriptor.enumerable, false);
+assert.sameValue(descriptor.configurable, true);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/shell.js
new file mode 100644
index 0000000000..728dc1717d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/shell.js
@@ -0,0 +1,1990 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "number":
+ return `${objectName}[${propertyKey}]`;
+ default:
+ // TODO: check if propertyKey is an integer index.
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+}
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ assert(duration instanceof Temporal.Duration, `${description} instanceof`);
+ assert.sameValue(duration.years, years, `${description} years result`);
+ assert.sameValue(duration.months, months, `${description} months result`);
+ assert.sameValue(duration.weeks, weeks, `${description} weeks result`);
+ assert.sameValue(duration.days, days, `${description} days result`);
+ assert.sameValue(duration.hours, hours, `${description} hours result`);
+ assert.sameValue(duration.minutes, minutes, `${description} minutes result`);
+ assert.sameValue(duration.seconds, seconds, `${description} seconds result`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${description} milliseconds result`);
+ assert.sameValue(duration.microseconds, microseconds, `${description} microseconds result`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${description} nanoseconds result`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.Duration, `${description} expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.Instant, `${description} expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * result of date.calendar.toString().)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ assert(date instanceof Temporal.PlainDate, `${description} instanceof`);
+ assert.sameValue(date.era, era, `${description} era result`);
+ assert.sameValue(date.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(date.year, year, `${description} year result`);
+ assert.sameValue(date.month, month, `${description} month result`);
+ assert.sameValue(date.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(date.day, day, `${description} day result`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of datetime.calendar.toString().)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ assert(datetime instanceof Temporal.PlainDateTime, `${description} instanceof`);
+ assert.sameValue(datetime.era, era, `${description} era result`);
+ assert.sameValue(datetime.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(datetime.year, year, `${description} year result`);
+ assert.sameValue(datetime.month, month, `${description} month result`);
+ assert.sameValue(datetime.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(datetime.day, day, `${description} day result`);
+ assert.sameValue(datetime.hour, hour, `${description} hour result`);
+ assert.sameValue(datetime.minute, minute, `${description} minute result`);
+ assert.sameValue(datetime.second, second, `${description} second result`);
+ assert.sameValue(datetime.millisecond, millisecond, `${description} millisecond result`);
+ assert.sameValue(datetime.microsecond, microsecond, `${description} microsecond result`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${description} nanosecond result`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendars are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.PlainDateTime, `${description} expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ assert.sameValue(actual.calendar, expected.calendar, `${description} calendar same value`);
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of monthDay.calendar.toString().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${description} instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(monthDay.day, day, `${description} day result`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${description} referenceISOYear result`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ assert(time instanceof Temporal.PlainTime, `${description} instanceof`);
+ assert.sameValue(time.hour, hour, `${description} hour result`);
+ assert.sameValue(time.minute, minute, `${description} minute result`);
+ assert.sameValue(time.second, second, `${description} second result`);
+ assert.sameValue(time.millisecond, millisecond, `${description} millisecond result`);
+ assert.sameValue(time.microsecond, microsecond, `${description} microsecond result`);
+ assert.sameValue(time.nanosecond, nanosecond, `${description} nanosecond result`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.PlainTime, `${description} expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of yearMonth.calendar.toString().)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${description} instanceof`);
+ assert.sameValue(yearMonth.era, era, `${description} era result`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(yearMonth.year, year, `${description} year result`);
+ assert.sameValue(yearMonth.month, month, `${description} month result`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${description} referenceISODay result`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendars are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.ZonedDateTime, `${description} expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${description} time zone same value`);
+ assert.sameValue(actual.calendar, expected.calendar, `${description} calendar same value`);
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns @returnValue from its dateUntil() method,
+ * recording the call in @calls.
+ */
+ calendarDateUntilObservable(calls, returnValue) {
+ class CalendarDateUntilObservable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil() {
+ calls.push("call dateUntil");
+ return returnValue;
+ }
+ }
+
+ return new CalendarDateUntilObservable();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = Temporal.PlainDate.compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ }
+ };
+ // Automatically generate the other methods that don't need any custom code
+ ["toString", "dateUntil", "era", "eraYear", "year", "month", "monthCode", "day", "daysInMonth", "fields", "mergeFields"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString());
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor() {
+ return [];
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {};
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-non-integer.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-non-integer.js
new file mode 100644
index 0000000000..c70670bca4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-non-integer.js
@@ -0,0 +1,18 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.era
+description: RangeError thrown if time zone reports an offset that is not an integer number of nanoseconds
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[3600_000_000_000.5, NaN, -Infinity, Infinity].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(RangeError, () => datetime.era);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-not-callable.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-not-callable.js
new file mode 100644
index 0000000000..979f76f272
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-not-callable.js
@@ -0,0 +1,22 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.era
+description: TypeError thrown if timeZone.getOffsetNanosecondsFor is not callable
+features: [BigInt, Symbol, Temporal, arrow-function]
+---*/
+
+[undefined, null, true, Math.PI, 'string', Symbol('sym'), 42n, {}].forEach((notCallable) => {
+ const timeZone = new Temporal.TimeZone("UTC");
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ timeZone.getOffsetNanosecondsFor = notCallable;
+ assert.throws(
+ TypeError,
+ () => datetime.era,
+ `Uncallable ${notCallable === null ? 'null' : typeof notCallable} getOffsetNanosecondsFor should throw TypeError`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-out-of-range.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-out-of-range.js
new file mode 100644
index 0000000000..c534c61438
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-out-of-range.js
@@ -0,0 +1,18 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.era
+description: RangeError thrown if time zone reports an offset that is out of range
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[-86400_000_000_000, 86400_000_000_000].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(RangeError, () => datetime.era);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-wrong-type.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-wrong-type.js
new file mode 100644
index 0000000000..bbe42f6d31
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-wrong-type.js
@@ -0,0 +1,27 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.era
+description: TypeError thrown if time zone reports an offset that is not a Number
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[
+ undefined,
+ null,
+ true,
+ "+01:00",
+ Symbol(),
+ 3600_000_000_000n,
+ {},
+ { valueOf() { return 3600_000_000_000; } },
+].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(TypeError, () => datetime.era);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/validate-calendar-value.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/validate-calendar-value.js
new file mode 100644
index 0000000000..1bdb4df02a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/validate-calendar-value.js
@@ -0,0 +1,54 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2023 Richard Gibson. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.era
+description: Validate result returned from calendar era() method
+features: [Temporal]
+---*/
+
+const badResults = [
+ [null, TypeError],
+ [false, TypeError],
+ [Infinity, TypeError],
+ [-Infinity, TypeError],
+ [NaN, TypeError],
+ [-7, TypeError],
+ [-0.1, TypeError],
+ [Symbol("foo"), TypeError],
+ [7n, TypeError],
+ [{}, TypeError],
+ [true, TypeError],
+ [7.1, TypeError],
+ [{valueOf() { return "7"; }}, TypeError],
+];
+
+badResults.forEach(([result, error]) => {
+ const calendar = new class extends Temporal.Calendar {
+ era() {
+ return result;
+ }
+ }("iso8601");
+ const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+ assert.throws(error, () => instance.era, `${typeof result} ${String(result)} not converted to string`);
+});
+
+const preservedResults = [
+ undefined,
+ "string",
+ "7",
+ "7.5",
+];
+
+preservedResults.forEach(result => {
+ const calendar = new class extends Temporal.Calendar {
+ era() {
+ return result;
+ }
+ }("iso8601");
+ const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+ assert.sameValue(instance.era, result, `${typeof result} ${String(result)} preserved`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/branding.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/branding.js
new file mode 100644
index 0000000000..d2b26a3565
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/branding.js
@@ -0,0 +1,25 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.erayear
+description: Throw a TypeError if the receiver is invalid
+features: [Symbol, Temporal]
+---*/
+
+const eraYear = Object.getOwnPropertyDescriptor(Temporal.ZonedDateTime.prototype, "eraYear").get;
+
+assert.sameValue(typeof eraYear, "function");
+
+assert.throws(TypeError, () => eraYear.call(undefined), "undefined");
+assert.throws(TypeError, () => eraYear.call(null), "null");
+assert.throws(TypeError, () => eraYear.call(true), "true");
+assert.throws(TypeError, () => eraYear.call(""), "empty string");
+assert.throws(TypeError, () => eraYear.call(Symbol()), "symbol");
+assert.throws(TypeError, () => eraYear.call(1), "1");
+assert.throws(TypeError, () => eraYear.call({}), "plain object");
+assert.throws(TypeError, () => eraYear.call(Temporal.ZonedDateTime), "Temporal.ZonedDateTime");
+assert.throws(TypeError, () => eraYear.call(Temporal.ZonedDateTime.prototype), "Temporal.ZonedDateTime.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/prop-desc.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/prop-desc.js
new file mode 100644
index 0000000000..53ddcc1be6
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/prop-desc.js
@@ -0,0 +1,17 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.erayear
+description: The "eraYear" property of Temporal.ZonedDateTime.prototype
+features: [Temporal]
+---*/
+
+const descriptor = Object.getOwnPropertyDescriptor(Temporal.ZonedDateTime.prototype, "eraYear");
+assert.sameValue(typeof descriptor.get, "function");
+assert.sameValue(descriptor.set, undefined);
+assert.sameValue(descriptor.enumerable, false);
+assert.sameValue(descriptor.configurable, true);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/shell.js
new file mode 100644
index 0000000000..728dc1717d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/shell.js
@@ -0,0 +1,1990 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "number":
+ return `${objectName}[${propertyKey}]`;
+ default:
+ // TODO: check if propertyKey is an integer index.
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+}
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ assert(duration instanceof Temporal.Duration, `${description} instanceof`);
+ assert.sameValue(duration.years, years, `${description} years result`);
+ assert.sameValue(duration.months, months, `${description} months result`);
+ assert.sameValue(duration.weeks, weeks, `${description} weeks result`);
+ assert.sameValue(duration.days, days, `${description} days result`);
+ assert.sameValue(duration.hours, hours, `${description} hours result`);
+ assert.sameValue(duration.minutes, minutes, `${description} minutes result`);
+ assert.sameValue(duration.seconds, seconds, `${description} seconds result`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${description} milliseconds result`);
+ assert.sameValue(duration.microseconds, microseconds, `${description} microseconds result`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${description} nanoseconds result`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.Duration, `${description} expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.Instant, `${description} expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * result of date.calendar.toString().)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ assert(date instanceof Temporal.PlainDate, `${description} instanceof`);
+ assert.sameValue(date.era, era, `${description} era result`);
+ assert.sameValue(date.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(date.year, year, `${description} year result`);
+ assert.sameValue(date.month, month, `${description} month result`);
+ assert.sameValue(date.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(date.day, day, `${description} day result`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of datetime.calendar.toString().)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ assert(datetime instanceof Temporal.PlainDateTime, `${description} instanceof`);
+ assert.sameValue(datetime.era, era, `${description} era result`);
+ assert.sameValue(datetime.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(datetime.year, year, `${description} year result`);
+ assert.sameValue(datetime.month, month, `${description} month result`);
+ assert.sameValue(datetime.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(datetime.day, day, `${description} day result`);
+ assert.sameValue(datetime.hour, hour, `${description} hour result`);
+ assert.sameValue(datetime.minute, minute, `${description} minute result`);
+ assert.sameValue(datetime.second, second, `${description} second result`);
+ assert.sameValue(datetime.millisecond, millisecond, `${description} millisecond result`);
+ assert.sameValue(datetime.microsecond, microsecond, `${description} microsecond result`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${description} nanosecond result`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendars are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.PlainDateTime, `${description} expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ assert.sameValue(actual.calendar, expected.calendar, `${description} calendar same value`);
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of monthDay.calendar.toString().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${description} instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(monthDay.day, day, `${description} day result`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${description} referenceISOYear result`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ assert(time instanceof Temporal.PlainTime, `${description} instanceof`);
+ assert.sameValue(time.hour, hour, `${description} hour result`);
+ assert.sameValue(time.minute, minute, `${description} minute result`);
+ assert.sameValue(time.second, second, `${description} second result`);
+ assert.sameValue(time.millisecond, millisecond, `${description} millisecond result`);
+ assert.sameValue(time.microsecond, microsecond, `${description} microsecond result`);
+ assert.sameValue(time.nanosecond, nanosecond, `${description} nanosecond result`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.PlainTime, `${description} expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of yearMonth.calendar.toString().)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${description} instanceof`);
+ assert.sameValue(yearMonth.era, era, `${description} era result`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(yearMonth.year, year, `${description} year result`);
+ assert.sameValue(yearMonth.month, month, `${description} month result`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${description} referenceISODay result`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendars are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.ZonedDateTime, `${description} expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${description} time zone same value`);
+ assert.sameValue(actual.calendar, expected.calendar, `${description} calendar same value`);
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns @returnValue from its dateUntil() method,
+ * recording the call in @calls.
+ */
+ calendarDateUntilObservable(calls, returnValue) {
+ class CalendarDateUntilObservable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil() {
+ calls.push("call dateUntil");
+ return returnValue;
+ }
+ }
+
+ return new CalendarDateUntilObservable();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = Temporal.PlainDate.compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ }
+ };
+ // Automatically generate the other methods that don't need any custom code
+ ["toString", "dateUntil", "era", "eraYear", "year", "month", "monthCode", "day", "daysInMonth", "fields", "mergeFields"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString());
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor() {
+ return [];
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {};
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-non-integer.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-non-integer.js
new file mode 100644
index 0000000000..47db35da85
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-non-integer.js
@@ -0,0 +1,18 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.erayear
+description: RangeError thrown if time zone reports an offset that is not an integer number of nanoseconds
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[3600_000_000_000.5, NaN, -Infinity, Infinity].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(RangeError, () => datetime.eraYear);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-not-callable.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-not-callable.js
new file mode 100644
index 0000000000..cc19087ae5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-not-callable.js
@@ -0,0 +1,22 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.erayear
+description: TypeError thrown if timeZone.getOffsetNanosecondsFor is not callable
+features: [BigInt, Symbol, Temporal, arrow-function]
+---*/
+
+[undefined, null, true, Math.PI, 'string', Symbol('sym'), 42n, {}].forEach((notCallable) => {
+ const timeZone = new Temporal.TimeZone("UTC");
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ timeZone.getOffsetNanosecondsFor = notCallable;
+ assert.throws(
+ TypeError,
+ () => datetime.eraYear,
+ `Uncallable ${notCallable === null ? 'null' : typeof notCallable} getOffsetNanosecondsFor should throw TypeError`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-out-of-range.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-out-of-range.js
new file mode 100644
index 0000000000..690682e239
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-out-of-range.js
@@ -0,0 +1,18 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.erayear
+description: RangeError thrown if time zone reports an offset that is out of range
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[-86400_000_000_000, 86400_000_000_000].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(RangeError, () => datetime.eraYear);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-wrong-type.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-wrong-type.js
new file mode 100644
index 0000000000..7020148119
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-wrong-type.js
@@ -0,0 +1,27 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.erayear
+description: TypeError thrown if time zone reports an offset that is not a Number
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[
+ undefined,
+ null,
+ true,
+ "+01:00",
+ Symbol(),
+ 3600_000_000_000n,
+ {},
+ { valueOf() { return 3600_000_000_000; } },
+].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(TypeError, () => datetime.eraYear);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/validate-calendar-value.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/validate-calendar-value.js
new file mode 100644
index 0000000000..bb2d4cebb6
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/validate-calendar-value.js
@@ -0,0 +1,54 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2023 Richard Gibson. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.erayear
+description: Validate result returned from calendar eraYear() method
+features: [Temporal]
+---*/
+
+const badResults = [
+ [null, TypeError],
+ [false, TypeError],
+ [Infinity, RangeError],
+ [-Infinity, RangeError],
+ [NaN, RangeError],
+ [-0.1, RangeError],
+ ["string", TypeError],
+ [Symbol("foo"), TypeError],
+ [7n, TypeError],
+ [{}, TypeError],
+ [true, TypeError],
+ [7.1, RangeError],
+ ["7", TypeError],
+ ["7.5", TypeError],
+ [{valueOf() { return 7; }}, TypeError],
+];
+
+badResults.forEach(([result, error]) => {
+ const calendar = new class extends Temporal.Calendar {
+ eraYear() {
+ return result;
+ }
+ }("iso8601");
+ const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+ assert.throws(error, () => instance.eraYear, `${typeof result} ${String(result)} not converted to integer`);
+});
+
+const preservedResults = [
+ undefined,
+ -7,
+];
+
+preservedResults.forEach(result => {
+ const calendar = new class extends Temporal.Calendar {
+ eraYear() {
+ return result;
+ }
+ }("iso8601");
+ const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+ assert.sameValue(instance.eraYear, result, `${typeof result} ${String(result)} preserved`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/argument-propertybag-timezone-string-datetime.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/argument-propertybag-timezone-string-datetime.js
new file mode 100644
index 0000000000..1c87ef5ee6
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/argument-propertybag-timezone-string-datetime.js
@@ -0,0 +1,25 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: Conversion of ISO date-time strings to Temporal.TimeZone instances (with Intl time zones)
+features: [Temporal]
+---*/
+
+const expectedTimeZone = "America/Vancouver";
+const instance = new Temporal.ZonedDateTime(0n, expectedTimeZone);
+let timeZone = "2021-08-19T17:30[America/Vancouver]";
+instance.since({ year: 2020, month: 5, day: 2, timeZone });
+instance.since({ year: 2020, month: 5, day: 2, timeZone: { timeZone } });
+
+timeZone = "2021-08-19T17:30Z[America/Vancouver]";
+instance.since({ year: 2020, month: 5, day: 2, timeZone });
+instance.since({ year: 2020, month: 5, day: 2, timeZone: { timeZone } });
+
+timeZone = "2021-08-19T17:30-07:00[America/Vancouver]";
+instance.since({ year: 2020, month: 5, day: 2, timeZone });
+instance.since({ year: 2020, month: 5, day: 2, timeZone: { timeZone } });
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..c2d028b83d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.zoneddatetime.prototype.since
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", "gregory");
+const base = { era: "ad", month: 5, day: 2, hour: 15, timeZone: "UTC", calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.since({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.since({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/shell.js
new file mode 100644
index 0000000000..728dc1717d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/shell.js
@@ -0,0 +1,1990 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "number":
+ return `${objectName}[${propertyKey}]`;
+ default:
+ // TODO: check if propertyKey is an integer index.
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+}
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ assert(duration instanceof Temporal.Duration, `${description} instanceof`);
+ assert.sameValue(duration.years, years, `${description} years result`);
+ assert.sameValue(duration.months, months, `${description} months result`);
+ assert.sameValue(duration.weeks, weeks, `${description} weeks result`);
+ assert.sameValue(duration.days, days, `${description} days result`);
+ assert.sameValue(duration.hours, hours, `${description} hours result`);
+ assert.sameValue(duration.minutes, minutes, `${description} minutes result`);
+ assert.sameValue(duration.seconds, seconds, `${description} seconds result`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${description} milliseconds result`);
+ assert.sameValue(duration.microseconds, microseconds, `${description} microseconds result`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${description} nanoseconds result`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.Duration, `${description} expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.Instant, `${description} expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * result of date.calendar.toString().)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ assert(date instanceof Temporal.PlainDate, `${description} instanceof`);
+ assert.sameValue(date.era, era, `${description} era result`);
+ assert.sameValue(date.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(date.year, year, `${description} year result`);
+ assert.sameValue(date.month, month, `${description} month result`);
+ assert.sameValue(date.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(date.day, day, `${description} day result`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of datetime.calendar.toString().)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ assert(datetime instanceof Temporal.PlainDateTime, `${description} instanceof`);
+ assert.sameValue(datetime.era, era, `${description} era result`);
+ assert.sameValue(datetime.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(datetime.year, year, `${description} year result`);
+ assert.sameValue(datetime.month, month, `${description} month result`);
+ assert.sameValue(datetime.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(datetime.day, day, `${description} day result`);
+ assert.sameValue(datetime.hour, hour, `${description} hour result`);
+ assert.sameValue(datetime.minute, minute, `${description} minute result`);
+ assert.sameValue(datetime.second, second, `${description} second result`);
+ assert.sameValue(datetime.millisecond, millisecond, `${description} millisecond result`);
+ assert.sameValue(datetime.microsecond, microsecond, `${description} microsecond result`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${description} nanosecond result`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendars are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.PlainDateTime, `${description} expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ assert.sameValue(actual.calendar, expected.calendar, `${description} calendar same value`);
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of monthDay.calendar.toString().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${description} instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(monthDay.day, day, `${description} day result`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${description} referenceISOYear result`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ assert(time instanceof Temporal.PlainTime, `${description} instanceof`);
+ assert.sameValue(time.hour, hour, `${description} hour result`);
+ assert.sameValue(time.minute, minute, `${description} minute result`);
+ assert.sameValue(time.second, second, `${description} second result`);
+ assert.sameValue(time.millisecond, millisecond, `${description} millisecond result`);
+ assert.sameValue(time.microsecond, microsecond, `${description} microsecond result`);
+ assert.sameValue(time.nanosecond, nanosecond, `${description} nanosecond result`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.PlainTime, `${description} expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of yearMonth.calendar.toString().)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${description} instanceof`);
+ assert.sameValue(yearMonth.era, era, `${description} era result`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(yearMonth.year, year, `${description} year result`);
+ assert.sameValue(yearMonth.month, month, `${description} month result`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${description} referenceISODay result`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendars are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.ZonedDateTime, `${description} expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${description} time zone same value`);
+ assert.sameValue(actual.calendar, expected.calendar, `${description} calendar same value`);
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns @returnValue from its dateUntil() method,
+ * recording the call in @calls.
+ */
+ calendarDateUntilObservable(calls, returnValue) {
+ class CalendarDateUntilObservable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil() {
+ calls.push("call dateUntil");
+ return returnValue;
+ }
+ }
+
+ return new CalendarDateUntilObservable();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = Temporal.PlainDate.compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ }
+ };
+ // Automatically generate the other methods that don't need any custom code
+ ["toString", "dateUntil", "era", "eraYear", "year", "month", "monthCode", "day", "daysInMonth", "fields", "mergeFields"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString());
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor() {
+ return [];
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {};
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/locales-undefined.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/locales-undefined.js
new file mode 100644
index 0000000000..27f0e248c9
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/locales-undefined.js
@@ -0,0 +1,21 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.tolocalestring
+description: Omitting the locales argument defaults to the DateTimeFormat default
+features: [BigInt, Temporal]
+---*/
+
+const datetime = new Temporal.ZonedDateTime(957270896_987_650_000n, "UTC");
+const defaultFormatter = new Intl.DateTimeFormat([], Object.create(null));
+const expected = defaultFormatter.format(datetime);
+
+const actualExplicit = datetime.toLocaleString(undefined);
+assert.sameValue(actualExplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+const actualImplicit = datetime.toLocaleString();
+assert.sameValue(actualImplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-conflict.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-conflict.js
new file mode 100644
index 0000000000..3eda971d0d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-conflict.js
@@ -0,0 +1,51 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Kate Miháliková. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sup-temporal.zoneddatetime.prototype.tolocalestring
+description: >
+ Conflicting properties of dateStyle must be rejected with a TypeError for the options argument
+info: |
+ Using sec-temporal-getdatetimeformatpattern:
+ GetDateTimeFormatPattern ( dateStyle, timeStyle, matcher, opt, dataLocaleData, hc )
+
+ 1. If dateStyle is not undefined or timeStyle is not undefined, then
+ a. For each row in Table 7, except the header row, do
+ i. Let prop be the name given in the Property column of the row.
+ ii. Let p be opt.[[<prop>]].
+ iii. If p is not undefined, then
+ 1. Throw a TypeError exception.
+features: [BigInt, Temporal]
+---*/
+
+// Table 14 - Supported fields + example value for each field
+const conflictingOptions = [
+ [ "weekday", "short" ],
+ [ "era", "short" ],
+ [ "year", "numeric" ],
+ [ "month", "numeric" ],
+ [ "day", "numeric" ],
+ [ "hour", "numeric" ],
+ [ "minute", "numeric" ],
+ [ "second", "numeric" ],
+ [ "dayPeriod", "short" ],
+ [ "fractionalSecondDigits", 3 ],
+ [ "timeZoneName", "short" ],
+];
+const datetime = new Temporal.ZonedDateTime(957270896_987_650_000n, "UTC");
+
+assert.sameValue(typeof datetime.toLocaleString("en", { dateStyle: "short" }), "string");
+assert.sameValue(typeof datetime.toLocaleString("en", { timeStyle: "short" }), "string");
+
+for (const [ option, value ] of conflictingOptions) {
+ assert.throws(TypeError, function() {
+ datetime.toLocaleString("en", { [option]: value, dateStyle: "short" });
+ }, `datetime.toLocaleString("en", { ${option}: "${value}", dateStyle: "short" }) throws TypeError`);
+
+ assert.throws(TypeError, function() {
+ datetime.toLocaleString("en", { [option]: value, timeStyle: "short" });
+ }, `datetime.toLocaleString("en", { ${option}: "${value}", timeStyle: "short" }) throws TypeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-undefined.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-undefined.js
new file mode 100644
index 0000000000..557205cfc4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-undefined.js
@@ -0,0 +1,21 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.tolocalestring
+description: Verify that undefined options are handled correctly.
+features: [BigInt, Temporal]
+---*/
+
+const datetime = new Temporal.ZonedDateTime(957270896_987_650_000n, "UTC");
+const defaultFormatter = new Intl.DateTimeFormat('en', Object.create(null));
+const expected = defaultFormatter.format(datetime);
+
+const actualExplicit = datetime.toLocaleString('en', undefined);
+assert.sameValue(actualExplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+const actualImplicit = datetime.toLocaleString('en');
+assert.sameValue(actualImplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/argument-propertybag-timezone-string-datetime.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/argument-propertybag-timezone-string-datetime.js
new file mode 100644
index 0000000000..47ed816e28
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/argument-propertybag-timezone-string-datetime.js
@@ -0,0 +1,25 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.until
+description: Conversion of ISO date-time strings to Temporal.TimeZone instances (with Intl time zones)
+features: [Temporal]
+---*/
+
+const expectedTimeZone = "America/Vancouver";
+const instance = new Temporal.ZonedDateTime(0n, expectedTimeZone);
+let timeZone = "2021-08-19T17:30[America/Vancouver]";
+instance.until({ year: 2020, month: 5, day: 2, timeZone });
+instance.until({ year: 2020, month: 5, day: 2, timeZone: { timeZone } });
+
+timeZone = "2021-08-19T17:30Z[America/Vancouver]";
+instance.until({ year: 2020, month: 5, day: 2, timeZone });
+instance.until({ year: 2020, month: 5, day: 2, timeZone: { timeZone } });
+
+timeZone = "2021-08-19T17:30-07:00[America/Vancouver]";
+instance.until({ year: 2020, month: 5, day: 2, timeZone });
+instance.until({ year: 2020, month: 5, day: 2, timeZone: { timeZone } });
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..06379383ee
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.zoneddatetime.prototype.until
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", "gregory");
+const base = { era: "ad", month: 5, day: 2, hour: 15, timeZone: "UTC", calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.until({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.until({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/shell.js
new file mode 100644
index 0000000000..728dc1717d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/shell.js
@@ -0,0 +1,1990 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "number":
+ return `${objectName}[${propertyKey}]`;
+ default:
+ // TODO: check if propertyKey is an integer index.
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+}
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ assert(duration instanceof Temporal.Duration, `${description} instanceof`);
+ assert.sameValue(duration.years, years, `${description} years result`);
+ assert.sameValue(duration.months, months, `${description} months result`);
+ assert.sameValue(duration.weeks, weeks, `${description} weeks result`);
+ assert.sameValue(duration.days, days, `${description} days result`);
+ assert.sameValue(duration.hours, hours, `${description} hours result`);
+ assert.sameValue(duration.minutes, minutes, `${description} minutes result`);
+ assert.sameValue(duration.seconds, seconds, `${description} seconds result`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${description} milliseconds result`);
+ assert.sameValue(duration.microseconds, microseconds, `${description} microseconds result`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${description} nanoseconds result`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.Duration, `${description} expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.Instant, `${description} expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * result of date.calendar.toString().)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ assert(date instanceof Temporal.PlainDate, `${description} instanceof`);
+ assert.sameValue(date.era, era, `${description} era result`);
+ assert.sameValue(date.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(date.year, year, `${description} year result`);
+ assert.sameValue(date.month, month, `${description} month result`);
+ assert.sameValue(date.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(date.day, day, `${description} day result`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of datetime.calendar.toString().)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ assert(datetime instanceof Temporal.PlainDateTime, `${description} instanceof`);
+ assert.sameValue(datetime.era, era, `${description} era result`);
+ assert.sameValue(datetime.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(datetime.year, year, `${description} year result`);
+ assert.sameValue(datetime.month, month, `${description} month result`);
+ assert.sameValue(datetime.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(datetime.day, day, `${description} day result`);
+ assert.sameValue(datetime.hour, hour, `${description} hour result`);
+ assert.sameValue(datetime.minute, minute, `${description} minute result`);
+ assert.sameValue(datetime.second, second, `${description} second result`);
+ assert.sameValue(datetime.millisecond, millisecond, `${description} millisecond result`);
+ assert.sameValue(datetime.microsecond, microsecond, `${description} microsecond result`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${description} nanosecond result`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendars are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.PlainDateTime, `${description} expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ assert.sameValue(actual.calendar, expected.calendar, `${description} calendar same value`);
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of monthDay.calendar.toString().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${description} instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(monthDay.day, day, `${description} day result`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${description} referenceISOYear result`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ assert(time instanceof Temporal.PlainTime, `${description} instanceof`);
+ assert.sameValue(time.hour, hour, `${description} hour result`);
+ assert.sameValue(time.minute, minute, `${description} minute result`);
+ assert.sameValue(time.second, second, `${description} second result`);
+ assert.sameValue(time.millisecond, millisecond, `${description} millisecond result`);
+ assert.sameValue(time.microsecond, microsecond, `${description} microsecond result`);
+ assert.sameValue(time.nanosecond, nanosecond, `${description} nanosecond result`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.PlainTime, `${description} expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of yearMonth.calendar.toString().)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${description} instanceof`);
+ assert.sameValue(yearMonth.era, era, `${description} era result`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(yearMonth.year, year, `${description} year result`);
+ assert.sameValue(yearMonth.month, month, `${description} month result`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${description} referenceISODay result`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendars are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.ZonedDateTime, `${description} expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${description} time zone same value`);
+ assert.sameValue(actual.calendar, expected.calendar, `${description} calendar same value`);
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns @returnValue from its dateUntil() method,
+ * recording the call in @calls.
+ */
+ calendarDateUntilObservable(calls, returnValue) {
+ class CalendarDateUntilObservable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil() {
+ calls.push("call dateUntil");
+ return returnValue;
+ }
+ }
+
+ return new CalendarDateUntilObservable();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = Temporal.PlainDate.compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ }
+ };
+ // Automatically generate the other methods that don't need any custom code
+ ["toString", "dateUntil", "era", "eraYear", "year", "month", "monthCode", "day", "daysInMonth", "fields", "mergeFields"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString());
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor() {
+ return [];
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {};
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/calendar-case-insensitive.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/calendar-case-insensitive.js
new file mode 100644
index 0000000000..f87e625ba6
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/calendar-case-insensitive.js
@@ -0,0 +1,17 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.withcalendar
+description: Calendar names are case-insensitive
+features: [Temporal]
+---*/
+
+const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", { id: "replace-me" });
+
+const arg = "jApAnEsE";;
+const result = instance.withCalendar(arg);
+assert.sameValue(result.calendar.id, "japanese", "Calendar is case-insensitive");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..f0e58e5961
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip -- Temporal is not supported
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.zoneddatetime.prototype.withplaindate
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", "gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.withPlainDate({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.withPlainDate({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/shell.js
new file mode 100644
index 0000000000..728dc1717d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/shell.js
@@ -0,0 +1,1990 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "number":
+ return `${objectName}[${propertyKey}]`;
+ default:
+ // TODO: check if propertyKey is an integer index.
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+}
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ assert(duration instanceof Temporal.Duration, `${description} instanceof`);
+ assert.sameValue(duration.years, years, `${description} years result`);
+ assert.sameValue(duration.months, months, `${description} months result`);
+ assert.sameValue(duration.weeks, weeks, `${description} weeks result`);
+ assert.sameValue(duration.days, days, `${description} days result`);
+ assert.sameValue(duration.hours, hours, `${description} hours result`);
+ assert.sameValue(duration.minutes, minutes, `${description} minutes result`);
+ assert.sameValue(duration.seconds, seconds, `${description} seconds result`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${description} milliseconds result`);
+ assert.sameValue(duration.microseconds, microseconds, `${description} microseconds result`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${description} nanoseconds result`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.Duration, `${description} expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.Instant, `${description} expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * result of date.calendar.toString().)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ assert(date instanceof Temporal.PlainDate, `${description} instanceof`);
+ assert.sameValue(date.era, era, `${description} era result`);
+ assert.sameValue(date.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(date.year, year, `${description} year result`);
+ assert.sameValue(date.month, month, `${description} month result`);
+ assert.sameValue(date.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(date.day, day, `${description} day result`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of datetime.calendar.toString().)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ assert(datetime instanceof Temporal.PlainDateTime, `${description} instanceof`);
+ assert.sameValue(datetime.era, era, `${description} era result`);
+ assert.sameValue(datetime.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(datetime.year, year, `${description} year result`);
+ assert.sameValue(datetime.month, month, `${description} month result`);
+ assert.sameValue(datetime.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(datetime.day, day, `${description} day result`);
+ assert.sameValue(datetime.hour, hour, `${description} hour result`);
+ assert.sameValue(datetime.minute, minute, `${description} minute result`);
+ assert.sameValue(datetime.second, second, `${description} second result`);
+ assert.sameValue(datetime.millisecond, millisecond, `${description} millisecond result`);
+ assert.sameValue(datetime.microsecond, microsecond, `${description} microsecond result`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${description} nanosecond result`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendars are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.PlainDateTime, `${description} expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ assert.sameValue(actual.calendar, expected.calendar, `${description} calendar same value`);
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of monthDay.calendar.toString().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${description} instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(monthDay.day, day, `${description} day result`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${description} referenceISOYear result`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ assert(time instanceof Temporal.PlainTime, `${description} instanceof`);
+ assert.sameValue(time.hour, hour, `${description} hour result`);
+ assert.sameValue(time.minute, minute, `${description} minute result`);
+ assert.sameValue(time.second, second, `${description} second result`);
+ assert.sameValue(time.millisecond, millisecond, `${description} millisecond result`);
+ assert.sameValue(time.microsecond, microsecond, `${description} microsecond result`);
+ assert.sameValue(time.nanosecond, nanosecond, `${description} nanosecond result`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.PlainTime, `${description} expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the result of yearMonth.calendar.toString().)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${description} instanceof`);
+ assert.sameValue(yearMonth.era, era, `${description} era result`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${description} eraYear result`);
+ assert.sameValue(yearMonth.year, year, `${description} year result`);
+ assert.sameValue(yearMonth.month, month, `${description} month result`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${description} monthCode result`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${description} referenceISODay result`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendars are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ assert(expected instanceof Temporal.ZonedDateTime, `${description} expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${description} instanceof`);
+ assert(actual.equals(expected), `${description} equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${description} time zone same value`);
+ assert.sameValue(actual.calendar, expected.calendar, `${description} calendar same value`);
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns @returnValue from its dateUntil() method,
+ * recording the call in @calls.
+ */
+ calendarDateUntilObservable(calls, returnValue) {
+ class CalendarDateUntilObservable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil() {
+ calls.push("call dateUntil");
+ return returnValue;
+ }
+ }
+
+ return new CalendarDateUntilObservable();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = Temporal.PlainDate.compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ // Remove the HasProperty check resulting from the above constructor call
+ assert.sameValue(calls.pop(), `has ${objectName}.calendar`);
+ return result;
+ }
+ };
+ // Automatically generate the other methods that don't need any custom code
+ ["toString", "dateUntil", "era", "eraYear", "year", "month", "monthCode", "day", "daysInMonth", "fields", "mergeFields"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString());
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor() {
+ return [];
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {};
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/shell.js