| // 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, 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, actual.length); // 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 ${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, `${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 ${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 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); |
| }, |
| |
| /* |
| * 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.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); |
| }, |
| |
| /* |
| * 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(); |
| }; |
| }, |
| }; |
| }, |
| }; |