text: // 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]
  ---*/
  
  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]]):
     *
     * 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) {
      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`);
    },
  
    /*
     * 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, actual.length); // empty it for the next check
      });
    },
  
    /*
     * checkFractionalSecondDigitsOptionWrongType(temporalObject):
     *
     * Checks the string-or-number type handling of the fractionalSecondDigits
     * option to the various types' toString() methods. temporalObject is an
     * instance of the Temporal type under test.
     */
    checkFractionalSecondDigitsOptionWrongType(temporalObject) {
      // null is not a number, and converts to the string "null", which is an invalid string value
      assert.throws(RangeError, () => temporalObject.toString({ fractionalSecondDigits: null }), "null");
      // Booleans are not numbers, and convert to the strings "true" or "false", which are invalid
      assert.throws(RangeError, () => temporalObject.toString({ fractionalSecondDigits: true }), "true");
      assert.throws(RangeError, () => temporalObject.toString({ fractionalSecondDigits: false }), "false");
      // Symbols are not numbers and cannot convert to strings
      assert.throws(TypeError, () => temporalObject.toString({ fractionalSecondDigits: Symbol() }), "symbol");
      // BigInts are not numbers and convert to strings which are invalid
      assert.throws(RangeError, () => temporalObject.toString({ fractionalSecondDigits: 2n }), "bigint");
  
      // Objects are not numbers and prefer their toString() methods when converting to a string
      assert.throws(RangeError, () => temporalObject.toString({ fractionalSecondDigits: {} }), "plain object");
  
      const toStringExpected = temporalObject.toString({ fractionalSecondDigits: 'auto' });
      const expected = [
        "get fractionalSecondDigits.toString",
        "call fractionalSecondDigits.toString",
      ];
      const actual = [];
      const observer = TemporalHelpers.toPrimitiveObserver(actual, "auto", "fractionalSecondDigits");
      const result = temporalObject.toString({ fractionalSecondDigits: observer });
      assert.sameValue(result, toStringExpected, "object with toString");
      assert.compareArray(actual, expected, "order of operations");
    },
  
    /*
     * 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) {
      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 ${property}`);
            const value = prototypeDescrs[property].get.call(this);
            return {
              toString() {
                actual.push(`toString ${property}`);
                return value.toString();
              },
              valueOf() {
                actual.push(`valueOf ${property}`);
                return value;
              },
            };
          },
        });
      });
      Object.defineProperty(datetime, "calendar", {
        get() {
          actual.push("get calendar");
          return calendar;
        },
      });
  
      func(datetime, calendar);
      assert.compareArray(actual, expected, "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 ${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 ${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 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 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);
    },
  
    /*
     * 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) {
      let displayName = propertyName;
      if (typeof propertyName === 'symbol') {
        if (Symbol.keyFor(propertyName) !== undefined) {
          displayName = `[Symbol.for('${Symbol.keyFor(propertyName)}')]`;
        } else if (propertyName.description.startsWith('Symbol.')) {
          displayName = `[${propertyName.description}]`;
        } else {
          displayName = `[Symbol('${propertyName.description}')]`
        }
      }
      Object.defineProperty(object, propertyName, {
        get() {
          calls.push(`get ${displayName}`);
          return value;
        },
        set(v) {
          calls.push(`set ${displayName}`);
        }
      });
    },
  
    /*
     * 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.add(this._shift)];
          }
          if (instant.epochNanoseconds < this._epoch2) return [instant];
          const shifted = instant.add(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);
    },
  
    /*
     * 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();
    },
  
    /*
     * 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();
          };
        },
      };
    },
  };
  