1 // GENERATED, DO NOT EDIT
2 // file: temporalHelpers.js
3 // Copyright (C) 2021 Igalia, S.L. All rights reserved.
4 // This code is governed by the BSD license found in the LICENSE file.
7 This defines helper objects and functions for testing Temporal.
8 defines: [TemporalHelpers]
9 features: [Symbol.species, Symbol.iterator, Temporal]
12 const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
14 function formatPropertyName(propertyKey, objectName = "") {
15 switch (typeof propertyKey) {
17 if (Symbol.keyFor(propertyKey) !== undefined) {
18 return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
19 } else if (propertyKey.description.startsWith('Symbol.')) {
20 return `${objectName}[${propertyKey.description}]`;
22 return `${objectName}[Symbol('${propertyKey.description}')]`
25 if (propertyKey !== String(Number(propertyKey))) {
26 if (ASCII_IDENTIFIER.test(propertyKey)) {
27 return objectName ? `${objectName}.${propertyKey}` : propertyKey;
29 return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
33 // integer or string integer-index
34 return `${objectName}[${propertyKey}]`;
38 const SKIP_SYMBOL = Symbol("Skip");
40 var TemporalHelpers = {
42 * Codes and maximum lengths of months in the ISO 8601 calendar.
45 { month: 1, monthCode: "M01", daysInMonth: 31 },
46 { month: 2, monthCode: "M02", daysInMonth: 29 },
47 { month: 3, monthCode: "M03", daysInMonth: 31 },
48 { month: 4, monthCode: "M04", daysInMonth: 30 },
49 { month: 5, monthCode: "M05", daysInMonth: 31 },
50 { month: 6, monthCode: "M06", daysInMonth: 30 },
51 { month: 7, monthCode: "M07", daysInMonth: 31 },
52 { month: 8, monthCode: "M08", daysInMonth: 31 },
53 { month: 9, monthCode: "M09", daysInMonth: 30 },
54 { month: 10, monthCode: "M10", daysInMonth: 31 },
55 { month: 11, monthCode: "M11", daysInMonth: 30 },
56 { month: 12, monthCode: "M12", daysInMonth: 31 }
60 * assertDuration(duration, years, ..., nanoseconds[, description]):
62 * Shorthand for asserting that each field of a Temporal.Duration is equal to
65 assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
66 assert(duration instanceof Temporal.Duration, `${description} instanceof`);
67 assert.sameValue(duration.years, years, `${description} years result`);
68 assert.sameValue(duration.months, months, `${description} months result`);
69 assert.sameValue(duration.weeks, weeks, `${description} weeks result`);
70 assert.sameValue(duration.days, days, `${description} days result`);
71 assert.sameValue(duration.hours, hours, `${description} hours result`);
72 assert.sameValue(duration.minutes, minutes, `${description} minutes result`);
73 assert.sameValue(duration.seconds, seconds, `${description} seconds result`);
74 assert.sameValue(duration.milliseconds, milliseconds, `${description} milliseconds result`);
75 assert.sameValue(duration.microseconds, microseconds, `${description} microseconds result`);
76 assert.sameValue(duration.nanoseconds, nanoseconds, `${description} nanoseconds result`);
80 * assertDateDuration(duration, years, months, weeks, days, [, description]):
82 * Shorthand for asserting that each date field of a Temporal.Duration is
83 * equal to an expected value.
85 assertDateDuration(duration, years, months, weeks, days, description = "") {
86 assert(duration instanceof Temporal.Duration, `${description} instanceof`);
87 assert.sameValue(duration.years, years, `${description} years result`);
88 assert.sameValue(duration.months, months, `${description} months result`);
89 assert.sameValue(duration.weeks, weeks, `${description} weeks result`);
90 assert.sameValue(duration.days, days, `${description} days result`);
91 assert.sameValue(duration.hours, 0, `${description} hours result should be zero`);
92 assert.sameValue(duration.minutes, 0, `${description} minutes result should be zero`);
93 assert.sameValue(duration.seconds, 0, `${description} seconds result should be zero`);
94 assert.sameValue(duration.milliseconds, 0, `${description} milliseconds result should be zero`);
95 assert.sameValue(duration.microseconds, 0, `${description} microseconds result should be zero`);
96 assert.sameValue(duration.nanoseconds, 0, `${description} nanoseconds result should be zero`);
100 * assertDurationsEqual(actual, expected[, description]):
102 * Shorthand for asserting that each field of a Temporal.Duration is equal to
103 * the corresponding field in another Temporal.Duration.
105 assertDurationsEqual(actual, expected, description = "") {
106 assert(expected instanceof Temporal.Duration, `${description} expected value should be a Temporal.Duration`);
107 TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
111 * assertInstantsEqual(actual, expected[, description]):
113 * Shorthand for asserting that two Temporal.Instants are of the correct type
114 * and equal according to their equals() methods.
116 assertInstantsEqual(actual, expected, description = "") {
117 assert(expected instanceof Temporal.Instant, `${description} expected value should be a Temporal.Instant`);
118 assert(actual instanceof Temporal.Instant, `${description} instanceof`);
119 assert(actual.equals(expected), `${description} equals method`);
123 * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
125 * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
126 * an expected value. (Except the `calendar` property, since callers may want
127 * to assert either object equality with an object they put in there, or the
128 * value of date.calendarId.)
130 assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
131 assert(date instanceof Temporal.PlainDate, `${description} instanceof`);
132 assert.sameValue(date.era, era, `${description} era result`);
133 assert.sameValue(date.eraYear, eraYear, `${description} eraYear result`);
134 assert.sameValue(date.year, year, `${description} year result`);
135 assert.sameValue(date.month, month, `${description} month result`);
136 assert.sameValue(date.monthCode, monthCode, `${description} monthCode result`);
137 assert.sameValue(date.day, day, `${description} day result`);
141 * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
143 * Shorthand for asserting that each field of a Temporal.PlainDateTime is
144 * equal to an expected value. (Except the `calendar` property, since callers
145 * may want to assert either object equality with an object they put in there,
146 * or the value of datetime.calendarId.)
148 assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
149 assert(datetime instanceof Temporal.PlainDateTime, `${description} instanceof`);
150 assert.sameValue(datetime.era, era, `${description} era result`);
151 assert.sameValue(datetime.eraYear, eraYear, `${description} eraYear result`);
152 assert.sameValue(datetime.year, year, `${description} year result`);
153 assert.sameValue(datetime.month, month, `${description} month result`);
154 assert.sameValue(datetime.monthCode, monthCode, `${description} monthCode result`);
155 assert.sameValue(datetime.day, day, `${description} day result`);
156 assert.sameValue(datetime.hour, hour, `${description} hour result`);
157 assert.sameValue(datetime.minute, minute, `${description} minute result`);
158 assert.sameValue(datetime.second, second, `${description} second result`);
159 assert.sameValue(datetime.millisecond, millisecond, `${description} millisecond result`);
160 assert.sameValue(datetime.microsecond, microsecond, `${description} microsecond result`);
161 assert.sameValue(datetime.nanosecond, nanosecond, `${description} nanosecond result`);
165 * assertPlainDateTimesEqual(actual, expected[, description]):
167 * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
168 * type, equal according to their equals() methods, and additionally that
169 * their calendar internal slots are the same value.
171 assertPlainDateTimesEqual(actual, expected, description = "") {
172 assert(expected instanceof Temporal.PlainDateTime, `${description} expected value should be a Temporal.PlainDateTime`);
173 assert(actual instanceof Temporal.PlainDateTime, `${description} instanceof`);
174 assert(actual.equals(expected), `${description} equals method`);
176 actual.getISOFields().calendar,
177 expected.getISOFields().calendar,
178 `${description} calendar same value`
183 * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
185 * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
186 * equal to an expected value. (Except the `calendar` property, since callers
187 * may want to assert either object equality with an object they put in there,
188 * or the value of monthDay.calendarId().)
190 assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
191 assert(monthDay instanceof Temporal.PlainMonthDay, `${description} instanceof`);
192 assert.sameValue(monthDay.monthCode, monthCode, `${description} monthCode result`);
193 assert.sameValue(monthDay.day, day, `${description} day result`);
194 assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${description} referenceISOYear result`);
198 * assertPlainTime(time, hour, ..., nanosecond[, description]):
200 * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
203 assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
204 assert(time instanceof Temporal.PlainTime, `${description} instanceof`);
205 assert.sameValue(time.hour, hour, `${description} hour result`);
206 assert.sameValue(time.minute, minute, `${description} minute result`);
207 assert.sameValue(time.second, second, `${description} second result`);
208 assert.sameValue(time.millisecond, millisecond, `${description} millisecond result`);
209 assert.sameValue(time.microsecond, microsecond, `${description} microsecond result`);
210 assert.sameValue(time.nanosecond, nanosecond, `${description} nanosecond result`);
214 * assertPlainTimesEqual(actual, expected[, description]):
216 * Shorthand for asserting that two Temporal.PlainTimes are of the correct
217 * type and equal according to their equals() methods.
219 assertPlainTimesEqual(actual, expected, description = "") {
220 assert(expected instanceof Temporal.PlainTime, `${description} expected value should be a Temporal.PlainTime`);
221 assert(actual instanceof Temporal.PlainTime, `${description} instanceof`);
222 assert(actual.equals(expected), `${description} equals method`);
226 * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
228 * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
229 * equal to an expected value. (Except the `calendar` property, since callers
230 * may want to assert either object equality with an object they put in there,
231 * or the value of yearMonth.calendarId.)
233 assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
234 assert(yearMonth instanceof Temporal.PlainYearMonth, `${description} instanceof`);
235 assert.sameValue(yearMonth.era, era, `${description} era result`);
236 assert.sameValue(yearMonth.eraYear, eraYear, `${description} eraYear result`);
237 assert.sameValue(yearMonth.year, year, `${description} year result`);
238 assert.sameValue(yearMonth.month, month, `${description} month result`);
239 assert.sameValue(yearMonth.monthCode, monthCode, `${description} monthCode result`);
240 assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${description} referenceISODay result`);
244 * assertZonedDateTimesEqual(actual, expected[, description]):
246 * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
247 * type, equal according to their equals() methods, and additionally that
248 * their time zones and calendar internal slots are the same value.
250 assertZonedDateTimesEqual(actual, expected, description = "") {
251 assert(expected instanceof Temporal.ZonedDateTime, `${description} expected value should be a Temporal.ZonedDateTime`);
252 assert(actual instanceof Temporal.ZonedDateTime, `${description} instanceof`);
253 assert(actual.equals(expected), `${description} equals method`);
254 assert.sameValue(actual.timeZone, expected.timeZone, `${description} time zone same value`);
256 actual.getISOFields().calendar,
257 expected.getISOFields().calendar,
258 `${description} calendar same value`
263 * assertUnreachable(description):
265 * Helper for asserting that code is not executed. This is useful for
266 * assertions that methods of user calendars and time zones are not called.
268 assertUnreachable(description) {
269 let message = "This code should not be executed";
271 message = `${message}: ${description}`;
273 throw new Test262Error(message);
277 * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
279 * When an options object with a largestUnit property is synthesized inside
280 * Temporal and passed to user code such as calendar.dateUntil(), the value of
281 * the largestUnit property should be in the singular form, even if the input
282 * was given in the plural form.
283 * (This doesn't apply when the options object is passed through verbatim.)
285 * func(calendar, largestUnit, index) is the operation under test. It's called
286 * with an instance of a calendar that keeps track of which largestUnit is
287 * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
288 * the key's numerical index in case the function needs to generate test data
289 * based on the index. At the end, the actual values passed to dateUntil() are
290 * compared with the array values of expectedLargestUnitCalls.
292 checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
295 class DateUntilOptionsCalendar extends Temporal.Calendar {
300 dateUntil(earlier, later, options) {
301 actual.push(options.largestUnit);
302 return super.dateUntil(earlier, later, options);
306 return "date-until-options";
310 const calendar = new DateUntilOptionsCalendar();
311 Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
312 func(calendar, largestUnit, index);
313 assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
314 actual.splice(0); // empty it for the next check
319 * checkPlainDateTimeConversionFastPath(func):
321 * ToTemporalDate and ToTemporalTime should both, if given a
322 * Temporal.PlainDateTime instance, convert to the desired type by reading the
323 * PlainDateTime's internal slots, rather than calling any getters.
325 * func(datetime, calendar) is the actual operation to test, that must
326 * internally call the abstract operation ToTemporalDate or ToTemporalTime.
327 * It is passed a Temporal.PlainDateTime instance, as well as the instance's
328 * calendar object (so that it doesn't have to call the calendar getter itself
329 * if it wants to make any assertions about the calendar.)
331 checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
335 const calendar = new Temporal.Calendar("iso8601");
336 const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
337 const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
338 ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
339 Object.defineProperty(datetime, property, {
341 actual.push(`get ${formatPropertyName(property)}`);
342 const value = prototypeDescrs[property].get.call(this);
345 actual.push(`toString ${formatPropertyName(property)}`);
346 return value.toString();
349 actual.push(`valueOf ${formatPropertyName(property)}`);
356 Object.defineProperty(datetime, "calendar", {
358 actual.push("get calendar");
363 func(datetime, calendar);
364 assert.compareArray(actual, expected, `${message}: property getters not called`);
368 * Check that an options bag that accepts units written in the singular form,
369 * also accepts the same units written in the plural form.
370 * func(unit) should call the method with the appropriate options bag
371 * containing unit as a value. This will be called twice for each element of
372 * validSingularUnits, once with singular and once with plural, and the
373 * results of each pair should be the same (whether a Temporal object or a
376 checkPluralUnitsAccepted(func, validSingularUnits) {
385 millisecond: 'milliseconds',
386 microsecond: 'microseconds',
387 nanosecond: 'nanoseconds',
390 validSingularUnits.forEach((unit) => {
391 const singularValue = func(unit);
392 const pluralValue = func(plurals[unit]);
393 const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
394 if (singularValue instanceof Temporal.Duration) {
395 TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
396 } else if (singularValue instanceof Temporal.Instant) {
397 TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
398 } else if (singularValue instanceof Temporal.PlainDateTime) {
399 TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
400 } else if (singularValue instanceof Temporal.PlainTime) {
401 TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
402 } else if (singularValue instanceof Temporal.ZonedDateTime) {
403 TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
405 assert.sameValue(pluralValue, singularValue);
411 * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
413 * Checks the type handling of the roundingIncrement option.
414 * checkFunc(roundingIncrement) is a function which takes the value of
415 * roundingIncrement to test, and calls the method under test with it,
416 * returning the result. assertTrueResultFunc(result, description) should
417 * assert that result is the expected result with roundingIncrement: true, and
418 * assertObjectResultFunc(result, description) should assert that result is
419 * the expected result with roundingIncrement being an object with a valueOf()
422 checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
423 // null converts to 0, which is out of range
424 assert.throws(RangeError, () => checkFunc(null), "null");
425 // Booleans convert to either 0 or 1, and 1 is allowed
426 const trueResult = checkFunc(true);
427 assertTrueResultFunc(trueResult, "true");
428 assert.throws(RangeError, () => checkFunc(false), "false");
429 // Symbols and BigInts cannot convert to numbers
430 assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
431 assert.throws(TypeError, () => checkFunc(2n), "bigint");
433 // Objects prefer their valueOf() methods when converting to a number
434 assert.throws(RangeError, () => checkFunc({}), "plain object");
437 "get roundingIncrement.valueOf",
438 "call roundingIncrement.valueOf",
441 const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
442 const objectResult = checkFunc(observer);
443 assertObjectResultFunc(objectResult, "object with valueOf");
444 assert.compareArray(actual, expected, "order of operations");
448 * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
450 * Checks the type handling of a string option, of which there are several in
452 * propertyName is the name of the option, and value is the value that
453 * assertFunc should expect it to have.
454 * checkFunc(value) is a function which takes the value of the option to test,
455 * and calls the method under test with it, returning the result.
456 * assertFunc(result, description) should assert that result is the expected
457 * result with the option value being an object with a toString() method
458 * which returns the given value.
460 checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
461 // null converts to the string "null", which is an invalid string value
462 assert.throws(RangeError, () => checkFunc(null), "null");
463 // Booleans convert to the strings "true" or "false", which are invalid
464 assert.throws(RangeError, () => checkFunc(true), "true");
465 assert.throws(RangeError, () => checkFunc(false), "false");
466 // Symbols cannot convert to strings
467 assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
468 // Numbers convert to strings which are invalid
469 assert.throws(RangeError, () => checkFunc(2), "number");
470 // BigInts convert to strings which are invalid
471 assert.throws(RangeError, () => checkFunc(2n), "bigint");
473 // Objects prefer their toString() methods when converting to a string
474 assert.throws(RangeError, () => checkFunc({}), "plain object");
477 `get ${propertyName}.toString`,
478 `call ${propertyName}.toString`,
481 const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
482 const result = checkFunc(observer);
483 assertFunc(result, "object with toString");
484 assert.compareArray(actual, expected, "order of operations");
488 * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
491 * Methods of Temporal classes that return a new instance of the same class,
492 * must not take the constructor of a subclass into account, nor the @@species
493 * property. This helper runs tests to ensure this.
495 * construct(...constructArgs) must yield a valid instance of the Temporal
496 * class. instance[method](...methodArgs) is the method call under test, which
497 * must also yield a valid instance of the same Temporal class, not a
498 * subclass. See below for the individual tests that this runs.
499 * resultAssertions() is a function that performs additional assertions on the
500 * instance returned by the method under test.
502 checkSubclassingIgnored(...args) {
503 this.checkSubclassConstructorNotObject(...args);
504 this.checkSubclassConstructorUndefined(...args);
505 this.checkSubclassConstructorThrows(...args);
506 this.checkSubclassConstructorNotCalled(...args);
507 this.checkSubclassSpeciesInvalidResult(...args);
508 this.checkSubclassSpeciesNotAConstructor(...args);
509 this.checkSubclassSpeciesNull(...args);
510 this.checkSubclassSpeciesUndefined(...args);
511 this.checkSubclassSpeciesThrows(...args);
515 * Checks that replacing the 'constructor' property of the instance with
516 * various primitive values does not affect the returned new instance.
518 checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
519 function check(value, description) {
520 const instance = new construct(...constructArgs);
521 instance.constructor = value;
522 const result = instance[method](...methodArgs);
523 assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
524 resultAssertions(result);
529 check("test", "string");
530 check(Symbol(), "Symbol");
536 * Checks that replacing the 'constructor' property of the subclass with
537 * undefined does not affect the returned new instance.
539 checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
542 class MySubclass extends construct {
545 super(...constructArgs);
549 const instance = new MySubclass();
550 assert.sameValue(called, 1);
552 MySubclass.prototype.constructor = undefined;
554 const result = instance[method](...methodArgs);
555 assert.sameValue(called, 1);
556 assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
557 resultAssertions(result);
561 * Checks that making the 'constructor' property of the instance throw when
562 * called does not affect the returned new instance.
564 checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
565 function CustomError() {}
566 const instance = new construct(...constructArgs);
567 Object.defineProperty(instance, "constructor", {
569 throw new CustomError();
572 const result = instance[method](...methodArgs);
573 assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
574 resultAssertions(result);
578 * Checks that when subclassing, the subclass constructor is not called by
579 * the method under test.
581 checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
584 class MySubclass extends construct {
587 super(...constructArgs);
591 const instance = new MySubclass();
592 assert.sameValue(called, 1);
594 const result = instance[method](...methodArgs);
595 assert.sameValue(called, 1);
596 assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
597 resultAssertions(result);
601 * Check that the constructor's @@species property is ignored when it's a
602 * constructor that returns a non-object value.
604 checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
605 function check(value, description) {
606 const instance = new construct(...constructArgs);
607 instance.constructor = {
608 [Symbol.species]: function() {
612 const result = instance[method](...methodArgs);
613 assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
614 resultAssertions(result);
617 check(undefined, "undefined");
620 check("test", "string");
621 check(Symbol(), "Symbol");
624 check({}, "plain object");
628 * Check that the constructor's @@species property is ignored when it's not a
631 checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
632 function check(value, description) {
633 const instance = new construct(...constructArgs);
634 instance.constructor = {
635 [Symbol.species]: value,
637 const result = instance[method](...methodArgs);
638 assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
639 resultAssertions(result);
643 check("test", "string");
644 check(Symbol(), "Symbol");
647 check({}, "plain object");
651 * Check that the constructor's @@species property is ignored when it's null.
653 checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
656 class MySubclass extends construct {
659 super(...constructArgs);
663 const instance = new MySubclass();
664 assert.sameValue(called, 1);
666 MySubclass.prototype.constructor = {
667 [Symbol.species]: null,
670 const result = instance[method](...methodArgs);
671 assert.sameValue(called, 1);
672 assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
673 resultAssertions(result);
677 * Check that the constructor's @@species property is ignored when it's
680 checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
683 class MySubclass extends construct {
686 super(...constructArgs);
690 const instance = new MySubclass();
691 assert.sameValue(called, 1);
693 MySubclass.prototype.constructor = {
694 [Symbol.species]: undefined,
697 const result = instance[method](...methodArgs);
698 assert.sameValue(called, 1);
699 assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
700 resultAssertions(result);
704 * Check that the constructor's @@species property is ignored when it throws,
705 * i.e. it is not called at all.
707 checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
708 function CustomError() {}
710 const instance = new construct(...constructArgs);
711 instance.constructor = {
712 get [Symbol.species]() {
713 throw new CustomError();
717 const result = instance[method](...methodArgs);
718 assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
722 * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
724 * Static methods of Temporal classes that return a new instance of the class,
725 * must not use the this-value as a constructor. This helper runs tests to
728 * construct[method](...methodArgs) is the static method call under test, and
729 * must yield a valid instance of the Temporal class, not a subclass. See
730 * below for the individual tests that this runs.
731 * resultAssertions() is a function that performs additional assertions on the
732 * instance returned by the method under test.
734 checkSubclassingIgnoredStatic(...args) {
735 this.checkStaticInvalidReceiver(...args);
736 this.checkStaticReceiverNotCalled(...args);
737 this.checkThisValueNotCalled(...args);
741 * Check that calling the static method with a receiver that's not callable,
742 * still calls the intrinsic constructor.
744 checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
745 function check(value, description) {
746 const result = construct[method].apply(value, methodArgs);
747 assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
748 resultAssertions(result);
751 check(undefined, "undefined");
754 check("test", "string");
755 check(Symbol(), "symbol");
758 check({}, "Non-callable object");
762 * Check that calling the static method with a receiver that returns a value
763 * that's not callable, still calls the intrinsic constructor.
765 checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
766 function check(value, description) {
767 const receiver = function () {
770 const result = construct[method].apply(receiver, methodArgs);
771 assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
772 resultAssertions(result);
775 check(undefined, "undefined");
778 check("test", "string");
779 check(Symbol(), "symbol");
782 check({}, "Non-callable object");
786 * Check that the receiver isn't called.
788 checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
791 class MySubclass extends construct {
792 constructor(...args) {
798 const result = MySubclass[method](...methodArgs);
799 assert.sameValue(called, false);
800 assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
801 resultAssertions(result);
805 * Check that any iterable returned from a custom time zone's
806 * getPossibleInstantsFor() method is exhausted.
807 * The custom time zone object is passed in to func().
808 * expected is an array of strings representing the expected calls to the
809 * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
810 * are compared (using their toString() results) with the array.
812 checkTimeZonePossibleInstantsIterable(func, expected) {
813 // A custom time zone that returns an iterable instead of an array from its
814 // getPossibleInstantsFor() method, and for testing purposes skips
815 // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
816 // January 3, 2030. Otherwise identical to the UTC time zone.
817 class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
820 this.getPossibleInstantsForCallCount = 0;
821 this.getPossibleInstantsForCalledWith = [];
822 this.getPossibleInstantsForReturns = [];
823 this.iteratorExhausted = [];
827 return "Custom/Iterable";
830 getOffsetNanosecondsFor(instant) {
831 if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
832 Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
833 return 3600_000_000_000;
839 getPossibleInstantsFor(dateTime) {
840 this.getPossibleInstantsForCallCount++;
841 this.getPossibleInstantsForCalledWith.push(dateTime);
843 // Fake DST transition
844 let retval = super.getPossibleInstantsFor(dateTime);
845 if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
847 } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
848 retval.push(retval[0].subtract({ hours: 1 }));
849 } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
850 retval[0] = retval[0].subtract({ hours: 1 });
853 this.getPossibleInstantsForReturns.push(retval);
854 this.iteratorExhausted.push(false);
856 callIndex: this.getPossibleInstantsForCallCount - 1,
858 *[Symbol.iterator]() {
859 yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
860 this.timeZone.iteratorExhausted[this.callIndex] = true;
866 const timeZone = new TimeZonePossibleInstantsIterable();
869 assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
871 for (let index = 0; index < expected.length; index++) {
872 assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
873 assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
878 * Check that any calendar-carrying Temporal object has its [[Calendar]]
879 * internal slot read by ToTemporalCalendar, and does not fetch the calendar
880 * by calling getters.
881 * The custom calendar object is passed in to func() so that it can do its
882 * own additional assertions involving the calendar if necessary. (Sometimes
883 * there is nothing to assert as the calendar isn't stored anywhere that can
884 * be asserted about.)
886 checkToTemporalCalendarFastPath(func) {
887 class CalendarFastPathCheck extends Temporal.Calendar {
892 dateFromFields(...args) {
893 return super.dateFromFields(...args).withCalendar(this);
896 monthDayFromFields(...args) {
897 const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
898 return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
901 yearMonthFromFields(...args) {
902 const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
903 return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
907 return "fast-path-check";
910 const calendar = new CalendarFastPathCheck();
912 const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
913 const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
914 const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
915 const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
916 const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
918 [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
922 Object.defineProperty(temporalObject, "calendar", {
924 actual.push("get calendar");
929 func(temporalObject, calendar);
930 assert.compareArray(actual, expected, "calendar getter not called");
934 checkToTemporalInstantFastPath(func) {
938 const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
939 Object.defineProperty(datetime, 'toString', {
941 actual.push("get toString");
942 return function (options) {
943 actual.push("call toString");
944 return Temporal.ZonedDateTime.prototype.toString.call(this, options);
950 assert.compareArray(actual, expected, "toString not called");
953 checkToTemporalPlainDateTimeFastPath(func) {
957 const calendar = new Temporal.Calendar("iso8601");
958 const date = new Temporal.PlainDate(2000, 5, 2, calendar);
959 const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
960 ["year", "month", "monthCode", "day"].forEach((property) => {
961 Object.defineProperty(date, property, {
963 actual.push(`get ${formatPropertyName(property)}`);
964 const value = prototypeDescrs[property].get.call(this);
965 return TemporalHelpers.toPrimitiveObserver(actual, value, property);
969 ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
970 Object.defineProperty(date, property, {
972 actual.push(`get ${formatPropertyName(property)}`);
977 Object.defineProperty(date, "calendar", {
979 actual.push("get calendar");
984 func(date, calendar);
985 assert.compareArray(actual, expected, "property getters not called");
989 * A custom calendar used in prototype pollution checks. Verifies that the
990 * fromFields methods are always called with a null-prototype fields object.
992 calendarCheckFieldsPrototypePollution() {
993 class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
996 this.dateFromFieldsCallCount = 0;
997 this.yearMonthFromFieldsCallCount = 0;
998 this.monthDayFromFieldsCallCount = 0;
1001 // toString must remain "iso8601", so that some methods don't throw due to
1002 // incompatible calendars
1004 dateFromFields(fields, options = {}) {
1005 this.dateFromFieldsCallCount++;
1006 assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
1007 return super.dateFromFields(fields, options);
1010 yearMonthFromFields(fields, options = {}) {
1011 this.yearMonthFromFieldsCallCount++;
1012 assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
1013 return super.yearMonthFromFields(fields, options);
1016 monthDayFromFields(fields, options = {}) {
1017 this.monthDayFromFieldsCallCount++;
1018 assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
1019 return super.monthDayFromFields(fields, options);
1023 return new CalendarCheckFieldsPrototypePollution();
1027 * A custom calendar used in prototype pollution checks. Verifies that the
1028 * mergeFields() method is always called with null-prototype fields objects.
1030 calendarCheckMergeFieldsPrototypePollution() {
1031 class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
1034 this.mergeFieldsCallCount = 0;
1038 return "merge-fields-null-proto";
1041 mergeFields(fields, additionalFields) {
1042 this.mergeFieldsCallCount++;
1043 assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
1044 assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
1045 return super.mergeFields(fields, additionalFields);
1049 return new CalendarCheckMergeFieldsPrototypePollution();
1053 * A custom calendar used in prototype pollution checks. Verifies that methods
1054 * are always called with a null-prototype options object.
1056 calendarCheckOptionsPrototypePollution() {
1057 class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
1060 this.yearMonthFromFieldsCallCount = 0;
1061 this.dateUntilCallCount = 0;
1065 return "options-null-proto";
1068 yearMonthFromFields(fields, options) {
1069 this.yearMonthFromFieldsCallCount++;
1070 assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
1071 return super.yearMonthFromFields(fields, options);
1074 dateUntil(one, two, options) {
1075 this.dateUntilCallCount++;
1076 assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
1077 return super.dateUntil(one, two, options);
1081 return new CalendarCheckOptionsPrototypePollution();
1085 * A custom calendar that asserts its dateAdd() method is called with the
1086 * options parameter having the value undefined.
1088 calendarDateAddUndefinedOptions() {
1089 class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
1092 this.dateAddCallCount = 0;
1096 return "dateadd-undef-options";
1099 dateAdd(date, duration, options) {
1100 this.dateAddCallCount++;
1101 assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
1102 return super.dateAdd(date, duration, options);
1105 return new CalendarDateAddUndefinedOptions();
1109 * A custom calendar that asserts its dateAdd() method is called with a
1110 * PlainDate instance. Optionally, it also asserts that the PlainDate instance
1111 * is the specific object `this.specificPlainDate`, if it is set by the
1114 calendarDateAddPlainDateInstance() {
1115 class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
1118 this.dateAddCallCount = 0;
1119 this.specificPlainDate = undefined;
1123 return "dateadd-plain-date-instance";
1126 dateFromFields(...args) {
1127 return super.dateFromFields(...args).withCalendar(this);
1130 dateAdd(date, duration, options) {
1131 this.dateAddCallCount++;
1132 assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
1133 if (this.dateAddCallCount === 1 && this.specificPlainDate) {
1134 assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
1136 return super.dateAdd(date, duration, options).withCalendar(this);
1139 return new CalendarDateAddPlainDateInstance();
1143 * A custom calendar that returns an iterable instead of an array from its
1144 * fields() method, otherwise identical to the ISO calendar.
1146 calendarFieldsIterable() {
1147 class CalendarFieldsIterable extends Temporal.Calendar {
1150 this.fieldsCallCount = 0;
1151 this.fieldsCalledWith = [];
1152 this.iteratorExhausted = [];
1156 return "fields-iterable";
1159 fields(fieldNames) {
1160 this.fieldsCallCount++;
1161 this.fieldsCalledWith.push(fieldNames.slice());
1162 this.iteratorExhausted.push(false);
1164 callIndex: this.fieldsCallCount - 1,
1166 *[Symbol.iterator]() {
1167 yield* this.calendar.fieldsCalledWith[this.callIndex];
1168 this.calendar.iteratorExhausted[this.callIndex] = true;
1173 return new CalendarFieldsIterable();
1177 * A custom calendar that asserts its ...FromFields() methods are called with
1178 * the options parameter having the value undefined.
1180 calendarFromFieldsUndefinedOptions() {
1181 class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
1184 this.dateFromFieldsCallCount = 0;
1185 this.monthDayFromFieldsCallCount = 0;
1186 this.yearMonthFromFieldsCallCount = 0;
1190 return "from-fields-undef-options";
1193 dateFromFields(fields, options) {
1194 this.dateFromFieldsCallCount++;
1195 assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
1196 return super.dateFromFields(fields, options);
1199 yearMonthFromFields(fields, options) {
1200 this.yearMonthFromFieldsCallCount++;
1201 assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
1202 return super.yearMonthFromFields(fields, options);
1205 monthDayFromFields(fields, options) {
1206 this.monthDayFromFieldsCallCount++;
1207 assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
1208 return super.monthDayFromFields(fields, options);
1211 return new CalendarFromFieldsUndefinedOptions();
1215 * A custom calendar that modifies the fields object passed in to
1216 * dateFromFields, sabotaging its time properties.
1218 calendarMakeInfinityTime() {
1219 class CalendarMakeInfinityTime extends Temporal.Calendar {
1224 dateFromFields(fields, options) {
1225 const retval = super.dateFromFields(fields, options);
1226 fields.hour = Infinity;
1227 fields.minute = Infinity;
1228 fields.second = Infinity;
1229 fields.millisecond = Infinity;
1230 fields.microsecond = Infinity;
1231 fields.nanosecond = Infinity;
1235 return new CalendarMakeInfinityTime();
1239 * A custom calendar that defines getters on the fields object passed into
1240 * dateFromFields that throw, sabotaging its time properties.
1242 calendarMakeInvalidGettersTime() {
1243 class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
1248 dateFromFields(fields, options) {
1249 const retval = super.dateFromFields(fields, options);
1250 const throwingDescriptor = {
1252 throw new Test262Error("reading a sabotaged time field");
1255 Object.defineProperties(fields, {
1256 hour: throwingDescriptor,
1257 minute: throwingDescriptor,
1258 second: throwingDescriptor,
1259 millisecond: throwingDescriptor,
1260 microsecond: throwingDescriptor,
1261 nanosecond: throwingDescriptor,
1266 return new CalendarMakeInvalidGettersTime();
1270 * A custom calendar whose mergeFields() method returns a proxy object with
1271 * all of its Get and HasProperty operations observable, as well as adding a
1272 * "shouldNotBeCopied": true property.
1274 calendarMergeFieldsGetters() {
1275 class CalendarMergeFieldsGetters extends Temporal.Calendar {
1278 this.mergeFieldsReturnOperations = [];
1282 return "merge-fields-getters";
1285 dateFromFields(fields, options) {
1286 assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
1287 return super.dateFromFields(fields, options);
1290 yearMonthFromFields(fields, options) {
1291 assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
1292 return super.yearMonthFromFields(fields, options);
1295 monthDayFromFields(fields, options) {
1296 assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
1297 return super.monthDayFromFields(fields, options);
1300 mergeFields(fields, additionalFields) {
1301 const retval = super.mergeFields(fields, additionalFields);
1302 retval._calendar = this;
1303 retval.shouldNotBeCopied = true;
1304 return new Proxy(retval, {
1306 target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
1307 const result = target[key];
1308 if (result === undefined) {
1311 return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
1314 target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
1315 return key in target;
1320 return new CalendarMergeFieldsGetters();
1324 * A custom calendar whose mergeFields() method returns a primitive value,
1325 * given by @primitive, and which records the number of calls made to its
1326 * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
1328 calendarMergeFieldsReturnsPrimitive(primitive) {
1329 class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
1330 constructor(mergeFieldsReturnValue) {
1332 this._mergeFieldsReturnValue = mergeFieldsReturnValue;
1333 this.dateFromFieldsCallCount = 0;
1334 this.monthDayFromFieldsCallCount = 0;
1335 this.yearMonthFromFieldsCallCount = 0;
1339 return "merge-fields-primitive";
1342 dateFromFields(fields, options) {
1343 this.dateFromFieldsCallCount++;
1344 return super.dateFromFields(fields, options);
1347 yearMonthFromFields(fields, options) {
1348 this.yearMonthFromFieldsCallCount++;
1349 return super.yearMonthFromFields(fields, options);
1352 monthDayFromFields(fields, options) {
1353 this.monthDayFromFieldsCallCount++;
1354 return super.monthDayFromFields(fields, options);
1358 return this._mergeFieldsReturnValue;
1361 return new CalendarMergeFieldsPrimitive(primitive);
1365 * A custom calendar whose fields() method returns the same value as the
1366 * iso8601 calendar, with the addition of extraFields provided as parameter.
1368 calendarWithExtraFields(fields) {
1369 class CalendarWithExtraFields extends Temporal.Calendar {
1370 constructor(extraFields) {
1372 this._extraFields = extraFields;
1375 fields(fieldNames) {
1376 return super.fields(fieldNames).concat(this._extraFields);
1380 return new CalendarWithExtraFields(fields);
1384 * crossDateLineTimeZone():
1386 * This returns an instance of a custom time zone class that implements one
1387 * single transition where the time zone moves from one side of the
1388 * International Date Line to the other, for the purpose of testing time zone
1389 * calculations without depending on system time zone data.
1391 * The transition occurs at epoch second 1325239200 and goes from offset
1392 * -10:00 to +14:00. In other words, the time zone skips the whole calendar
1393 * day of 2011-12-30. This is the same as the real-life transition in the
1394 * Pacific/Apia time zone.
1396 crossDateLineTimeZone() {
1397 const { compare } = Temporal.PlainDateTime;
1398 const skippedDay = new Temporal.PlainDate(2011, 12, 30);
1399 const transitionEpoch = 1325239200_000_000_000n;
1400 const beforeOffset = new Temporal.TimeZone("-10:00");
1401 const afterOffset = new Temporal.TimeZone("+14:00");
1403 class CrossDateLineTimeZone extends Temporal.TimeZone {
1408 getOffsetNanosecondsFor(instant) {
1409 if (instant.epochNanoseconds < transitionEpoch) {
1410 return beforeOffset.getOffsetNanosecondsFor(instant);
1412 return afterOffset.getOffsetNanosecondsFor(instant);
1415 getPossibleInstantsFor(datetime) {
1416 const comparison = Temporal.PlainDate.compare(datetime.toPlainDate(), skippedDay);
1417 if (comparison === 0) {
1420 if (comparison < 0) {
1421 return [beforeOffset.getInstantFor(datetime)];
1423 return [afterOffset.getInstantFor(datetime)];
1426 getPreviousTransition(instant) {
1427 if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
1431 getNextTransition(instant) {
1432 if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
1437 return "Custom/Date_Line";
1440 return new CrossDateLineTimeZone();
1444 * observeProperty(calls, object, propertyName, value):
1446 * Defines an own property @object.@propertyName with value @value, that
1447 * will log any calls to its accessors to the array @calls.
1449 observeProperty(calls, object, propertyName, value, objectName = "") {
1450 Object.defineProperty(object, propertyName, {
1452 calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
1456 calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
1462 * observeMethod(calls, object, propertyName, value):
1464 * Defines an own property @object.@propertyName with value @value, that
1465 * will log any calls of @value to the array @calls.
1467 observeMethod(calls, object, propertyName, objectName = "") {
1468 const method = object[propertyName];
1469 object[propertyName] = function () {
1470 calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
1471 return method.apply(object, arguments);
1476 * Used for substituteMethod to indicate default behavior instead of a
1479 SUBSTITUTE_SKIP: SKIP_SYMBOL,
1482 * substituteMethod(object, propertyName, values):
1484 * Defines an own property @object.@propertyName that will, for each
1485 * subsequent call to the method previously defined as
1486 * @object.@propertyName:
1487 * - Call the method, if no more values remain
1488 * - Call the method, if the value in @values for the corresponding call
1489 * is SUBSTITUTE_SKIP
1490 * - Otherwise, return the corresponding value in @value
1492 substituteMethod(object, propertyName, values) {
1494 const method = object[propertyName];
1495 object[propertyName] = function () {
1496 if (calls >= values.length) {
1497 return method.apply(object, arguments);
1498 } else if (values[calls] === SKIP_SYMBOL) {
1500 return method.apply(object, arguments);
1502 return values[calls++];
1509 * A custom calendar that behaves exactly like the ISO 8601 calendar but
1510 * tracks calls to any of its methods, and Get/Has operations on its
1511 * properties, by appending messages to an array. This is for the purpose of
1512 * testing order of operations that are observable from user code.
1513 * objectName is used in the log.
1515 calendarObserver(calls, objectName, methodOverrides = {}) {
1516 function removeExtraHasPropertyChecks(objectName, calls) {
1517 // Inserting the tracking calendar into the return values of methods
1518 // that we chain up into the ISO calendar for, causes extra HasProperty
1519 // checks, which we observe. This removes them so that we don't leak
1520 // implementation details of the helper into the test code.
1521 assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
1522 assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
1523 assert.sameValue(calls.pop(), `has ${objectName}.year`);
1524 assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
1525 assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
1526 assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
1527 assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
1528 assert.sameValue(calls.pop(), `has ${objectName}.month`);
1529 assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
1530 assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
1531 assert.sameValue(calls.pop(), `has ${objectName}.id`);
1532 assert.sameValue(calls.pop(), `has ${objectName}.fields`);
1533 assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
1534 assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
1535 assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
1536 assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
1537 assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
1538 assert.sameValue(calls.pop(), `has ${objectName}.day`);
1539 assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
1540 assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
1541 assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
1544 const iso8601 = new Temporal.Calendar("iso8601");
1545 const trackingMethods = {
1546 dateFromFields(...args) {
1547 calls.push(`call ${objectName}.dateFromFields`);
1548 if ('dateFromFields' in methodOverrides) {
1549 const value = methodOverrides.dateFromFields;
1550 return typeof value === "function" ? value(...args) : value;
1552 const originalResult = iso8601.dateFromFields(...args);
1553 // Replace the calendar in the result with the call-tracking calendar
1554 const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
1555 const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
1556 removeExtraHasPropertyChecks(objectName, calls);
1559 yearMonthFromFields(...args) {
1560 calls.push(`call ${objectName}.yearMonthFromFields`);
1561 if ('yearMonthFromFields' in methodOverrides) {
1562 const value = methodOverrides.yearMonthFromFields;
1563 return typeof value === "function" ? value(...args) : value;
1565 const originalResult = iso8601.yearMonthFromFields(...args);
1566 // Replace the calendar in the result with the call-tracking calendar
1567 const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
1568 const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
1569 removeExtraHasPropertyChecks(objectName, calls);
1572 monthDayFromFields(...args) {
1573 calls.push(`call ${objectName}.monthDayFromFields`);
1574 if ('monthDayFromFields' in methodOverrides) {
1575 const value = methodOverrides.monthDayFromFields;
1576 return typeof value === "function" ? value(...args) : value;
1578 const originalResult = iso8601.monthDayFromFields(...args);
1579 // Replace the calendar in the result with the call-tracking calendar
1580 const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
1581 const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
1582 removeExtraHasPropertyChecks(objectName, calls);
1586 calls.push(`call ${objectName}.dateAdd`);
1587 if ('dateAdd' in methodOverrides) {
1588 const value = methodOverrides.dateAdd;
1589 return typeof value === "function" ? value(...args) : value;
1591 const originalResult = iso8601.dateAdd(...args);
1592 const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
1593 const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
1594 removeExtraHasPropertyChecks(objectName, calls);
1599 // Automatically generate the other methods that don't need any custom code
1620 ].forEach((methodName) => {
1621 trackingMethods[methodName] = function (...args) {
1622 calls.push(`call ${formatPropertyName(methodName, objectName)}`);
1623 if (methodName in methodOverrides) {
1624 const value = methodOverrides[methodName];
1625 return typeof value === "function" ? value(...args) : value;
1627 return iso8601[methodName](...args);
1630 return new Proxy(trackingMethods, {
1631 get(target, key, receiver) {
1632 const result = Reflect.get(target, key, receiver);
1633 calls.push(`get ${formatPropertyName(key, objectName)}`);
1637 calls.push(`has ${formatPropertyName(key, objectName)}`);
1638 return Reflect.has(target, key);
1644 * A custom calendar that does not allow any of its methods to be called, for
1645 * the purpose of asserting that a particular operation does not call into
1648 calendarThrowEverything() {
1649 class CalendarThrowEverything extends Temporal.Calendar {
1654 TemporalHelpers.assertUnreachable("toString should not be called");
1657 TemporalHelpers.assertUnreachable("dateFromFields should not be called");
1659 yearMonthFromFields() {
1660 TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
1662 monthDayFromFields() {
1663 TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
1666 TemporalHelpers.assertUnreachable("dateAdd should not be called");
1669 TemporalHelpers.assertUnreachable("dateUntil should not be called");
1672 TemporalHelpers.assertUnreachable("era should not be called");
1675 TemporalHelpers.assertUnreachable("eraYear should not be called");
1678 TemporalHelpers.assertUnreachable("year should not be called");
1681 TemporalHelpers.assertUnreachable("month should not be called");
1684 TemporalHelpers.assertUnreachable("monthCode should not be called");
1687 TemporalHelpers.assertUnreachable("day should not be called");
1690 TemporalHelpers.assertUnreachable("fields should not be called");
1693 TemporalHelpers.assertUnreachable("mergeFields should not be called");
1697 return new CalendarThrowEverything();
1701 * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
1703 * In the case of a spring-forward time zone offset transition (skipped time),
1704 * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
1705 * negative number of nanoseconds from a PlainDateTime, which should balance
1706 * with the microseconds field.
1708 * This returns an instance of a custom time zone class which skips a length
1709 * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
1710 * shiftInstant. Before shiftInstant, it's identical to UTC, and after
1711 * shiftInstant it's a constant-offset time zone.
1713 * It provides a getPossibleInstantsForCalledWith member which is an array
1714 * with the result of calling toString() on any PlainDateTimes passed to
1715 * getPossibleInstantsFor().
1717 oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
1718 class OneShiftTimeZone extends Temporal.TimeZone {
1719 constructor(shiftInstant, shiftNanoseconds) {
1721 this._shiftInstant = shiftInstant;
1722 this._epoch1 = shiftInstant.epochNanoseconds;
1723 this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
1724 this._shiftNanoseconds = shiftNanoseconds;
1725 this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
1726 this.getPossibleInstantsForCalledWith = [];
1729 _isBeforeShift(instant) {
1730 return instant.epochNanoseconds < this._epoch1;
1733 getOffsetNanosecondsFor(instant) {
1734 return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
1737 getPossibleInstantsFor(plainDateTime) {
1738 this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
1739 const [instant] = super.getPossibleInstantsFor(plainDateTime);
1740 if (this._shiftNanoseconds > 0) {
1741 if (this._isBeforeShift(instant)) return [instant];
1742 if (instant.epochNanoseconds < this._epoch2) return [];
1743 return [instant.subtract(this._shift)];
1745 if (instant.epochNanoseconds < this._epoch2) return [instant];
1746 const shifted = instant.subtract(this._shift);
1747 if (this._isBeforeShift(instant)) return [instant, shifted];
1751 getNextTransition(instant) {
1752 return this._isBeforeShift(instant) ? this._shiftInstant : null;
1755 getPreviousTransition(instant) {
1756 return this._isBeforeShift(instant) ? null : this._shiftInstant;
1760 return "Custom/One_Shift";
1763 return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
1767 * propertyBagObserver():
1768 * Returns an object that behaves like the given propertyBag but tracks Get
1769 * and Has operations on any of its properties, by appending messages to an
1770 * array. If the value of a property in propertyBag is a primitive, the value
1771 * of the returned object's property will additionally be a
1772 * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
1773 * and valueOf methods in the same array. This is for the purpose of testing
1774 * order of operations that are observable from user code. objectName is used
1777 propertyBagObserver(calls, propertyBag, objectName) {
1778 return new Proxy(propertyBag, {
1780 calls.push(`ownKeys ${objectName}`);
1781 return Reflect.ownKeys(target);
1783 getOwnPropertyDescriptor(target, key) {
1784 calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
1785 return Reflect.getOwnPropertyDescriptor(target, key);
1787 get(target, key, receiver) {
1788 calls.push(`get ${formatPropertyName(key, objectName)}`);
1789 const result = Reflect.get(target, key, receiver);
1790 if (result === undefined) {
1793 if ((result !== null && typeof result === "object") || typeof result === "function") {
1796 return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
1799 calls.push(`has ${formatPropertyName(key, objectName)}`);
1800 return Reflect.has(target, key);
1806 * specificOffsetTimeZone():
1808 * This returns an instance of a custom time zone class, which returns a
1809 * specific custom value from its getOffsetNanosecondsFrom() method. This is
1810 * for the purpose of testing the validation of what this method returns.
1812 * It also returns an empty array from getPossibleInstantsFor(), so as to
1813 * trigger calls to getOffsetNanosecondsFor() when used from the
1814 * BuiltinTimeZoneGetInstantFor operation.
1816 specificOffsetTimeZone(offsetValue) {
1817 class SpecificOffsetTimeZone extends Temporal.TimeZone {
1818 constructor(offsetValue) {
1820 this._offsetValue = offsetValue;
1823 getOffsetNanosecondsFor() {
1824 return this._offsetValue;
1827 getPossibleInstantsFor(dt) {
1828 if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
1829 const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
1830 return [zdt.toInstant()];
1834 return this.getOffsetStringFor(new Temporal.Instant(0n));
1837 return new SpecificOffsetTimeZone(offsetValue);
1841 * springForwardFallBackTimeZone():
1843 * This returns an instance of a custom time zone class that implements one
1844 * single spring-forward/fall-back transition, for the purpose of testing the
1845 * disambiguation option, without depending on system time zone data.
1847 * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
1848 * local) and goes from offset -08:00 to -07:00.
1850 * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
1851 * goes from offset -07:00 to -08:00.
1853 springForwardFallBackTimeZone() {
1854 const { compare } = Temporal.PlainDateTime;
1855 const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
1856 const springForwardEpoch = 954669600_000_000_000n;
1857 const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
1858 const fallBackEpoch = 972810000_000_000_000n;
1859 const winterOffset = new Temporal.TimeZone('-08:00');
1860 const summerOffset = new Temporal.TimeZone('-07:00');
1862 class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
1867 getOffsetNanosecondsFor(instant) {
1868 if (instant.epochNanoseconds < springForwardEpoch ||
1869 instant.epochNanoseconds >= fallBackEpoch) {
1870 return winterOffset.getOffsetNanosecondsFor(instant);
1872 return summerOffset.getOffsetNanosecondsFor(instant);
1875 getPossibleInstantsFor(datetime) {
1876 if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
1879 if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
1880 return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
1882 if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
1883 return [winterOffset.getInstantFor(datetime)];
1885 return [summerOffset.getInstantFor(datetime)];
1888 getPreviousTransition(instant) {
1889 if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
1890 if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
1894 getNextTransition(instant) {
1895 if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
1896 if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
1901 return "Custom/Spring_Fall";
1905 return "Custom/Spring_Fall";
1908 return new SpringForwardFallBackTimeZone();
1913 * A custom calendar that behaves exactly like the UTC time zone but tracks
1914 * calls to any of its methods, and Get/Has operations on its properties, by
1915 * appending messages to an array. This is for the purpose of testing order of
1916 * operations that are observable from user code. objectName is used in the
1917 * log. methodOverrides is an optional object containing properties with the
1918 * same name as Temporal.TimeZone methods. If the property value is a function
1919 * it will be called with the proper arguments instead of the UTC method.
1920 * Otherwise, the property value will be returned directly.
1922 timeZoneObserver(calls, objectName, methodOverrides = {}) {
1923 const utc = new Temporal.TimeZone("UTC");
1924 const trackingMethods = {
1927 // Automatically generate the methods
1928 ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
1929 trackingMethods[methodName] = function (...args) {
1930 calls.push(`call ${formatPropertyName(methodName, objectName)}`);
1931 if (methodName in methodOverrides) {
1932 const value = methodOverrides[methodName];
1933 return typeof value === "function" ? value(...args) : value;
1935 return utc[methodName](...args);
1938 return new Proxy(trackingMethods, {
1939 get(target, key, receiver) {
1940 const result = Reflect.get(target, key, receiver);
1941 calls.push(`get ${formatPropertyName(key, objectName)}`);
1945 calls.push(`has ${formatPropertyName(key, objectName)}`);
1946 return Reflect.has(target, key);
1952 * A custom time zone that does not allow any of its methods to be called, for
1953 * the purpose of asserting that a particular operation does not call into
1956 timeZoneThrowEverything() {
1957 class TimeZoneThrowEverything extends Temporal.TimeZone {
1961 getOffsetNanosecondsFor() {
1962 TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
1964 getPossibleInstantsFor() {
1965 TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
1968 TemporalHelpers.assertUnreachable("toString should not be called");
1972 return new TimeZoneThrowEverything();
1976 * Returns an object that will append logs of any Gets or Calls of its valueOf
1977 * or toString properties to the array calls. Both valueOf and toString will
1978 * return the actual primitiveValue. propertyName is used in the log.
1980 toPrimitiveObserver(calls, primitiveValue, propertyName) {
1983 calls.push(`get ${propertyName}.valueOf`);
1984 return function () {
1985 calls.push(`call ${propertyName}.valueOf`);
1986 return primitiveValue;
1990 calls.push(`get ${propertyName}.toString`);
1991 return function () {
1992 calls.push(`call ${propertyName}.toString`);
1993 if (primitiveValue === undefined) return undefined;
1994 return primitiveValue.toString();
2001 * An object containing further methods that return arrays of ISO strings, for
2006 * PlainMonthDay strings that are not valid.
2008 plainMonthDayStringsInvalid() {
2011 "11-18[u-ca=gregory]",
2012 "11-18[u-ca=hebrew]",
2017 * PlainMonthDay strings that are valid and that should produce October 1st.
2019 plainMonthDayStringsValid() {
2024 "1976-10-01T152330.1+00:00",
2025 "19761001T15:23:30.1+00:00",
2026 "1976-10-01T15:23:30.1+0000",
2027 "1976-10-01T152330.1+0000",
2028 "19761001T15:23:30.1+0000",
2029 "19761001T152330.1+00:00",
2030 "19761001T152330.1+0000",
2031 "+001976-10-01T152330.1+00:00",
2032 "+0019761001T15:23:30.1+00:00",
2033 "+001976-10-01T15:23:30.1+0000",
2034 "+001976-10-01T152330.1+0000",
2035 "+0019761001T15:23:30.1+0000",
2036 "+0019761001T152330.1+00:00",
2037 "+0019761001T152330.1+0000",
2038 "1976-10-01T15:23:00",
2048 * PlainTime strings that may be mistaken for PlainMonthDay or
2049 * PlainYearMonth strings, and so require a time designator.
2051 plainTimeStringsAmbiguous() {
2052 const ambiguousStrings = [
2053 "2021-12", // ambiguity between YYYY-MM and HHMM-UU
2054 "2021-12[-12:00]", // ditto, TZ does not disambiguate
2055 "1214", // ambiguity between MMDD and HHMM
2056 "0229", // ditto, including MMDD that doesn't occur every year
2057 "1130", // ditto, including DD that doesn't occur in every month
2058 "12-14", // ambiguity between MM-DD and HH-UU
2059 "12-14[-14:00]", // ditto, TZ does not disambiguate
2060 "202112", // ambiguity between YYYYMM and HHMMSS
2061 "202112[UTC]", // ditto, TZ does not disambiguate
2063 // Adding a calendar annotation to one of these strings must not cause
2064 // disambiguation in favour of time.
2065 const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
2066 return ambiguousStrings.concat(stringsWithCalendar);
2070 * PlainTime strings that are of similar form to PlainMonthDay and
2071 * PlainYearMonth strings, but are not ambiguous due to components that
2072 * aren't valid as months or days.
2074 plainTimeStringsUnambiguous() {
2076 "2021-13", // 13 is not a month
2078 "2021-13[-13:00]", // ditto
2079 "202113[-13:00]", // ditto
2080 "0000-00", // 0 is not a month
2082 "0000-00[UTC]", // ditto
2083 "000000[UTC]", // ditto
2084 "1314", // 13 is not a month
2086 "1232", // 32 is not a day
2087 "0230", // 30 is not a day in February
2088 "0631", // 31 is not a day in June
2089 "0000", // 0 is neither a month nor a day
2095 * PlainYearMonth-like strings that are not valid.
2097 plainYearMonthStringsInvalid() {
2104 * PlainYearMonth-like strings that are valid and should produce November
2105 * 1976 in the ISO 8601 calendar.
2107 plainYearMonthStringsValid() {
2111 "1976-11-01T09:00:00+00:00",
2112 "1976-11-01T00:00:00+05:00",
2115 "1976-11-18T15:23:30.1\u221202:00",
2116 "1976-11-18T152330.1+00:00",
2117 "19761118T15:23:30.1+00:00",
2118 "1976-11-18T15:23:30.1+0000",
2119 "1976-11-18T152330.1+0000",
2120 "19761118T15:23:30.1+0000",
2121 "19761118T152330.1+00:00",
2122 "19761118T152330.1+0000",
2123 "+001976-11-18T152330.1+00:00",
2124 "+0019761118T15:23:30.1+00:00",
2125 "+001976-11-18T15:23:30.1+0000",
2126 "+001976-11-18T152330.1+0000",
2127 "+0019761118T15:23:30.1+0000",
2128 "+0019761118T152330.1+00:00",
2129 "+0019761118T152330.1+0000",
2137 * PlainYearMonth-like strings that are valid and should produce November of
2138 * the ISO year -9999.
2140 plainYearMonthStringsValidNegativeYear() {