2021-07-16 15:25:55 +02:00
// 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 ]
-- - * /
2022-09-20 21:53:57 +02:00
function formatPropertyName ( propertyKey , objectName = "" ) {
switch ( typeof propertyKey ) {
case "symbol" :
if ( Symbol . keyFor ( propertyKey ) !== undefined ) {
return ` ${ objectName } [Symbol.for(' ${ Symbol . keyFor ( propertyKey ) } ')] ` ;
} else if ( propertyKey . description . startsWith ( 'Symbol.' ) ) {
return ` ${ objectName } [ ${ propertyKey . description } ] ` ;
} else {
return ` ${ objectName } [Symbol(' ${ propertyKey . description } ')] `
}
case "number" :
return ` ${ objectName } [ ${ propertyKey } ] ` ;
default :
2023-03-10 11:46:59 +01:00
// TODO: check if propertyKey is an integer index.
2022-09-20 21:53:57 +02:00
return objectName ? ` ${ objectName } . ${ propertyKey } ` : propertyKey ;
}
}
2022-11-26 04:20:11 +01:00
const SKIP _SYMBOL = Symbol ( "Skip" ) ;
2022-09-20 21:53:57 +02:00
2021-07-16 15:25:55 +02:00
var TemporalHelpers = {
2021-07-20 21:26:29 +02:00
/ *
* 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 ` ) ;
} ,
2021-11-04 22:06:23 +01:00
/ *
* 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 ` ) ;
} ,
2021-07-20 21:26:29 +02:00
/ *
* 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
2023-02-17 21:28:55 +01:00
* value of date . calendarId . )
2021-07-20 21:26:29 +02:00
* /
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 ,
2023-02-17 21:28:55 +01:00
* or the value of datetime . calendarId . )
2021-07-20 21:26:29 +02:00
* /
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 ` ) ;
} ,
2021-11-04 22:06:23 +01:00
/ *
* 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
2023-02-17 21:28:55 +01:00
* their calendar internal slots are the same value .
2021-11-04 22:06:23 +01:00
* /
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 ` ) ;
2023-02-17 21:28:55 +01:00
assert . sameValue (
actual . getISOFields ( ) . calendar ,
expected . getISOFields ( ) . calendar ,
` ${ description } calendar same value `
) ;
2021-11-04 22:06:23 +01:00
} ,
2021-07-20 21:26:29 +02:00
/ *
2021-09-29 21:48:22 +02:00
* assertPlainMonthDay ( monthDay , monthCode , day [ , description [ , referenceISOYear ] ] ) :
2021-07-20 21:26:29 +02:00
*
* 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 ,
2023-02-17 21:28:55 +01:00
* or the value of monthDay . calendarId ( ) . )
2021-07-20 21:26:29 +02:00
* /
2021-09-29 21:48:22 +02:00
assertPlainMonthDay ( monthDay , monthCode , day , description = "" , referenceISOYear = 1972 ) {
2021-07-20 21:26:29 +02:00
assert ( monthDay instanceof Temporal . PlainMonthDay , ` ${ description } instanceof ` ) ;
assert . sameValue ( monthDay . monthCode , monthCode , ` ${ description } monthCode result ` ) ;
assert . sameValue ( monthDay . day , day , ` ${ description } day result ` ) ;
2021-09-29 21:48:22 +02:00
assert . sameValue ( monthDay . getISOFields ( ) . isoYear , referenceISOYear , ` ${ description } referenceISOYear result ` ) ;
2021-07-20 21:26:29 +02:00
} ,
/ *
* 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 ` ) ;
} ,
2021-11-04 22:06:23 +01:00
/ *
* 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 ` ) ;
} ,
2021-07-20 21:26:29 +02:00
/ *
2022-04-20 00:43:18 +02:00
* assertPlainYearMonth ( yearMonth , year , month , monthCode [ , description [ , era , eraYear , referenceISODay ] ] ) :
2021-07-20 21:26:29 +02:00
*
* 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 ,
2023-02-17 21:28:55 +01:00
* or the value of yearMonth . calendarId . )
2021-07-20 21:26:29 +02:00
* /
2022-04-20 00:43:18 +02:00
assertPlainYearMonth ( yearMonth , year , month , monthCode , description = "" , era = undefined , eraYear = undefined , referenceISODay = 1 ) {
2021-07-20 21:26:29 +02:00
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 ` ) ;
2022-04-20 00:43:18 +02:00
assert . sameValue ( yearMonth . getISOFields ( ) . isoDay , referenceISODay , ` ${ description } referenceISODay result ` ) ;
2021-07-20 21:26:29 +02:00
} ,
2021-09-29 21:48:22 +02:00
2021-11-04 22:06:23 +01:00
/ *
* 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
2023-02-17 21:28:55 +01:00
* their time zones and calendar internal slots are the same value .
2021-11-04 22:06:23 +01:00
* /
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 ` ) ;
2023-02-17 21:28:55 +01:00
assert . sameValue (
actual . getISOFields ( ) . calendar ,
expected . getISOFields ( ) . calendar ,
` ${ description } calendar same value `
) ;
2021-11-04 22:06:23 +01:00
} ,
2021-09-29 21:48:22 +02:00
/ *
* 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 } ` ) ;
2022-11-28 20:31:53 +01:00
actual . splice ( 0 ) ; // empty it for the next check
2021-09-29 21:48:22 +02:00
} ) ;
} ,
/ *
* 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 . )
* /
2022-06-08 18:02:01 +02:00
checkPlainDateTimeConversionFastPath ( func , message = "checkPlainDateTimeConversionFastPath" ) {
2021-09-29 21:48:22 +02:00
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 ( ) {
2022-09-20 21:53:57 +02:00
actual . push ( ` get ${ formatPropertyName ( property ) } ` ) ;
2021-09-29 21:48:22 +02:00
const value = prototypeDescrs [ property ] . get . call ( this ) ;
return {
toString ( ) {
2022-09-20 21:53:57 +02:00
actual . push ( ` toString ${ formatPropertyName ( property ) } ` ) ;
2021-09-29 21:48:22 +02:00
return value . toString ( ) ;
} ,
valueOf ( ) {
2022-09-20 21:53:57 +02:00
actual . push ( ` valueOf ${ formatPropertyName ( property ) } ` ) ;
2021-09-29 21:48:22 +02:00
return value ;
} ,
} ;
} ,
} ) ;
} ) ;
Object . defineProperty ( datetime , "calendar" , {
get ( ) {
actual . push ( "get calendar" ) ;
return calendar ;
} ,
} ) ;
func ( datetime , calendar ) ;
2022-06-08 18:02:01 +02:00
assert . compareArray ( actual , expected , ` ${ message } : property getters not called ` ) ;
2021-09-29 21:48:22 +02:00
} ,
/ *
* 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 ] ) ;
2021-11-04 22:06:23 +01:00
const desc = ` Plural ${ plurals [ unit ] } produces the same result as singular ${ unit } ` ;
2021-09-29 21:48:22 +02:00
if ( singularValue instanceof Temporal . Duration ) {
2021-11-04 22:06:23 +01:00
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 ) ;
2021-09-29 21:48:22 +02:00
} 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 ( 2 n ) , "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 ( 2 n ) , "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 ( 7 n , "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 ( 7 n , "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 ( 7 n , "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 ( 7 n , "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 ( 7 n , "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" ) ;
}
} ,
2021-07-16 15:25:55 +02:00
/ *
* 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" ) ;
}
2023-02-17 21:33:18 +01:00
dateFromFields ( ... args ) {
return super . dateFromFields ( ... args ) . withCalendar ( this ) ;
}
monthDayFromFields ( ... args ) {
const { isoYear , isoMonth , isoDay } = super . monthDayFromFields ( ... args ) . getISOFields ( ) ;
return new Temporal . PlainMonthDay ( isoMonth , isoDay , this , isoYear ) ;
}
yearMonthFromFields ( ... args ) {
const { isoYear , isoMonth , isoDay } = super . yearMonthFromFields ( ... args ) . getISOFields ( ) ;
return new Temporal . PlainYearMonth ( isoYear , isoMonth , this , isoDay ) ;
}
2021-07-16 15:25:55 +02:00
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_000 n , "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" ) ;
} ) ;
} ,
2021-09-29 21:48:22 +02:00
checkToTemporalInstantFastPath ( func ) {
const actual = [ ] ;
const expected = [ ] ;
const datetime = new Temporal . ZonedDateTime ( 1_000_000_000_987_654_321 n , "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 ( ) {
2022-09-20 21:53:57 +02:00
actual . push ( ` get ${ formatPropertyName ( property ) } ` ) ;
2021-09-29 21:48:22 +02:00
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 ( ) {
2022-09-20 21:53:57 +02:00
actual . push ( ` get ${ formatPropertyName ( property ) } ` ) ;
2021-09-29 21:48:22 +02:00
return undefined ;
} ,
} ) ;
} ) ;
Object . defineProperty ( date , "calendar" , {
get ( ) {
actual . push ( "get calendar" ) ;
return calendar ;
} ,
} ) ;
func ( date , calendar ) ;
assert . compareArray ( actual , expected , "property getters not called" ) ;
} ,
2022-06-26 11:42:21 +02:00
/ *
* A custom calendar used in prototype pollution checks . Verifies that the
* fromFields methods are always called with a null - prototype fields object .
* /
calendarCheckFieldsPrototypePollution ( ) {
class CalendarCheckFieldsPrototypePollution extends Temporal . Calendar {
constructor ( ) {
super ( "iso8601" ) ;
this . dateFromFieldsCallCount = 0 ;
this . yearMonthFromFieldsCallCount = 0 ;
this . monthDayFromFieldsCallCount = 0 ;
}
// toString must remain "iso8601", so that some methods don't throw due to
// incompatible calendars
dateFromFields ( fields , options = { } ) {
this . dateFromFieldsCallCount ++ ;
assert . sameValue ( Object . getPrototypeOf ( fields ) , null , "dateFromFields should be called with null-prototype fields object" ) ;
return super . dateFromFields ( fields , options ) ;
}
yearMonthFromFields ( fields , options = { } ) {
this . yearMonthFromFieldsCallCount ++ ;
assert . sameValue ( Object . getPrototypeOf ( fields ) , null , "yearMonthFromFields should be called with null-prototype fields object" ) ;
return super . yearMonthFromFields ( fields , options ) ;
}
monthDayFromFields ( fields , options = { } ) {
this . monthDayFromFieldsCallCount ++ ;
assert . sameValue ( Object . getPrototypeOf ( fields ) , null , "monthDayFromFields should be called with null-prototype fields object" ) ;
return super . monthDayFromFields ( fields , options ) ;
}
}
return new CalendarCheckFieldsPrototypePollution ( ) ;
} ,
2022-06-26 13:38:03 +02:00
/ *
* A custom calendar used in prototype pollution checks . Verifies that the
* mergeFields ( ) method is always called with null - prototype fields objects .
* /
calendarCheckMergeFieldsPrototypePollution ( ) {
class CalendarCheckMergeFieldsPrototypePollution extends Temporal . Calendar {
constructor ( ) {
super ( "iso8601" ) ;
this . mergeFieldsCallCount = 0 ;
}
toString ( ) {
return "merge-fields-null-proto" ;
}
mergeFields ( fields , additionalFields ) {
this . mergeFieldsCallCount ++ ;
assert . sameValue ( Object . getPrototypeOf ( fields ) , null , "mergeFields should be called with null-prototype fields object (first argument)" ) ;
assert . sameValue ( Object . getPrototypeOf ( additionalFields ) , null , "mergeFields should be called with null-prototype fields object (second argument)" ) ;
return super . mergeFields ( fields , additionalFields ) ;
}
}
return new CalendarCheckMergeFieldsPrototypePollution ( ) ;
} ,
2022-06-26 10:28:06 +02:00
/ *
* A custom calendar used in prototype pollution checks . Verifies that methods
* are always called with a null - prototype options object .
* /
calendarCheckOptionsPrototypePollution ( ) {
class CalendarCheckOptionsPrototypePollution extends Temporal . Calendar {
constructor ( ) {
super ( "iso8601" ) ;
2022-06-26 10:29:46 +02:00
this . yearMonthFromFieldsCallCount = 0 ;
2022-06-26 10:28:06 +02:00
this . dateUntilCallCount = 0 ;
}
toString ( ) {
return "options-null-proto" ;
}
2022-06-26 10:29:46 +02:00
yearMonthFromFields ( fields , options ) {
this . yearMonthFromFieldsCallCount ++ ;
assert . sameValue ( Object . getPrototypeOf ( options ) , null , "yearMonthFromFields should be called with null-prototype options" ) ;
return super . yearMonthFromFields ( fields , options ) ;
}
2022-06-26 10:28:06 +02:00
dateUntil ( one , two , options ) {
this . dateUntilCallCount ++ ;
assert . sameValue ( Object . getPrototypeOf ( options ) , null , "dateUntil should be called with null-prototype options" ) ;
return super . dateUntil ( one , two , options ) ;
}
}
return new CalendarCheckOptionsPrototypePollution ( ) ;
} ,
2021-09-29 21:48:22 +02:00
/ *
* 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" ;
}
2021-11-24 02:07:59 +01:00
dateAdd ( date , duration , options ) {
2021-09-29 21:48:22 +02:00
this . dateAddCallCount ++ ;
assert . sameValue ( options , undefined , "dateAdd shouldn't be called with options" ) ;
2021-11-24 02:07:59 +01:00
return super . dateAdd ( date , duration , options ) ;
2021-09-29 21:48:22 +02:00
}
}
return new CalendarDateAddUndefinedOptions ( ) ;
} ,
2021-11-24 19:50:26 +01:00
/ *
* 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" ;
}
2023-02-17 21:33:18 +01:00
dateFromFields ( ... args ) {
return super . dateFromFields ( ... args ) . withCalendar ( this ) ;
}
2021-11-24 19:50:26 +01:00
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 } ` ) ;
}
2023-02-17 21:33:18 +01:00
return super . dateAdd ( date , duration , options ) . withCalendar ( this ) ;
2021-11-24 19:50:26 +01:00
}
}
return new CalendarDateAddPlainDateInstance ( ) ;
} ,
2021-09-29 21:48:22 +02:00
/ *
* 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 ( ) ;
} ,
2022-02-02 00:38:24 +01:00
/ *
* 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 ( ) ;
} ,
2021-09-29 21:48:22 +02:00
/ *
* 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 ) ;
} ,
2022-09-30 22:08:36 +02:00
/ *
* crossDateLineTimeZone ( ) :
*
* This returns an instance of a custom time zone class that implements one
* single transition where the time zone moves from one side of the
* International Date Line to the other , for the purpose of testing time zone
* calculations without depending on system time zone data .
*
* The transition occurs at epoch second 1325239200 and goes from offset
* - 10 : 00 to + 14 : 00. In other words , the time zone skips the whole calendar
* day of 2011 - 12 - 30. This is the same as the real - life transition in the
* Pacific / Apia time zone .
* /
crossDateLineTimeZone ( ) {
const { compare } = Temporal . PlainDateTime ;
const skippedDay = new Temporal . PlainDate ( 2011 , 12 , 30 ) ;
const transitionEpoch = 1325239200_000_000_000 n ;
const beforeOffset = new Temporal . TimeZone ( "-10:00" ) ;
const afterOffset = new Temporal . TimeZone ( "+14:00" ) ;
class CrossDateLineTimeZone extends Temporal . TimeZone {
constructor ( ) {
super ( "+14:00" ) ;
}
getOffsetNanosecondsFor ( instant ) {
if ( instant . epochNanoseconds < transitionEpoch ) {
return beforeOffset . getOffsetNanosecondsFor ( instant ) ;
}
return afterOffset . getOffsetNanosecondsFor ( instant ) ;
}
getPossibleInstantsFor ( datetime ) {
const comparison = Temporal . PlainDate . compare ( datetime . toPlainDate ( ) , skippedDay ) ;
if ( comparison === 0 ) {
return [ ] ;
}
if ( comparison < 0 ) {
return [ beforeOffset . getInstantFor ( datetime ) ] ;
}
return [ afterOffset . getInstantFor ( datetime ) ] ;
}
getPreviousTransition ( instant ) {
if ( instant . epochNanoseconds > transitionEpoch ) return new Temporal . Instant ( transitionEpoch ) ;
return null ;
}
getNextTransition ( instant ) {
if ( instant . epochNanoseconds < transitionEpoch ) return new Temporal . Instant ( transitionEpoch ) ;
return null ;
}
toString ( ) {
return "Custom/Date_Line" ;
}
}
return new CrossDateLineTimeZone ( ) ;
} ,
2021-09-29 21:48:22 +02:00
/ *
* 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 .
* /
2022-09-20 21:53:57 +02:00
observeProperty ( calls , object , propertyName , value , objectName = "" ) {
2021-09-29 21:48:22 +02:00
Object . defineProperty ( object , propertyName , {
get ( ) {
2022-09-20 21:53:57 +02:00
calls . push ( ` get ${ formatPropertyName ( propertyName , objectName ) } ` ) ;
2021-09-29 21:48:22 +02:00
return value ;
} ,
set ( v ) {
2022-09-20 21:53:57 +02:00
calls . push ( ` set ${ formatPropertyName ( propertyName , objectName ) } ` ) ;
2021-09-29 21:48:22 +02:00
}
} ) ;
} ,
2022-12-14 03:46:20 +01:00
/ *
* observeMethod ( calls , object , propertyName , value ) :
*
* Defines an own property @ object . @ propertyName with value @ value , that
* will log any calls of @ value to the array @ calls .
* /
observeMethod ( calls , object , propertyName , objectName = "" ) {
const method = object [ propertyName ] ;
object [ propertyName ] = function ( ) {
calls . push ( ` call ${ formatPropertyName ( propertyName , objectName ) } ` ) ;
return method . apply ( object , arguments ) ;
} ;
} ,
2022-11-26 04:20:11 +01:00
/ *
* Used for substituteMethod to indicate default behavior instead of a
* substituted value
* /
SUBSTITUTE _SKIP : SKIP _SYMBOL ,
/ *
* substituteMethod ( object , propertyName , values ) :
*
* Defines an own property @ object . @ propertyName that will , for each
* subsequent call to the method previously defined as
* @ object . @ propertyName :
* - Call the method , if no more values remain
* - Call the method , if the value in @ values for the corresponding call
* is SUBSTITUTE _SKIP
* - Otherwise , return the corresponding value in @ value
* /
substituteMethod ( object , propertyName , values ) {
let calls = 0 ;
const method = object [ propertyName ] ;
object [ propertyName ] = function ( ) {
if ( calls >= values . length ) {
return method . apply ( object , arguments ) ;
} else if ( values [ calls ] === SKIP _SYMBOL ) {
calls ++ ;
return method . apply ( object , arguments ) ;
} else {
return values [ calls ++ ] ;
}
} ;
} ,
2022-09-20 21:45:59 +02:00
/ *
* calendarObserver :
* A custom calendar that behaves exactly like the ISO 8601 calendar but
* tracks calls to any of its methods , and Get / Has operations on its
* properties , by appending messages to an array . This is for the purpose of
* testing order of operations that are observable from user code .
* objectName is used in the log .
* /
calendarObserver ( calls , objectName , methodOverrides = { } ) {
const iso8601 = new Temporal . Calendar ( "iso8601" ) ;
const trackingMethods = {
dateFromFields ( ... args ) {
calls . push ( ` call ${ objectName } .dateFromFields ` ) ;
if ( 'dateFromFields' in methodOverrides ) {
const value = methodOverrides . dateFromFields ;
return typeof value === "function" ? value ( ... args ) : value ;
}
const originalResult = iso8601 . dateFromFields ( ... args ) ;
// Replace the calendar in the result with the call-tracking calendar
const { isoYear , isoMonth , isoDay } = originalResult . getISOFields ( ) ;
const result = new Temporal . PlainDate ( isoYear , isoMonth , isoDay , this ) ;
// Remove the HasProperty check resulting from the above constructor call
assert . sameValue ( calls . pop ( ) , ` has ${ objectName } .calendar ` ) ;
return result ;
} ,
yearMonthFromFields ( ... args ) {
calls . push ( ` call ${ objectName } .yearMonthFromFields ` ) ;
if ( 'yearMonthFromFields' in methodOverrides ) {
const value = methodOverrides . yearMonthFromFields ;
return typeof value === "function" ? value ( ... args ) : value ;
}
const originalResult = iso8601 . yearMonthFromFields ( ... args ) ;
// Replace the calendar in the result with the call-tracking calendar
const { isoYear , isoMonth , isoDay } = originalResult . getISOFields ( ) ;
const result = new Temporal . PlainYearMonth ( isoYear , isoMonth , this , isoDay ) ;
// Remove the HasProperty check resulting from the above constructor call
assert . sameValue ( calls . pop ( ) , ` has ${ objectName } .calendar ` ) ;
return result ;
} ,
monthDayFromFields ( ... args ) {
calls . push ( ` call ${ objectName } .monthDayFromFields ` ) ;
if ( 'monthDayFromFields' in methodOverrides ) {
const value = methodOverrides . monthDayFromFields ;
return typeof value === "function" ? value ( ... args ) : value ;
}
const originalResult = iso8601 . monthDayFromFields ( ... args ) ;
// Replace the calendar in the result with the call-tracking calendar
const { isoYear , isoMonth , isoDay } = originalResult . getISOFields ( ) ;
const result = new Temporal . PlainMonthDay ( isoMonth , isoDay , this , isoYear ) ;
// Remove the HasProperty check resulting from the above constructor call
assert . sameValue ( calls . pop ( ) , ` has ${ objectName } .calendar ` ) ;
return result ;
} ,
dateAdd ( ... args ) {
calls . push ( ` call ${ objectName } .dateAdd ` ) ;
if ( 'dateAdd' in methodOverrides ) {
const value = methodOverrides . dateAdd ;
return typeof value === "function" ? value ( ... args ) : value ;
}
const originalResult = iso8601 . dateAdd ( ... args ) ;
const { isoYear , isoMonth , isoDay } = originalResult . getISOFields ( ) ;
const result = new Temporal . PlainDate ( isoYear , isoMonth , isoDay , this ) ;
// Remove the HasProperty check resulting from the above constructor call
assert . sameValue ( calls . pop ( ) , ` has ${ objectName } .calendar ` ) ;
return result ;
}
} ;
// Automatically generate the other methods that don't need any custom code
2022-11-04 01:24:17 +01:00
[ "toString" , "dateUntil" , "era" , "eraYear" , "year" , "month" , "monthCode" , "day" , "daysInMonth" , "fields" , "mergeFields" ] . forEach ( ( methodName ) => {
2022-09-20 21:45:59 +02:00
trackingMethods [ methodName ] = function ( ... args ) {
2023-02-08 03:48:28 +01:00
calls . push ( ` call ${ formatPropertyName ( methodName , objectName ) } ` ) ;
2022-09-20 21:45:59 +02:00
if ( methodName in methodOverrides ) {
const value = methodOverrides [ methodName ] ;
return typeof value === "function" ? value ( ... args ) : value ;
}
return iso8601 [ methodName ] ( ... args ) ;
} ;
} ) ;
return new Proxy ( trackingMethods , {
get ( target , key , receiver ) {
const result = Reflect . get ( target , key , receiver ) ;
2023-02-08 03:48:28 +01:00
calls . push ( ` get ${ formatPropertyName ( key , objectName ) } ` ) ;
2022-09-20 21:45:59 +02:00
return result ;
} ,
has ( target , key ) {
2023-02-08 03:48:28 +01:00
calls . push ( ` has ${ formatPropertyName ( key , objectName ) } ` ) ;
2022-09-20 21:45:59 +02:00
return Reflect . has ( target , key ) ;
} ,
} ) ;
} ,
2021-09-29 21:48:22 +02:00
/ *
* 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 [ ] ;
2022-02-03 01:42:53 +01:00
return [ instant . subtract ( this . _shift ) ] ;
2021-09-29 21:48:22 +02:00
}
if ( instant . epochNanoseconds < this . _epoch2 ) return [ instant ] ;
2022-02-03 01:42:53 +01:00
const shifted = instant . subtract ( this . _shift ) ;
2021-09-29 21:48:22 +02:00
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 ) ;
} ,
2022-09-20 20:22:00 +02:00
/ *
* propertyBagObserver ( ) :
* Returns an object that behaves like the given propertyBag but tracks Get
* and Has operations on any of its properties , by appending messages to an
* array . If the value of a property in propertyBag is a primitive , the value
* of the returned object ' s property will additionally be a
* TemporalHelpers . toPrimitiveObserver that will track calls to its toString
* and valueOf methods in the same array . This is for the purpose of testing
* order of operations that are observable from user code . objectName is used
* in the log .
* /
propertyBagObserver ( calls , propertyBag , objectName ) {
return new Proxy ( propertyBag , {
ownKeys ( target ) {
calls . push ( ` ownKeys ${ objectName } ` ) ;
return Reflect . ownKeys ( target ) ;
} ,
2022-10-18 02:37:39 +02:00
getOwnPropertyDescriptor ( target , key ) {
calls . push ( ` getOwnPropertyDescriptor ${ formatPropertyName ( key , objectName ) } ` ) ;
return Reflect . getOwnPropertyDescriptor ( target , key ) ;
} ,
2022-09-20 20:22:00 +02:00
get ( target , key , receiver ) {
calls . push ( ` get ${ formatPropertyName ( key , objectName ) } ` ) ;
const result = Reflect . get ( target , key , receiver ) ;
if ( result === undefined ) {
return undefined ;
}
2023-02-24 03:21:41 +01:00
if ( ( result !== null && typeof result === "object" ) || typeof result === "function" ) {
2022-09-20 20:22:00 +02:00
return result ;
}
return TemporalHelpers . toPrimitiveObserver ( calls , result , ` ${ formatPropertyName ( key , objectName ) } ` ) ;
} ,
has ( target , key ) {
calls . push ( ` has ${ formatPropertyName ( key , objectName ) } ` ) ;
return Reflect . has ( target , key ) ;
} ,
} ) ;
} ,
2021-07-16 15:25:55 +02:00
/ *
* 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 ) ;
} ,
2021-09-29 21:48:22 +02:00
/ *
* 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 - 02 T02 : 00
* local ) and goes from offset - 08 : 00 to - 07 : 00.
*
* The fall - back occurs at epoch second 972810000 ( 2000 - 10 - 29 T02 : 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_000 n ;
const fallBackLocal = new Temporal . PlainDateTime ( 2000 , 10 , 29 , 1 ) ;
const fallBackEpoch = 972810000_000_000_000 n ;
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 ( ) ;
} ,
2022-09-20 21:18:21 +02:00
/ *
* timeZoneObserver :
* A custom calendar that behaves exactly like the UTC time zone but tracks
* calls to any of its methods , and Get / Has operations on its properties , by
* appending messages to an array . This is for the purpose of testing order of
* operations that are observable from user code . objectName is used in the
* log . methodOverrides is an optional object containing properties with the
* same name as Temporal . TimeZone methods . If the property value is a function
* it will be called with the proper arguments instead of the UTC method .
* Otherwise , the property value will be returned directly .
* /
timeZoneObserver ( calls , objectName , methodOverrides = { } ) {
const utc = new Temporal . TimeZone ( "UTC" ) ;
const trackingMethods = { } ;
// Automatically generate the methods
[ "getOffsetNanosecondsFor" , "getPossibleInstantsFor" , "toString" ] . forEach ( ( methodName ) => {
trackingMethods [ methodName ] = function ( ... args ) {
2023-02-08 03:48:28 +01:00
calls . push ( ` call ${ formatPropertyName ( methodName , objectName ) } ` ) ;
2022-09-20 21:18:21 +02:00
if ( methodName in methodOverrides ) {
const value = methodOverrides [ methodName ] ;
return typeof value === "function" ? value ( ... args ) : value ;
}
return utc [ methodName ] ( ... args ) ;
} ;
} ) ;
return new Proxy ( trackingMethods , {
get ( target , key , receiver ) {
const result = Reflect . get ( target , key , receiver ) ;
2023-02-08 03:48:28 +01:00
calls . push ( ` get ${ formatPropertyName ( key , objectName ) } ` ) ;
2022-09-20 21:18:21 +02:00
return result ;
} ,
has ( target , key ) {
2023-02-08 03:48:28 +01:00
calls . push ( ` has ${ formatPropertyName ( key , objectName ) } ` ) ;
2022-09-20 21:18:21 +02:00
return Reflect . has ( target , key ) ;
} ,
} ) ;
} ,
2021-09-29 21:48:22 +02:00
/ *
* 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 ( ) ;
} ;
} ,
} ;
} ,
2022-08-18 01:55:22 +02:00
/ *
* An object containing further methods that return arrays of ISO strings , for
* testing parsers .
* /
ISO : {
2022-10-21 23:53:42 +02:00
/ *
* PlainMonthDay strings that are not valid .
* /
plainMonthDayStringsInvalid ( ) {
return [
"11-18junk" ,
] ;
} ,
/ *
* PlainMonthDay strings that are valid and that should produce October 1 st .
* /
plainMonthDayStringsValid ( ) {
return [
"10-01" ,
"1001" ,
"1965-10-01" ,
"1976-10-01T152330.1+00:00" ,
"19761001T15:23:30.1+00:00" ,
"1976-10-01T15:23:30.1+0000" ,
"1976-10-01T152330.1+0000" ,
"19761001T15:23:30.1+0000" ,
"19761001T152330.1+00:00" ,
"19761001T152330.1+0000" ,
"+001976-10-01T152330.1+00:00" ,
"+0019761001T15:23:30.1+00:00" ,
"+001976-10-01T15:23:30.1+0000" ,
"+001976-10-01T152330.1+0000" ,
"+0019761001T15:23:30.1+0000" ,
"+0019761001T152330.1+00:00" ,
"+0019761001T152330.1+0000" ,
"1976-10-01T15:23:00" ,
"1976-10-01T15:23" ,
"1976-10-01T15" ,
"1976-10-01" ,
"--10-01" ,
"--1001" ,
] ;
} ,
2022-08-18 01:55:22 +02:00
/ *
* PlainTime strings that may be mistaken for PlainMonthDay or
* PlainYearMonth strings , and so require a time designator .
* /
plainTimeStringsAmbiguous ( ) {
const ambiguousStrings = [
"2021-12" , // ambiguity between YYYY-MM and HHMM-UU
2022-10-24 21:35:09 +02:00
"2021-12[-12:00]" , // ditto, TZ does not disambiguate
2022-08-18 01:55:22 +02:00
"1214" , // ambiguity between MMDD and HHMM
"0229" , // ditto, including MMDD that doesn't occur every year
"1130" , // ditto, including DD that doesn't occur in every month
"12-14" , // ambiguity between MM-DD and HH-UU
2022-10-24 21:35:09 +02:00
"12-14[-14:00]" , // ditto, TZ does not disambiguate
2022-08-18 01:55:22 +02:00
"202112" , // ambiguity between YYYYMM and HHMMSS
2022-10-24 21:35:09 +02:00
"202112[UTC]" , // ditto, TZ does not disambiguate
2022-08-18 01:55:22 +02:00
] ;
// Adding a calendar annotation to one of these strings must not cause
// disambiguation in favour of time.
const stringsWithCalendar = ambiguousStrings . map ( ( s ) => s + '[u-ca=iso8601]' ) ;
return ambiguousStrings . concat ( stringsWithCalendar ) ;
} ,
/ *
* PlainTime strings that are of similar form to PlainMonthDay and
* PlainYearMonth strings , but are not ambiguous due to components that
* aren ' t valid as months or days .
* /
plainTimeStringsUnambiguous ( ) {
return [
"2021-13" , // 13 is not a month
"202113" , // ditto
"2021-13[-13:00]" , // ditto
"202113[-13:00]" , // ditto
"0000-00" , // 0 is not a month
"000000" , // ditto
"0000-00[UTC]" , // ditto
"000000[UTC]" , // ditto
"1314" , // 13 is not a month
"13-14" , // ditto
"1232" , // 32 is not a day
"0230" , // 30 is not a day in February
"0631" , // 31 is not a day in June
"0000" , // 0 is neither a month nor a day
"00-00" , // ditto
] ;
2022-10-21 23:53:42 +02:00
} ,
/ *
* PlainYearMonth - like strings that are not valid .
* /
plainYearMonthStringsInvalid ( ) {
return [
"2020-13" ,
] ;
} ,
/ *
* PlainYearMonth - like strings that are valid and should produce November
* 1976 in the ISO 8601 calendar .
* /
plainYearMonthStringsValid ( ) {
return [
"1976-11" ,
"1976-11-10" ,
"1976-11-01T09:00:00+00:00" ,
"1976-11-01T00:00:00+05:00" ,
"197611" ,
"+00197611" ,
"1976-11-18T15:23:30.1\u221202:00" ,
"1976-11-18T152330.1+00:00" ,
"19761118T15:23:30.1+00:00" ,
"1976-11-18T15:23:30.1+0000" ,
"1976-11-18T152330.1+0000" ,
"19761118T15:23:30.1+0000" ,
"19761118T152330.1+00:00" ,
"19761118T152330.1+0000" ,
"+001976-11-18T152330.1+00:00" ,
"+0019761118T15:23:30.1+00:00" ,
"+001976-11-18T15:23:30.1+0000" ,
"+001976-11-18T152330.1+0000" ,
"+0019761118T15:23:30.1+0000" ,
"+0019761118T152330.1+00:00" ,
"+0019761118T152330.1+0000" ,
"1976-11-18T15:23" ,
"1976-11-18T15" ,
"1976-11-18" ,
] ;
} ,
/ *
* PlainYearMonth - like strings that are valid and should produce November of
* the ISO year - 9999.
* /
plainYearMonthStringsValidNegativeYear ( ) {
return [
"\u2212009999-11" ,
] ;
} ,
2022-08-18 01:55:22 +02:00
}
2021-07-16 15:25:55 +02:00
} ;