summaryrefslogtreecommitdiffstats
path: root/js/src/tests/test262/built-ins/Temporal
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/tests/test262/built-ins/Temporal')
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/from/calendar-iso-string.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateAdd/argument-propertybag-calendar-iso-string.js29
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateAdd/argument-propertybag-calendar-number.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateAdd/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateAdd/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateUntil/argument-propertybag-calendar-iso-string.js31
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateUntil/argument-string-calendar-annotation-invalid-key.js30
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/day/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/day/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/day/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfYear/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfYear/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfYear/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInMonth/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInMonth/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInMonth/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInWeek/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInWeek/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInWeek/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInYear/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInYear/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInYear/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/inLeapYear/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/inLeapYear/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/inLeapYear/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/month/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/month/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/month/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthCode/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthCode/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthCode/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthsInYear/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthsInYear/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthsInYear/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/weekOfYear/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/weekOfYear/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/weekOfYear/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/weekOfYear/custom-calendar-weekofyear.js26
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/year/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/year/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/year/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Calendar/prototype/yearOfWeek/custom-calendar-weekofyear.js26
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/compare/duration-out-of-range-added-to-relativeto.js30
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/compare/relativeto-propertybag-out-of-range-backward-offset-shift.js51
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/compare/relativeto-propertybag-out-of-range-forward-offset-shift.js46
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/from/argument-duration-max.js2
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/calendar-dateadd-called-with-options-undefined.js2
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/differencezoneddatetime-inconsistent-custom-calendar.js55
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/normalized-time-duration-to-days-loop-arbitrarily.js78
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/order-of-operations.js12
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-backward-offset-shift.js50
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-forward-offset-shift.js45
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js146
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/dst-rounding-result.js48
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/duration-out-of-range-added-to-relativeto.js45
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/normalized-time-duration-to-days-loop-arbitrarily.js88
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/out-of-range-when-adjusting-rounded-days.js32
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/out-of-range-when-converting-from-normalized-duration.js2
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-backward-offset-shift.js50
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-forward-offset-shift.js45
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js20
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/calendar-dateadd-called-with-options-undefined.js2
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/differencezoneddatetime-inconsistent-custom-calendar.js55
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/normalized-time-duration-to-days-loop-arbitrarily.js78
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/order-of-operations.js12
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-backward-offset-shift.js50
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-forward-offset-shift.js45
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js144
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/dst-rounding-result.js40
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/duration-out-of-range-added-to-relativeto.js45
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/normalized-time-duration-to-days-loop-arbitrarily.js86
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-3.js2
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-backward-offset-shift.js50
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-forward-offset-shift.js45
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js20
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Instant/compare/argument-string-calendar-annotation-invalid-key.js32
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Instant/from/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Instant/prototype/equals/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Instant/prototype/since/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Instant/prototype/toZonedDateTime/calendar-iso-string.js27
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Instant/prototype/until/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Now/plainDate/calendar-iso-string.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Now/plainDateTime/calendar-iso-string.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Now/zonedDateTime/calendar-iso-string.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/compare/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/compare/argument-string-calendar-annotation-invalid-key.js30
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/from/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/from/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/from/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/equals/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/equals/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/equals/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/since/argument-propertybag-calendar-iso-string.js29
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/since/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/since/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/since/wrapping-at-end-of-month.js120
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toPlainDateTime/argument-string-calendar-annotation-invalid-key.js31
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/argument-string-calendar-annotation-invalid-key.js31
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js44
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js53
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-iso-string.js29
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-higher-units.js10
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/wrapping-at-end-of-month.js120
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/weekOfYear/custom-calendar-weekofyear.js26
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/weekOfYear/validate-calendar-value.js1
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/withCalendar/calendar-iso-string.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/yearOfWeek/custom-calendar-weekofyear.js26
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/yearOfWeek/validate-calendar-value.js1
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/compare/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/compare/argument-string-calendar-annotation-invalid-key.js30
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/from/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/from/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/from/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/equals/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/equals/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/equals/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/argument-propertybag-calendar-iso-string.js29
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/argument-propertybag-calendar-number.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/result-mixed-sign.js29
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/wrapping-at-end-of-month.js120
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js44
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js53
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/argument-propertybag-calendar-iso-string.js29
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/argument-propertybag-calendar-number.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/no-unnecessary-units.js8
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/result-mixed-sign.js29
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/wrapping-at-end-of-month.js120
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/weekOfYear/custom-calendar-weekofyear.js26
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/weekOfYear/validate-calendar-value.js1
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withCalendar/calendar-iso-string.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-propertybag-calendar-iso-string.js29
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-propertybag-calendar-number.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainTime/argument-string-calendar-annotation-invalid-key.js31
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/yearOfWeek/custom-calendar-weekofyear.js26
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/yearOfWeek/validate-calendar-value.js1
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainMonthDay/from/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainMonthDay/from/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainMonthDay/from/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainMonthDay/prototype/equals/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainMonthDay/prototype/equals/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainMonthDay/prototype/equals/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainMonthDay/prototype/toPlainDate/calendar-fromfields-called-with-undefined-options.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainTime/compare/argument-string-calendar-annotation-invalid-key.js36
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainTime/from/argument-string-calendar-annotation-invalid-key.js31
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/equals/argument-string-calendar-annotation-invalid-key.js31
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/since/argument-string-calendar-annotation-invalid-key.js31
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-propertybag-calendar-iso-string.js29
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-propertybag-calendar-number.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js44
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js53
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/until/argument-string-calendar-annotation-invalid-key.js31
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainYearMonth/compare/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainYearMonth/compare/argument-string-calendar-annotation-invalid-key.js30
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainYearMonth/from/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainYearMonth/from/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainYearMonth/from/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/equals/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/equals/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/equals/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/since/argument-propertybag-calendar-iso-string.js29
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/since/argument-propertybag-calendar-number.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/since/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/since/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/toPlainDate/calendar-fromfields-called-with-undefined-options.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/until/argument-propertybag-calendar-iso-string.js29
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/until/argument-propertybag-calendar-number.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/until/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/until/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/getoffsetnanosecondsfor-maximum-forward-offset-shift.js51
-rw-r--r--js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js46
-rw-r--r--js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/getpossibleinstantsfor-maximum-backward-offset-shift.js55
-rw-r--r--js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/getpossibleinstantsfor-out-of-range-backward-offset-shift.js50
-rw-r--r--js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getNextTransition/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getOffsetStringFor/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/calendar-iso-string.js27
-rw-r--r--js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-propertybag-calendar-iso-string.js29
-rw-r--r--js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPreviousTransition/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-calendar-iso-string.js30
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-backward-offset-shift.js51
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-forward-offset-shift.js46
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-string-calendar-annotation-invalid-key.js32
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-calendar-iso-string.js28
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-backward-offset-shift.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-forward-offset-shift.js44
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-string-calendar-annotation-invalid-key.js32
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-calendar-iso-string.js29
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-backward-offset-shift.js50
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-forward-offset-shift.js45
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-string-calendar-annotation-invalid-key.js25
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js44
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-maximum-backward-offset-shift.js53
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/div-zero.js8
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getoffsetnanosecondsfor-maximum-forward-offset-shift.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-called-with-iso8601-calendar.js4
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-maximum-backward-offset-shift.js53
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-out-of-range-backward-offset-shift.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/order-of-operations.js42
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/smallest-unit-day-rounding-modes.js47
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/timezone-getpossibleinstantsfor-iterable.js2
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-iso-string.js30
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-backward-offset-shift.js50
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-forward-offset-shift.js45
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-string-calendar-annotation-invalid-key.js26
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/calendar-dateadd-called-with-options-undefined.js16
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/differencezoneddatetime-inconsistent-custom-calendar.js56
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-month-day-boundary.js29
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-rounding-result.js48
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/find-intermediate-instant.js84
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js86
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js51
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/order-of-operations.js15
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/wrapping-at-end-of-month.js120
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js44
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-maximum-backward-offset-shift.js53
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-calendar-iso-string.js30
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-backward-offset-shift.js50
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-forward-offset-shift.js45
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-string-calendar-annotation-invalid-key.js26
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/calendar-dateadd-called-with-options-undefined.js16
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/differencezoneddatetime-inconsistent-custom-calendar.js56
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/dst-month-day-boundary.js29
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/dst-rounding-result.js48
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/find-intermediate-instant.js85
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-loop-arbitrarily.js86
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-range-errors.js51
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/order-of-operations.js15
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/wrapping-at-end-of-month.js120
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/weekOfYear/custom-calendar-weekofyear.js27
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/weekOfYear/validate-calendar-value.js1
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-maximum-forward-offset-shift.js52
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js46
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-maximum-backward-offset-shift.js56
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-out-of-range-backward-offset-shift.js51
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withCalendar/calendar-iso-string.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-propertybag-calendar-iso-string.js29
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-string-calendar-annotation-invalid-key.js26
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-maximum-forward-offset-shift.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js44
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-maximum-backward-offset-shift.js53
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-out-of-range-backward-offset-shift.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/argument-string-calendar-annotation-invalid-key.js32
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js44
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-maximum-backward-offset-shift.js53
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js49
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/custom-calendar-weekofyear.js27
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/validate-calendar-value.js1
-rw-r--r--js/src/tests/test262/built-ins/Temporal/shell.js8
289 files changed, 8204 insertions, 974 deletions
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/from/calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/Calendar/from/calendar-iso-string.js
new file mode 100644
index 0000000000..1b8ebe9921
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/from/calendar-iso-string.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.from
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+for (const arg of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const result = Temporal.Calendar.from(arg);
+ assert.sameValue(result.id, "iso8601", `Calendar created from string "${arg}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateAdd/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateAdd/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..70e4e25b5f
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateAdd/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.dateadd
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.dateAdd(arg, new Temporal.Duration());
+ TemporalHelpers.assertPlainDate(result, 1976, 11, "M11", 18, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateAdd/argument-propertybag-calendar-number.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateAdd/argument-propertybag-calendar-number.js
index 80b3fcd76f..736c6dd4a9 100644
--- a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateAdd/argument-propertybag-calendar-number.js
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateAdd/argument-propertybag-calendar-number.js
@@ -8,7 +8,7 @@ description: A number as calendar in a property bag is not accepted
features: [Temporal]
---*/
-const instance = new Temporal.PlainDate(1976, 11, 18);
+const instance = new Temporal.Calendar("iso8601");
const numbers = [
1,
@@ -16,6 +16,7 @@ const numbers = [
-19970327,
1234567890,
];
+
for (const calendar of numbers) {
const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
assert.throws(
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateAdd/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateAdd/argument-propertybag-calendar-year-zero.js
index 2a93ec4ea9..2724dbf8cc 100644
--- a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateAdd/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateAdd/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.Calendar("iso8601");
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.dateAdd(arg, new Temporal.Duration()),
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateAdd/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateAdd/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..c67b09ebcc
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateAdd/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.dateadd
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.dateAdd(arg, new Temporal.Duration()),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateUntil/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateUntil/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..1afad6f499
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateUntil/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.dateuntil
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result1 = instance.dateUntil(arg, new Temporal.PlainDate(1976, 11, 19));
+ TemporalHelpers.assertDuration(result1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, `Calendar created from string "${calendar}" (first argument)`);
+ const result2 = instance.dateUntil(new Temporal.PlainDate(1976, 11, 19), arg);
+ TemporalHelpers.assertDuration(result2, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, `Calendar created from string "${calendar}" (second argument)`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateUntil/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateUntil/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..23b19e9bfc
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateUntil/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.dateuntil
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.dateUntil(arg, new Temporal.PlainDate(1977, 11, 19)),
+ `annotation keys must be lowercase: ${arg} - ${descr} (first argument)`
+ );
+ assert.throws(
+ RangeError,
+ () => instance.dateUntil(new Temporal.PlainDate(1977, 11, 19), arg),
+ `annotation keys must be lowercase: ${arg} - ${descr} (second argument)`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/day/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/day/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..47be8c3a73
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/day/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.day
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.day(arg);
+ assert.sameValue(result, 18, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/day/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/day/argument-propertybag-calendar-year-zero.js
index 454c6a68de..6338e62891 100644
--- a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/day/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/day/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.Calendar("iso8601");
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.day(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/day/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/day/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..d377a25e9d
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/day/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.day
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.day(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..c9f21ef479
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.dayofweek
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.dayOfWeek(arg);
+ assert.sameValue(result, 4, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-propertybag-calendar-year-zero.js
index 767b87d15d..0f344803c2 100644
--- a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.Calendar("iso8601");
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.dayOfWeek(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..34093a68f3
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.dayofweek
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.dayOfWeek(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfYear/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfYear/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..5195cd886d
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfYear/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.dayofyear
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.dayOfYear(arg);
+ assert.sameValue(result, 323, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfYear/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfYear/argument-propertybag-calendar-year-zero.js
index 7f06c31917..88fa0c5943 100644
--- a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfYear/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfYear/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.Calendar("iso8601");
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.dayOfYear(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfYear/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfYear/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..f2d9dad0fa
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfYear/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.dayofyear
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.dayOfYear(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInMonth/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInMonth/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..43fc09859c
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInMonth/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.daysinmonth
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.daysInMonth(arg);
+ assert.sameValue(result, 30, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInMonth/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInMonth/argument-propertybag-calendar-year-zero.js
index 8a6f651473..b6cd325c2e 100644
--- a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInMonth/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInMonth/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.Calendar("iso8601");
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.daysInMonth(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInMonth/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInMonth/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..c3db32b57b
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInMonth/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.daysinmonth
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.daysInMonth(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInWeek/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInWeek/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..48ca5287e4
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInWeek/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.daysinweek
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.daysInWeek(arg);
+ assert.sameValue(result, 7, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInWeek/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInWeek/argument-propertybag-calendar-year-zero.js
index 7d8395c074..6330524f58 100644
--- a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInWeek/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInWeek/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.Calendar("iso8601");
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.daysInWeek(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInWeek/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInWeek/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..6dec3595a1
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInWeek/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.daysinweek
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.daysInWeek(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInYear/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInYear/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..436cb1d1ea
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInYear/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.daysinyear
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.daysInYear(arg);
+ assert.sameValue(result, 366, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInYear/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInYear/argument-propertybag-calendar-year-zero.js
index 18d81a4c17..ce5d9ad77d 100644
--- a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInYear/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInYear/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.Calendar("iso8601");
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.daysInYear(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInYear/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInYear/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..0464d68688
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInYear/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.daysinyear
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.daysInYear(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/inLeapYear/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/inLeapYear/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..9cb59aa80a
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/inLeapYear/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.inleapyear
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.inLeapYear(arg);
+ assert.sameValue(result, true, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/inLeapYear/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/inLeapYear/argument-propertybag-calendar-year-zero.js
index 0835539959..a82d22bbfe 100644
--- a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/inLeapYear/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/inLeapYear/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.Calendar("iso8601");
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.inLeapYear(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/inLeapYear/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/inLeapYear/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..075c05f59a
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/inLeapYear/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.inleapyear
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.inLeapYear(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/month/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/month/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..a9c95173bc
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/month/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.month
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.month(arg);
+ assert.sameValue(result, 11, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/month/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/month/argument-propertybag-calendar-year-zero.js
index 81bd484995..86b60ce29c 100644
--- a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/month/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/month/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.Calendar("iso8601");
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.month(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/month/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/month/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..9a166ae68b
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/month/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.month
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.month(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthCode/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthCode/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..b4106f48e5
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthCode/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.monthcode
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.monthCode(arg);
+ assert.sameValue(result, "M11", `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthCode/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthCode/argument-propertybag-calendar-year-zero.js
index 24178dfdb9..c07ef5ec26 100644
--- a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthCode/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthCode/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.Calendar("iso8601");
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.monthCode(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthCode/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthCode/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..69e854b1cd
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthCode/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.monthcode
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.monthCode(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthsInYear/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthsInYear/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..bf5ce2c86a
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthsInYear/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.monthsinyear
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.monthsInYear(arg);
+ assert.sameValue(result, 12, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthsInYear/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthsInYear/argument-propertybag-calendar-year-zero.js
index c05540c245..517784b934 100644
--- a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthsInYear/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthsInYear/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.Calendar("iso8601");
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.monthsInYear(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthsInYear/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthsInYear/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..75cd954103
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthsInYear/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.monthsinyear
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.monthsInYear(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/weekOfYear/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/weekOfYear/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..97fa05fdca
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/weekOfYear/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.weekofyear
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.weekOfYear(arg);
+ assert.sameValue(result, 47, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/weekOfYear/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/weekOfYear/argument-propertybag-calendar-year-zero.js
index 023b2ca730..3465289d38 100644
--- a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/weekOfYear/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/weekOfYear/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.Calendar("iso8601");
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.weekOfYear(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/weekOfYear/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/weekOfYear/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..f32b39605e
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/weekOfYear/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.weekofyear
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.weekOfYear(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/weekOfYear/custom-calendar-weekofyear.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/weekOfYear/custom-calendar-weekofyear.js
new file mode 100644
index 0000000000..c7f69b11d8
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/weekOfYear/custom-calendar-weekofyear.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.weekofyear
+description: >
+ Temporal.Calendar.prototype.weekOfYear returns undefined for all
+ custom calendars where weekOfYear() returns undefined.
+features: [Temporal]
+---*/
+
+class CustomCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ weekOfYear() {
+ return undefined;
+ }
+}
+
+const calendar = new CustomCalendar();
+const customCalendarDate = { month: 1, day: 1, year: 2024, calendar};
+assert.sameValue(calendar.weekOfYear({...customCalendarDate}), undefined);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/year/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/year/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..d56ec21d0b
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/year/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.year
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.year(arg);
+ assert.sameValue(result, 1976, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/year/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/year/argument-propertybag-calendar-year-zero.js
index 742326e600..81e2cc5f75 100644
--- a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/year/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/year/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.Calendar("iso8601");
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.year(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/year/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/year/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..ab698cfd86
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/year/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.year
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.year(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..cf2ffb511d
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.yearofweek
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.yearOfWeek(arg);
+ assert.sameValue(result, 1976, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-propertybag-calendar-year-zero.js
index ad33245788..d3a054c682 100644
--- a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.Calendar("iso8601");
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.yearOfWeek(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..2293852bdc
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.yearofweek
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.yearOfWeek(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/yearOfWeek/custom-calendar-weekofyear.js b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/yearOfWeek/custom-calendar-weekofyear.js
new file mode 100644
index 0000000000..6d618ae608
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Calendar/prototype/yearOfWeek/custom-calendar-weekofyear.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.yearofweek
+description: >
+ Temporal.Calendar.prototype.yearOfWeek returns undefined for all
+ custom calendars where yearOfWeek() returns undefined.
+features: [Temporal]
+---*/
+
+class CustomCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ yearOfWeek() {
+ return undefined;
+ }
+}
+
+const calendar = new CustomCalendar();
+const customCalendarDate = { month: 1, day: 1, year: 2024, calendar};
+assert.sameValue(calendar.yearOfWeek({...customCalendarDate}), undefined);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/compare/duration-out-of-range-added-to-relativeto.js b/js/src/tests/test262/built-ins/Temporal/Duration/compare/duration-out-of-range-added-to-relativeto.js
new file mode 100644
index 0000000000..cb8c1157d7
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/compare/duration-out-of-range-added-to-relativeto.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.compare
+description: RangeError thrown when calendar part of duration added to relativeTo is out of range
+features: [Temporal]
+info: |
+ UnbalanceDateDurationRelative:
+ 11. Let _yearsMonthsWeeksDuration_ be ! CreateTemporalDuration(_years_, _months_, _weeks_, 0, 0, 0, 0, 0, 0, 0).
+ 12. Let _later_ be ? CalendarDateAdd(_calendaRec_, _plainRelativeTo_, _yearsMonthsWeeksDuration_).
+ 13. Let _yearsMonthsWeeksInDays_ be DaysUntil(_plainRelativeTo_, _later_).
+ 14. Return ? CreateDateDurationRecord(0, 0, 0, _days_ + _yearsMonthsWeeksInDays_).
+---*/
+
+// Based on a test case by André Bargull <andre.bargull@gmail.com>
+
+const relativeTo = new Temporal.PlainDate(2000, 1, 1);
+const zero = new Temporal.Duration();
+
+const instance = new Temporal.Duration(0, 0, /* weeks = */ 1, /* days = */ Math.trunc((2 ** 53) / 86_400));
+assert.throws(RangeError, () => Temporal.Duration.compare(instance, zero, {relativeTo}), "weeks + days out of range, positive, first argument");
+assert.throws(RangeError, () => Temporal.Duration.compare(zero, instance, {relativeTo}), "weeks + days out of range, positive, second argument");
+
+const negInstance = new Temporal.Duration(0, 0, /* weeks = */ -1, /* days = */ -Math.trunc((2 ** 53) / 86_400));
+assert.throws(RangeError, () => Temporal.Duration.compare(negInstance, zero, {relativeTo}), "weeks + days out of range, negative, first argument");
+assert.throws(RangeError, () => Temporal.Duration.compare(zero, negInstance, {relativeTo}), "weeks + days out of range, negative, second argument");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/compare/relativeto-propertybag-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/Duration/compare/relativeto-propertybag-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..98fb6b5ef9
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/compare/relativeto-propertybag-out-of-range-backward-offset-shift.js
@@ -0,0 +1,51 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.compare
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+const duration1 = new Temporal.Duration(1);
+const duration2 = new Temporal.Duration(2);
+
+assert.throws(RangeError, () => Temporal.Duration.compare(duration1, duration2, {relativeTo: relativeTo}), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/compare/relativeto-propertybag-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/Duration/compare/relativeto-propertybag-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..cbfca704c2
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/compare/relativeto-propertybag-out-of-range-forward-offset-shift.js
@@ -0,0 +1,46 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.compare
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+const duration1 = new Temporal.Duration(1);
+const duration2 = new Temporal.Duration(2);
+
+assert.throws(RangeError, () => Temporal.Duration.compare(duration1, duration2, {relativeTo: relativeTo}), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/from/argument-duration-max.js b/js/src/tests/test262/built-ins/Temporal/Duration/from/argument-duration-max.js
index 0a4043d391..af9f721949 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/from/argument-duration-max.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/from/argument-duration-max.js
@@ -11,7 +11,7 @@ features: [Temporal]
const maxCases = [
["P4294967295Y104249991374DT7H36M31.999999999S", "string with max years"],
[{ years: 4294967295, days: 104249991374, nanoseconds: 27391999999999 }, "property bag with max years"],
- ["P4294967295M104249991374DT7H36M31.999999999S", "string with max weeks"],
+ ["P4294967295M104249991374DT7H36M31.999999999S", "string with max months"],
[{ months: 4294967295, days: 104249991374, nanoseconds: 27391999999999 }, "property bag with max months"],
["P4294967295W104249991374DT7H36M31.999999999S", "string with max weeks"],
[{ weeks: 4294967295, days: 104249991374, nanoseconds: 27391999999999 }, "property bag with max weeks"],
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/calendar-dateadd-called-with-options-undefined.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/calendar-dateadd-called-with-options-undefined.js
index 4955abacdd..84079298ce 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/calendar-dateadd-called-with-options-undefined.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/calendar-dateadd-called-with-options-undefined.js
@@ -15,6 +15,6 @@ const calendar = TemporalHelpers.calendarDateAddUndefinedOptions();
const timeZone = TemporalHelpers.oneShiftTimeZone(new Temporal.Instant(0n), 3600e9);
const instance = new Temporal.Duration(1, 1, 1, 1);
instance.add(instance, { relativeTo: new Temporal.ZonedDateTime(0n, timeZone, calendar) });
-assert.sameValue(calendar.dateAddCallCount, 3);
+assert.sameValue(calendar.dateAddCallCount, 2);
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/differencezoneddatetime-inconsistent-custom-calendar.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/differencezoneddatetime-inconsistent-custom-calendar.js
new file mode 100644
index 0000000000..01bf6c756e
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/differencezoneddatetime-inconsistent-custom-calendar.js
@@ -0,0 +1,55 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.duration.prototype.add
+description: >
+ Throws a RangeError when custom calendar method returns inconsistent result
+info: |
+ DifferenceZonedDateTime ( ... )
+ 8. Repeat 3 times:
+ ...
+ g. If _sign_ = 0, or _timeSign_ = 0, or _sign_ = _timeSign_, then
+ ...
+ viii. Return ? CreateNormalizedDurationRecord(_dateDifference_.[[Years]],
+ _dateDifference_.[[Months]], _dateDifference_.[[Weeks]],
+ _dateDifference_.[[Days]], _norm_).
+ h. Set _dayCorrection_ to _dayCorrection_ + 1.
+ 9. NOTE: This step is only reached when custom calendar or time zone methods
+ return inconsistent values.
+ 10. Throw a *RangeError* exception.
+features: [Temporal]
+---*/
+
+// Based partly on a test case by André Bargull
+
+const duration1 = new Temporal.Duration(0, 0, /* weeks = */ 7, 0, /* hours = */ 12);
+const duration2 = new Temporal.Duration(0, 0, 0, /* days = */ 1);
+
+{
+ const tz = new (class extends Temporal.TimeZone {
+ getPossibleInstantsFor(dateTime) {
+ return super.getPossibleInstantsFor(dateTime.add({ days: 3 }));
+ }
+ })("UTC");
+
+ const relativeTo = new Temporal.ZonedDateTime(0n, tz);
+
+ assert.throws(RangeError, () => duration1.add(duration2, { relativeTo }),
+ "Calendar calculation where more than 2 days correction is needed should cause RangeError");
+}
+
+{
+ const cal = new (class extends Temporal.Calendar {
+ dateUntil(one, two, options) {
+ return super.dateUntil(one, two, options).negated();
+ }
+ })("iso8601");
+
+ const relativeTo = new Temporal.ZonedDateTime(0n, "UTC", cal);
+
+ assert.throws(RangeError, () => duration1.add(duration2, { relativeTo }),
+ "Calendar calculation causing mixed-sign values should cause RangeError");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/normalized-time-duration-to-days-loop-arbitrarily.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/normalized-time-duration-to-days-loop-arbitrarily.js
deleted file mode 100644
index 62833e87c6..0000000000
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/normalized-time-duration-to-days-loop-arbitrarily.js
+++ /dev/null
@@ -1,78 +0,0 @@
-// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
-// Copyright (C) 2022 Igalia, S.L. All rights reserved.
-// This code is governed by the BSD license found in the LICENSE file.
-
-/*---
-esid: sec-temporal.duration.prototype.add
-description: >
- NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer
-info: |
- NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
- ...
- 21. Repeat, while done is false,
- a. Let oneDayFarther be ? AddDaysToZonedDateTime(relativeResult.[[Instant]],
- relativeResult.[[DateTime]], timeZoneRec, zonedRelativeTo.[[Calendar]], sign).
- b. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(oneDayFarther.[[EpochNanoseconds]],
- relativeResult.[[EpochNanoseconds]]).
- c. Let oneDayLess be ? SubtractNormalizedTimeDuration(norm, dayLengthNs).
- c. If NormalizedTimeDurationSign(oneDayLess) × sign ≥ 0, then
- i. Set norm to oneDayLess.
- ii. Set relativeResult to oneDayFarther.
- iii. Set days to days + sign.
- d. Else,
- i. Set done to true.
-includes: [temporalHelpers.js]
-features: [Temporal]
----*/
-
-const calls = [];
-const duration = Temporal.Duration.from({ days: 1 });
-
-function createRelativeTo(count) {
- const dayLengthNs = 86400000000000n;
- const dayInstant = new Temporal.Instant(dayLengthNs);
- const substitutions = [];
- const timeZone = new Temporal.TimeZone("UTC");
- // Return constant value for first _count_ calls
- TemporalHelpers.substituteMethod(
- timeZone,
- "getPossibleInstantsFor",
- substitutions
- );
- substitutions.length = count;
- let i = 0;
- for (i = 0; i < substitutions.length; i++) {
- // (this value)
- substitutions[i] = [dayInstant];
- }
- // Record calls in calls[]
- TemporalHelpers.observeMethod(calls, timeZone, "getPossibleInstantsFor");
- return new Temporal.ZonedDateTime(0n, timeZone);
-}
-
-let zdt = createRelativeTo(50);
-calls.splice(0); // Reset calls list after ZonedDateTime construction
-duration.add(duration, {
- relativeTo: zdt,
-});
-assert.sameValue(
- calls.length,
- 50 + 1,
- "Expected duration.add to call getPossibleInstantsFor correct number of times"
-);
-
-zdt = createRelativeTo(100);
-calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
-duration.add(duration, {
- relativeTo: zdt,
-});
-assert.sameValue(
- calls.length,
- 100 + 1,
- "Expected duration.add to call getPossibleInstantsFor correct number of times"
-);
-
-zdt = createRelativeTo(107);
-assert.throws(RangeError, () => duration.add(duration, { relativeTo: zdt }), "107-2 days > 2⁵³ ns");
-
-reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/order-of-operations.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/order-of-operations.js
index a530ba55e9..c3a2f2c4a9 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/order-of-operations.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/order-of-operations.js
@@ -339,18 +339,8 @@ const expectedOpsForZonedRelativeTo = expected.concat([
"call options.relativeTo.timeZone.getPossibleInstantsFor",
// AddDuration → DifferenceZonedDateTime
"call options.relativeTo.timeZone.getOffsetNanosecondsFor",
- // AddDuration → DifferenceZonedDateTime → DifferenceISODateTime
- "call options.relativeTo.calendar.dateUntil",
- // AddDuration → DifferenceZonedDateTime → AddZonedDateTime
- "call options.relativeTo.calendar.dateAdd",
- "call options.relativeTo.timeZone.getPossibleInstantsFor",
- // AddDuration → DifferenceZonedDateTime → NanosecondsToDays
- "call options.relativeTo.timeZone.getOffsetNanosecondsFor",
- "call options.relativeTo.timeZone.getOffsetNanosecondsFor",
- // AddDuration → DifferenceZonedDateTime → NanosecondsToDays → AddZonedDateTime 1
- "call options.relativeTo.timeZone.getPossibleInstantsFor",
- // AddDuration → DifferenceZonedDateTime → NanosecondsToDays → AddZonedDateTime 2
"call options.relativeTo.timeZone.getPossibleInstantsFor",
+ "call options.relativeTo.calendar.dateUntil",
]);
const zonedRelativeTo = TemporalHelpers.propertyBagObserver(actual, {
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..a8506addba
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-backward-offset-shift.js
@@ -0,0 +1,50 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.add
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.Duration(1, 0, 0, 1);
+assert.throws(RangeError, () => instance.add(new Temporal.Duration(0, 0, 0, 0, -24), { relativeTo }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..705327b70e
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-forward-offset-shift.js
@@ -0,0 +1,45 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.add
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.Duration(1, 0, 0, 1);
+assert.throws(RangeError, () => instance.add(new Temporal.Duration(0, 0, 0, 0, -24), { relativeTo }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js
deleted file mode 100644
index 7acd5929b6..0000000000
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js
+++ /dev/null
@@ -1,146 +0,0 @@
-// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
-// Copyright (C) 2022 Igalia, S.L. All rights reserved.
-// This code is governed by the BSD license found in the LICENSE file.
-/*---
-esid: sec-temporal.duration.prototype.add
-description: >
- Abstract operation NormalizedTimeDurationToDays can throw four different
- RangeErrors.
-info: |
- NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
- 22. If days < 0 and sign = 1, throw a RangeError exception.
- 23. If days > 0 and sign = -1, throw a RangeError exception.
- ...
- 25. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception.
- ...
- 28. If dayLength ≥ 2⁵³, throw a RangeError exception.
-features: [Temporal, BigInt]
-includes: [temporalHelpers.js]
----*/
-
-const dayNs = 86_400_000_000_000;
-const dayDuration = Temporal.Duration.from({ days: 1 });
-const epochInstant = new Temporal.Instant(0n);
-
-function timeZoneSubstituteValues(
- getPossibleInstantsFor,
- getOffsetNanosecondsFor
-) {
- const tz = new Temporal.TimeZone("UTC");
- TemporalHelpers.substituteMethod(
- tz,
- "getPossibleInstantsFor",
- getPossibleInstantsFor
- );
- TemporalHelpers.substituteMethod(
- tz,
- "getOffsetNanosecondsFor",
- getOffsetNanosecondsFor
- );
- return tz;
-}
-
-// Step 22: days < 0 and sign = 1
-let zdt = new Temporal.ZonedDateTime(
- -1n, // Set DifferenceZonedDateTime _ns1_
- timeZoneSubstituteValues(
- [
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for first call, AddDuration step 15
- [epochInstant], // Returned in AddDuration step 16, setting _endNs_ -> DifferenceZonedDateTime _ns2_
- [epochInstant], // Returned in step 16, setting _relativeResult_
- ],
- [
- // Behave normally in 3 calls made prior to NormalizedTimeDurationToDays
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- dayNs - 1, // Returned in step 8, setting _startDateTime_
- -dayNs + 1, // Returned in step 9, setting _endDateTime_
- ]
- )
-);
-assert.throws(RangeError, () =>
- // Adding day to day sets largestUnit to 'day', avoids having any week/month/year components in differences
- dayDuration.add(dayDuration, {
- relativeTo: zdt,
- }),
- "days < 0 and sign = 1"
-);
-
-// Step 23: days > 0 and sign = -1
-zdt = new Temporal.ZonedDateTime(
- 1n, // Set DifferenceZonedDateTime _ns1_
- timeZoneSubstituteValues(
- [
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for first call, AddDuration step 15
- [epochInstant], // Returned in AddDuration step 16, setting _endNs_ -> DifferenceZonedDateTime _ns2_
- [epochInstant], // Returned in step 16, setting _relativeResult_
- ],
- [
- // Behave normally in 3 calls made prior to NanosecondsToDays
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- -dayNs + 1, // Returned in step 8, setting _startDateTime_
- dayNs - 1, // Returned in step 9, setting _endDateTime_
- ]
- )
-);
-assert.throws(RangeError, () =>
- // Adding day to day sets largestUnit to 'day', avoids having any week/month/year components in differences
- dayDuration.add(dayDuration, {
- relativeTo: zdt,
- }),
- "days > 0 and sign = -1"
-);
-
-// Step 25: nanoseconds > 0 and sign = -1
-zdt = new Temporal.ZonedDateTime(
- 0n, // Set DifferenceZonedDateTime _ns1_
- timeZoneSubstituteValues(
- [
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for first call, AddDuration step 15
- [new Temporal.Instant(-1n)], // Returned in AddDuration step 16, setting _endNs_ -> DifferenceZonedDateTime _ns2_
- [new Temporal.Instant(-2n)], // Returned in step 16, setting _relativeResult_
- [new Temporal.Instant(-4n)], // Returned in step 21.a, setting _oneDayFarther_
- ],
- [
- // Behave normally in 3 calls made prior to NanosecondsToDays
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- dayNs - 1, // Returned in step 8, setting _startDateTime_
- -dayNs + 1, // Returned in step 9, setting _endDateTime_
- ]
- )
-);
-assert.throws(RangeError, () =>
- // Adding day to day sets largestUnit to 'day', avoids having any week/month/year components in differences
- dayDuration.add(dayDuration, {
- relativeTo: zdt,
- }),
- "nanoseconds > 0 and sign = -1"
-);
-
-// Step 28: day length is an unsafe integer
-zdt = new Temporal.ZonedDateTime(
- 0n,
- timeZoneSubstituteValues(
- [
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for AddDuration step 15
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for AddDuration step 16
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for step 16, setting _relativeResult_
- // Returned in step 21.a, making _oneDayFarther_ 2^53 ns later than _relativeResult_
- [new Temporal.Instant(2n ** 53n + 2n * BigInt(dayNs))],
- ],
- []
- )
-);
-assert.throws(RangeError, () =>
- dayDuration.add(dayDuration, {
- relativeTo: zdt,
- }),
- "Should throw RangeError when time zone calculates an outrageous day length"
-);
-
-reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/dst-rounding-result.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/dst-rounding-result.js
new file mode 100644
index 0000000000..daff4e93f6
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/dst-rounding-result.js
@@ -0,0 +1,48 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.duration.prototype.round
+description: >
+ Rounding the resulting duration takes the time zone's UTC offset shifts
+ into account
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const timeZone = TemporalHelpers.springForwardFallBackTimeZone();
+
+// Based on a test case by Adam Shaw
+
+{
+ // Date part of duration lands on skipped DST hour, causing disambiguation
+ const duration = new Temporal.Duration(0, 1, 0, 15, 12);
+ const relativeTo = new Temporal.ZonedDateTime(
+ 950868000_000_000_000n /* = 2000-02-18T10Z */,
+ timeZone); /* = 2000-02-18T02-08 in local time */
+
+ TemporalHelpers.assertDuration(duration.round({ smallestUnit: "months", relativeTo }),
+ 0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
+ "1 month 15 days 12 hours should be exactly 1.5 months, which rounds up to 2 months");
+ TemporalHelpers.assertDuration(duration.round({ smallestUnit: "months", roundingMode: 'halfTrunc', relativeTo }),
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ "1 month 15 days 12 hours should be exactly 1.5 months, which rounds down to 1 month");
+}
+
+{
+ // Month-only part of duration lands on skipped DST hour, should not cause
+ // disambiguation
+ const duration = new Temporal.Duration(0, 1, 0, 15);
+ const relativeTo = new Temporal.ZonedDateTime(
+ 951991200_000_000_000n /* = 2000-03-02T10Z */,
+ timeZone); /* = 2000-03-02T02-08 in local time */
+
+ TemporalHelpers.assertDuration(duration.round({ smallestUnit: "months", relativeTo }),
+ 0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
+ "1 month 15 days should be exactly 1.5 months, which rounds up to 2 months");
+ TemporalHelpers.assertDuration(duration.round({ smallestUnit: "months", roundingMode: 'halfTrunc', relativeTo }),
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ "1 month 15 days should be exactly 1.5 months, which rounds down to 1 month");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/duration-out-of-range-added-to-relativeto.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/duration-out-of-range-added-to-relativeto.js
index b564b27eb4..f63eb3b789 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/duration-out-of-range-added-to-relativeto.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/duration-out-of-range-added-to-relativeto.js
@@ -8,21 +8,44 @@ description: RangeError thrown when calendar part of duration added to relativeT
features: [Temporal]
info: |
RoundDuration:
- 8.k. Let _isoResult_ be ! AddISODate(_plainRelativeTo_.[[ISOYear]]. _plainRelativeTo_.[[ISOMonth]], _plainRelativeTo_.[[ISODay]], 0, 0, 0, truncate(_fractionalDays_), *"constrain"*).
- l. Let _wholeDaysLater_ be ? CreateTemporalDate(_isoResult_.[[Year]], _isoResult_.[[Month]], _isoResult_.[[Day]], _calendar_).
+ 10.h. Let _isoResult_ be ! AddISODate(_plainRelativeTo_.[[ISOYear]]. _plainRelativeTo_.[[ISOMonth]], _plainRelativeTo_.[[ISODay]], 0, 0, 0, truncate(_fractionalDays_), *"constrain"*).
+ i. Let _wholeDaysLater_ be ? CreateTemporalDate(_isoResult_.[[Year]], _isoResult_.[[Month]], _isoResult_.[[Day]], _calendar_).
+ ...
+ 11.h. Let _isoResult_ be ! AddISODate(_plainRelativeTo_.[[ISOYear]]. _plainRelativeTo_.[[ISOMonth]], _plainRelativeTo_.[[ISODay]], 0, 0, 0, truncate(_fractionalDays_), *"constrain"*).
+ i. Let _wholeDaysLater_ be ? CreateTemporalDate(_isoResult_.[[Year]], _isoResult_.[[Month]], _isoResult_.[[Day]], _calendar_).
+ ...
+ 12.a. Let _isoResult_ be ! AddISODate(_plainRelativeTo_.[[ISOYear]]. _plainRelativeTo_.[[ISOMonth]], _plainRelativeTo_.[[ISODay]], 0, 0, 0, truncate(_fractionalDays_), *"constrain"*).
+ b. Let _wholeDaysLater_ be ? CreateTemporalDate(_isoResult_.[[Year]], _isoResult_.[[Month]], _isoResult_.[[Day]], _calendar_).
+
+ UnbalanceDateDurationRelative:
+ 11. Let _yearsMonthsWeeksDuration_ be ! CreateTemporalDuration(_years_, _months_, _weeks_, 0, 0, 0, 0, 0, 0, 0).
+ 12. Let _later_ be ? CalendarDateAdd(_calendaRec_, _plainRelativeTo_, _yearsMonthsWeeksDuration_).
+ 13. Let _yearsMonthsWeeksInDays_ be DaysUntil(_plainRelativeTo_, _later_).
+ 14. Return ? CreateDateDurationRecord(0, 0, 0, _days_ + _yearsMonthsWeeksInDays_).
---*/
// Based on a test case by André Bargull <andre.bargull@gmail.com>
-const instance = new Temporal.Duration(0, 0, 0, /* days = */ 500_000_000);
const relativeTo = new Temporal.PlainDate(2000, 1, 1);
-assert.throws(RangeError, () => instance.round({relativeTo, smallestUnit: "years"}));
-assert.throws(RangeError, () => instance.round({relativeTo, smallestUnit: "months"}));
-assert.throws(RangeError, () => instance.round({relativeTo, smallestUnit: "weeks"}));
-
-const negInstance = new Temporal.Duration(0, 0, 0, /* days = */ -500_000_000);
-assert.throws(RangeError, () => negInstance.round({relativeTo, smallestUnit: "years"}));
-assert.throws(RangeError, () => negInstance.round({relativeTo, smallestUnit: "months"}));
-assert.throws(RangeError, () => negInstance.round({relativeTo, smallestUnit: "weeks"}));
+
+{
+ const instance = new Temporal.Duration(0, 0, 0, /* days = */ 500_000_000);
+ assert.throws(RangeError, () => instance.round({relativeTo, smallestUnit: "years"}), "days out of range, positive, smallestUnit years");
+ assert.throws(RangeError, () => instance.round({relativeTo, smallestUnit: "months"}), "days out of range, positive, smallestUnit months");
+ assert.throws(RangeError, () => instance.round({relativeTo, smallestUnit: "weeks"}), "days out of range, positive, smallestUnit weeks");
+
+ const negInstance = new Temporal.Duration(0, 0, 0, /* days = */ -500_000_000);
+ assert.throws(RangeError, () => negInstance.round({relativeTo, smallestUnit: "years"}), "days out of range, negative, smallestUnit years");
+ assert.throws(RangeError, () => negInstance.round({relativeTo, smallestUnit: "months"}), "days out of range, negative, smallestUnit months");
+ assert.throws(RangeError, () => negInstance.round({relativeTo, smallestUnit: "weeks"}), "days out of range, negative, smallestUnit weeks");
+}
+
+{
+ const instance = new Temporal.Duration(0, 0, /* weeks = */ 1, /* days = */ Math.trunc((2 ** 53) / 86_400));
+ assert.throws(RangeError, () => instance.round({relativeTo, largestUnit: "days"}), "weeks + days out of range, positive");
+
+ const negInstance = new Temporal.Duration(0, 0, /* weeks = */ -1, /* days = */ -Math.trunc((2 ** 53) / 86_400));
+ assert.throws(RangeError, () => instance.round({relativeTo, largestUnit: "days"}), "weeks + days out of range, negative");
+}
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/normalized-time-duration-to-days-loop-arbitrarily.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/normalized-time-duration-to-days-loop-arbitrarily.js
index 923d788246..ed63026c0a 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/normalized-time-duration-to-days-loop-arbitrarily.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/normalized-time-duration-to-days-loop-arbitrarily.js
@@ -4,76 +4,44 @@
/*---
esid: sec-temporal.duration.prototype.round
description: >
- NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer
+ NormalizedTimeDurationToDays should not be able to loop arbitrarily.
info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
...
- 21. Repeat, while done is false,
- a. Let oneDayFarther be ? AddDaysToZonedDateTime(relativeResult.[[Instant]],
- relativeResult.[[DateTime]], timeZoneRec, zonedRelativeTo.[[Calendar]], sign).
- b. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(oneDayFarther.[[EpochNanoseconds]],
- relativeResult.[[EpochNanoseconds]]).
- c. Let oneDayLess be ? SubtractNormalizedTimeDuration(norm, dayLengthNs).
- c. If NormalizedTimeDurationSign(oneDayLess) × sign ≥ 0, then
- i. Set norm to oneDayLess.
- ii. Set relativeResult to oneDayFarther.
- iii. Set days to days + sign.
- d. Else,
- i. Set done to true.
-includes: [temporalHelpers.js]
+ 22. If NormalizedTimeDurationSign(_oneDayLess_) × _sign_ ≥ 0, then
+ a. Set _norm_ to _oneDayLess_.
+ b. Set _relativeResult_ to _oneDayFarther_.
+ c. Set _days_ to _days_ + _sign_.
+ d. Set _oneDayFarther_ to ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_).
+ e. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther.[[EpochNanoseconds]], relativeResult.[[EpochNanoseconds]]).
+ f. If NormalizedTimeDurationSign(? SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_)) × _sign_ ≥ 0, then
+ i. Throw a *RangeError* exception.
features: [Temporal]
---*/
-const calls = [];
const duration = Temporal.Duration.from({ days: 1 });
-function createRelativeTo(count) {
- const dayLengthNs = 86400000000000n;
- const dayInstant = new Temporal.Instant(dayLengthNs);
- const substitutions = [];
- const timeZone = new Temporal.TimeZone("UTC");
- // Return constant value for first _count_ calls
- TemporalHelpers.substituteMethod(
- timeZone,
- "getPossibleInstantsFor",
- substitutions
- );
- substitutions.length = count;
- let i = 0;
- for (i = 0; i < substitutions.length; i++) {
- // (this value)
- substitutions[i] = [dayInstant];
+const dayLengthNs = 86400000000000n;
+const dayInstant = new Temporal.Instant(dayLengthNs);
+let calls = 0;
+const timeZone = new class extends Temporal.TimeZone {
+ getPossibleInstantsFor() {
+ calls++;
+ return [dayInstant];
}
- // Record calls in calls[]
- TemporalHelpers.observeMethod(calls, timeZone, "getPossibleInstantsFor");
- return new Temporal.ZonedDateTime(0n, timeZone);
-}
+}("UTC");
-let zdt = createRelativeTo(50);
-calls.splice(0); // Reset calls list after ZonedDateTime construction
-duration.round({
- smallestUnit: "days",
- relativeTo: zdt,
-});
-assert.sameValue(
- calls.length,
- 50 + 1,
- "Expected duration.round to call getPossibleInstantsFor correct number of times"
-);
+const relativeTo = new Temporal.ZonedDateTime(0n, timeZone);
-zdt = createRelativeTo(100);
-calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
-duration.round({
- smallestUnit: "days",
- relativeTo: zdt,
-});
-assert.sameValue(
- calls.length,
- 100 + 1,
- "Expected duration.round to call getPossibleInstantsFor correct number of times"
-);
-
-zdt = createRelativeTo(107);
-assert.throws(RangeError, () => duration.round({ smallestUnit: "days", relativeTo: zdt }), "107-2 days > 2⁵³ ns");
+assert.throws(RangeError, () => duration.round({ smallestUnit: "days", relativeTo }), "indefinite loop is prevented");
+assert.sameValue(calls, 5, "getPossibleInstantsFor is not called indefinitely");
+ // Expected calls:
+ // RoundDuration -> MoveRelativeZonedDateTime -> AddZonedDateTime (1)
+ // BalanceTimeDurationRelative ->
+ // AddZonedDateTime (2)
+ // NormalizedTimeDurationToDays ->
+ // AddDaysToZonedDateTime (3, step 12)
+ // AddDaysToZonedDateTime (4, step 15)
+ // AddDaysToZonedDateTime (5, step 18.d)
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/out-of-range-when-adjusting-rounded-days.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/out-of-range-when-adjusting-rounded-days.js
new file mode 100644
index 0000000000..cd6c307e5b
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/out-of-range-when-adjusting-rounded-days.js
@@ -0,0 +1,32 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.round
+description: >
+ When adjusting the rounded days after rounding relative to a ZonedDateTime,
+ the duration may go out of range
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+// Based on a test case by André Bargull
+
+const d = new Temporal.Duration(0, 0, 0, /* days = */ 1, 0, 0, /* s = */ Number.MAX_SAFE_INTEGER - 86400, 0, 0, /* ns = */ 999_999_999);
+
+const timeZone = new Temporal.TimeZone("UTC");
+TemporalHelpers.substituteMethod(timeZone, 'getPossibleInstantsFor', [
+ TemporalHelpers.SUBSTITUTE_SKIP,
+ [new Temporal.Instant(1n)],
+]);
+
+const relativeTo = new Temporal.ZonedDateTime(0n, timeZone);
+
+assert.throws(RangeError, () => d.round({
+ largestUnit: 'nanoseconds',
+ roundingIncrement: 2,
+ relativeTo
+}));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/out-of-range-when-converting-from-normalized-duration.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/out-of-range-when-converting-from-normalized-duration.js
index 4fde761351..d4f973d639 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/out-of-range-when-converting-from-normalized-duration.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/out-of-range-when-converting-from-normalized-duration.js
@@ -14,6 +14,6 @@ const d = new Temporal.Duration(0, 0, 0, 0, 0, 0, /* s = */ Number.MAX_SAFE_INTE
assert.throws(RangeError, () => d.round({
largestUnit: "nanoseconds",
roundingIncrement: 1,
-}), "nanoseconds component is an unsafe integer after balancing");
+}), "nanoseconds component after balancing as a float64-representable integer is out of range");
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..e147558077
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-backward-offset-shift.js
@@ -0,0 +1,50 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.round
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.Duration(1, 0, 0, 0, 24);
+assert.throws(RangeError, () => instance.round({ largestUnit: "years", relativeTo }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..f3b8d604ee
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-forward-offset-shift.js
@@ -0,0 +1,45 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.round
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.Duration(1, 0, 0, 0, 24);
+assert.throws(RangeError, () => instance.round({ largestUnit: "years", relativeTo }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js
index 150f9f419e..9c04b2991e 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js
@@ -8,12 +8,12 @@ description: >
RangeErrors.
info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
- 22. If days < 0 and sign = 1, throw a RangeError exception.
- 23. If days > 0 and sign = -1, throw a RangeError exception.
+ 23. If days < 0 and sign = 1, throw a RangeError exception.
+ 24. If days > 0 and sign = -1, throw a RangeError exception.
...
- 25. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception.
+ 26. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception.
...
- 28. If dayLength ≥ 2⁵³, throw a RangeError exception.
+ 29. If dayLength ≥ 2⁵³, throw a RangeError exception.
features: [Temporal, BigInt]
includes: [temporalHelpers.js]
---*/
@@ -41,7 +41,7 @@ function timeZoneSubstituteValues(
return tz;
}
-// Step 22: days < 0 and sign = 1
+// Step 23: days < 0 and sign = 1
let zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0
timeZoneSubstituteValues(
@@ -62,7 +62,7 @@ assert.throws(RangeError, () =>
"RangeError when days < 0 and sign = 1"
);
-// Step 23: days > 0 and sign = -1
+// Step 24: days > 0 and sign = -1
zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0
timeZoneSubstituteValues(
@@ -83,13 +83,13 @@ assert.throws(RangeError, () =>
"RangeError when days > 0 and sign = -1"
);
-// Step 25: nanoseconds > 0 and sign = -1
+// Step 26: nanoseconds > 0 and sign = -1
zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0
timeZoneSubstituteValues(
[
[new Temporal.Instant(-2n)], // Returned in step 16, setting _relativeResult_
- [new Temporal.Instant(-4n)], // Returned in step 21.a, setting _oneDayFarther_
+ [new Temporal.Instant(-4n)], // Returned in step 19, setting _oneDayFarther_
],
[
TemporalHelpers.SUBSTITUTE_SKIP, // Pre-conversion in Duration.p.round
@@ -107,12 +107,12 @@ assert.throws(RangeError, () =>
"RangeError when nanoseconds > 0 and sign = -1"
);
-// Step 28: day length is an unsafe integer
+// Step 29: day length is an unsafe integer
zdt = new Temporal.ZonedDateTime(
0n,
timeZoneSubstituteValues(
// Not called in step 16 because _days_ = 0
- // Returned in step 21.a, making _oneDayFarther_ 2^53 ns later than _relativeResult_
+ // Returned in step 19, making _oneDayFarther_ 2^53 ns later than _relativeResult_
[[new Temporal.Instant(2n ** 53n)]],
[]
)
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/calendar-dateadd-called-with-options-undefined.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/calendar-dateadd-called-with-options-undefined.js
index 5f5155e292..e0c22a689e 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/calendar-dateadd-called-with-options-undefined.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/calendar-dateadd-called-with-options-undefined.js
@@ -15,6 +15,6 @@ const calendar = TemporalHelpers.calendarDateAddUndefinedOptions();
const timeZone = TemporalHelpers.oneShiftTimeZone(new Temporal.Instant(0n), 3600e9);
const instance = new Temporal.Duration(1, 1, 1, 1);
instance.subtract(new Temporal.Duration(-1, -1, -1, -1), { relativeTo: new Temporal.ZonedDateTime(0n, timeZone, calendar) });
-assert.sameValue(calendar.dateAddCallCount, 3);
+assert.sameValue(calendar.dateAddCallCount, 2);
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/differencezoneddatetime-inconsistent-custom-calendar.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/differencezoneddatetime-inconsistent-custom-calendar.js
new file mode 100644
index 0000000000..e2c543c7d2
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/differencezoneddatetime-inconsistent-custom-calendar.js
@@ -0,0 +1,55 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.duration.prototype.subtract
+description: >
+ Throws a RangeError when custom calendar method returns inconsistent result
+info: |
+ DifferenceZonedDateTime ( ... )
+ 8. Repeat 3 times:
+ ...
+ g. If _sign_ = 0, or _timeSign_ = 0, or _sign_ = _timeSign_, then
+ ...
+ viii. Return ? CreateNormalizedDurationRecord(_dateDifference_.[[Years]],
+ _dateDifference_.[[Months]], _dateDifference_.[[Weeks]],
+ _dateDifference_.[[Days]], _norm_).
+ h. Set _dayCorrection_ to _dayCorrection_ + 1.
+ 9. NOTE: This step is only reached when custom calendar or time zone methods
+ return inconsistent values.
+ 10. Throw a *RangeError* exception.
+features: [Temporal]
+---*/
+
+// Based partly on a test case by André Bargull
+
+const duration1 = new Temporal.Duration(0, 0, /* weeks = */ 7, 0, /* hours = */ 12);
+const duration2 = new Temporal.Duration(0, 0, 0, /* days = */ -1);
+
+{
+ const tz = new (class extends Temporal.TimeZone {
+ getPossibleInstantsFor(dateTime) {
+ return super.getPossibleInstantsFor(dateTime.add({ days: 3 }));
+ }
+ })("UTC");
+
+ const relativeTo = new Temporal.ZonedDateTime(0n, tz);
+
+ assert.throws(RangeError, () => duration1.subtract(duration2, { relativeTo }),
+ "Calendar calculation where more than 2 days correction is needed should cause RangeError");
+}
+
+{
+ const cal = new (class extends Temporal.Calendar {
+ dateUntil(one, two, options) {
+ return super.dateUntil(one, two, options).negated();
+ }
+ })("iso8601");
+
+ const relativeTo = new Temporal.ZonedDateTime(0n, "UTC", cal);
+
+ assert.throws(RangeError, () => duration1.subtract(duration2, { relativeTo }),
+ "Calendar calculation causing mixed-sign values should cause RangeError");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/normalized-time-duration-to-days-loop-arbitrarily.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/normalized-time-duration-to-days-loop-arbitrarily.js
deleted file mode 100644
index 8873380037..0000000000
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/normalized-time-duration-to-days-loop-arbitrarily.js
+++ /dev/null
@@ -1,78 +0,0 @@
-// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
-// Copyright (C) 2022 Igalia, S.L. All rights reserved.
-// This code is governed by the BSD license found in the LICENSE file.
-
-/*---
-esid: sec-temporal.duration.prototype.subtract
-description: >
- NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer
-info: |
- NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
- ...
- 21. Repeat, while done is false,
- a. Let oneDayFarther be ? AddDaysToZonedDateTime(relativeResult.[[Instant]],
- relativeResult.[[DateTime]], timeZoneRec, zonedRelativeTo.[[Calendar]], sign).
- b. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(oneDayFarther.[[EpochNanoseconds]],
- relativeResult.[[EpochNanoseconds]]).
- c. Let oneDayLess be ? SubtractNormalizedTimeDuration(norm, dayLengthNs).
- c. If NormalizedTimeDurationSign(oneDayLess) × sign ≥ 0, then
- i. Set norm to oneDayLess.
- ii. Set relativeResult to oneDayFarther.
- iii. Set days to days + sign.
- d. Else,
- i. Set done to true.
-includes: [temporalHelpers.js]
-features: [Temporal]
----*/
-
-const calls = [];
-const duration = Temporal.Duration.from({ days: 1 });
-
-function createRelativeTo(count) {
- const dayLengthNs = 86400000000000n;
- const dayInstant = new Temporal.Instant(dayLengthNs);
- const substitutions = [];
- const timeZone = new Temporal.TimeZone("UTC");
- // Return constant value for first _count_ calls
- TemporalHelpers.substituteMethod(
- timeZone,
- "getPossibleInstantsFor",
- substitutions
- );
- substitutions.length = count;
- let i = 0;
- for (i = 0; i < substitutions.length; i++) {
- // (this value)
- substitutions[i] = [dayInstant];
- }
- // Record calls in calls[]
- TemporalHelpers.observeMethod(calls, timeZone, "getPossibleInstantsFor");
- return new Temporal.ZonedDateTime(0n, timeZone);
-}
-
-let zdt = createRelativeTo(50);
-calls.splice(0); // Reset calls list after ZonedDateTime construction
-duration.subtract(duration, {
- relativeTo: zdt,
-});
-assert.sameValue(
- calls.length,
- 50 + 1,
- "Expected duration.subtract to call getPossibleInstantsFor correct number of times"
-);
-
-zdt = createRelativeTo(100);
-calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
-duration.subtract(duration, {
- relativeTo: zdt,
-});
-assert.sameValue(
- calls.length,
- 100 + 1,
- "Expected duration.subtract to call getPossibleInstantsFor correct number of times"
-);
-
-zdt = createRelativeTo(107);
-assert.throws(RangeError, () => duration.subtract(duration, { relativeTo: zdt }), "107-2 days > 2⁵³ ns");
-
-reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/order-of-operations.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/order-of-operations.js
index 988835838f..55438a2b97 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/order-of-operations.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/order-of-operations.js
@@ -339,18 +339,8 @@ const expectedOpsForZonedRelativeTo = expected.concat([
"call options.relativeTo.timeZone.getPossibleInstantsFor",
// AddDuration → DifferenceZonedDateTime
"call options.relativeTo.timeZone.getOffsetNanosecondsFor",
- // AddDuration → DifferenceZonedDateTime → DifferenceISODateTime
- "call options.relativeTo.calendar.dateUntil",
- // AddDuration → DifferenceZonedDateTime → AddZonedDateTime
- "call options.relativeTo.calendar.dateAdd",
- "call options.relativeTo.timeZone.getPossibleInstantsFor",
- // AddDuration → DifferenceZonedDateTime → NanosecondsToDays
- "call options.relativeTo.timeZone.getOffsetNanosecondsFor",
- "call options.relativeTo.timeZone.getOffsetNanosecondsFor",
- // AddDuration → DifferenceZonedDateTime → NanosecondsToDays → AddZonedDateTime 1
- "call options.relativeTo.timeZone.getPossibleInstantsFor",
- // AddDuration → DifferenceZonedDateTime → NanosecondsToDays → AddZonedDateTime 2
"call options.relativeTo.timeZone.getPossibleInstantsFor",
+ "call options.relativeTo.calendar.dateUntil",
]);
const zonedRelativeTo = TemporalHelpers.propertyBagObserver(actual, {
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..193c09e806
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-backward-offset-shift.js
@@ -0,0 +1,50 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.subtract
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.Duration(1, 0, 0, 1);
+assert.throws(RangeError, () => instance.subtract(new Temporal.Duration(0, 0, 0, 0, 24), { relativeTo }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..3d1d6262f0
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-forward-offset-shift.js
@@ -0,0 +1,45 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.subtract
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.Duration(1, 0, 0, 1);
+assert.throws(RangeError, () => instance.subtract(new Temporal.Duration(0, 0, 0, 0, 24), { relativeTo }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js
deleted file mode 100644
index 46307bcaf9..0000000000
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js
+++ /dev/null
@@ -1,144 +0,0 @@
-// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
-// Copyright (C) 2022 Igalia, S.L. All rights reserved.
-// This code is governed by the BSD license found in the LICENSE file.
-/*---
-esid: sec-temporal.duration.prototype.subtract
-description: >
- Abstract operation NormalizedTimeDurationToDays can throw four different
- RangeErrors.
-info: |
- NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
- 22. If days < 0 and sign = 1, throw a RangeError exception.
- 23. If days > 0 and sign = -1, throw a RangeError exception.
- ...
- 25. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception.
- ...
- 28. If dayLength ≥ 2⁵³, throw a RangeError exception.
-features: [Temporal, BigInt]
-includes: [temporalHelpers.js]
----*/
-
-const dayNs = 86_400_000_000_000;
-const dayDuration = Temporal.Duration.from({ days: 1 });
-const epochInstant = new Temporal.Instant(0n);
-
-function timeZoneSubstituteValues(
- getPossibleInstantsFor,
- getOffsetNanosecondsFor
-) {
- const tz = new Temporal.TimeZone("UTC");
- TemporalHelpers.substituteMethod(
- tz,
- "getPossibleInstantsFor",
- getPossibleInstantsFor
- );
- TemporalHelpers.substituteMethod(
- tz,
- "getOffsetNanosecondsFor",
- getOffsetNanosecondsFor
- );
- return tz;
-}
-
-// Step 22: days < 0 and sign = 1
-let zdt = new Temporal.ZonedDateTime(
- -1n, // Set DifferenceZonedDateTime _ns1_
- timeZoneSubstituteValues(
- [
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for first call, AddDuration step 15
- [epochInstant], // Returned in AddDuration step 16, setting _endNs_ -> DifferenceZonedDateTime _ns2_
- [epochInstant], // Returned in step 16, setting _relativeResult_
- ],
- [
- // Behave normally in 3 calls made prior to NormalizedTimeDurationToDays
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- dayNs - 1, // Returned in step 8, setting _startDateTime_
- -dayNs + 1, // Returned in step 9, setting _endDateTime_
- ]
- )
-);
-assert.throws(RangeError, () =>
- // Subtracting day from day sets largestUnit to 'day', avoids having any week/month/year components in difference
- dayDuration.subtract(dayDuration, {
- relativeTo: zdt,
- })
-);
-
-// Step 23: days > 0 and sign = -1
-zdt = new Temporal.ZonedDateTime(
- 1n, // Set DifferenceZonedDateTime _ns1_
- timeZoneSubstituteValues(
- [
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for first call, AddDuration step 15
- [epochInstant], // Returned in AddDuration step 16, setting _endNs_ -> DifferenceZonedDateTime _ns2_
- [epochInstant], // Returned in step 16, setting _relativeResult_
- ],
- [
- // Behave normally in 3 calls made prior to NanosecondsToDays
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- -dayNs + 1, // Returned in step 8, setting _startDateTime_
- dayNs - 1, // Returned in step 9, setting _endDateTime_
- ]
- )
-);
-assert.throws(RangeError, () =>
- // Subtracting day from day sets largestUnit to 'day', avoids having any week/month/year components in difference
- dayDuration.subtract(dayDuration, {
- relativeTo: zdt,
- })
-);
-
-// Step 25: nanoseconds > 0 and sign = -1
-zdt = new Temporal.ZonedDateTime(
- 0n, // Set DifferenceZonedDateTime _ns1_
- timeZoneSubstituteValues(
- [
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for first call, AddDuration step 15
- [new Temporal.Instant(-1n)], // Returned in AddDuration step 16, setting _endNs_ -> DifferenceZonedDateTime _ns2_
- [new Temporal.Instant(-2n)], // Returned in step 16, setting _relativeResult_
- [new Temporal.Instant(-4n)], // Returned in step 21.a, setting _oneDayFarther_
- ],
- [
- // Behave normally in 3 calls made prior to NanosecondsToDays
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- dayNs - 1, // Returned in step 8, setting _startDateTime_
- -dayNs + 1, // Returned in step 9, setting _endDateTime_
- ]
- )
-);
-assert.throws(RangeError, () =>
- // Subtracting day from day sets largestUnit to 'day', avoids having any week/month/year components in difference
- dayDuration.subtract(dayDuration, {
- relativeTo: zdt,
- })
-);
-
-// Step 28: day length is an unsafe integer
-zdt = new Temporal.ZonedDateTime(
- 0n,
- timeZoneSubstituteValues(
- [
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for AddDuration step 15
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for AddDuration step 16
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for step 16, setting _relativeResult_
- // Returned in step 21.a, making _oneDayFarther_ 2^53 ns later than _relativeResult_
- [new Temporal.Instant(2n ** 53n - 3n * BigInt(dayNs))],
- ],
- []
- )
-);
-const twoDaysDuration = new Temporal.Duration(0, 0, 0, 2);
-assert.throws(RangeError, () =>
- dayDuration.subtract(twoDaysDuration, {
- relativeTo: zdt,
- }),
- "Should throw RangeError when time zone calculates an outrageous day length"
-);
-
-reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/dst-rounding-result.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/dst-rounding-result.js
new file mode 100644
index 0000000000..b2b4daf2de
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/dst-rounding-result.js
@@ -0,0 +1,40 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.duration.prototype.total
+description: >
+ Rounding the resulting duration takes the time zone's UTC offset shifts
+ into account
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const timeZone = TemporalHelpers.springForwardFallBackTimeZone();
+
+// Based on a test case by Adam Shaw
+
+{
+ // Date part of duration lands on skipped DST hour, causing disambiguation
+ const duration = new Temporal.Duration(0, 1, 0, 15, 12);
+ const relativeTo = new Temporal.ZonedDateTime(
+ 950868000_000_000_000n /* = 2000-02-18T10Z */,
+ timeZone); /* = 2000-02-18T02-08 in local time */
+
+ assert.sameValue(duration.total({ unit: "months", relativeTo }), 1.5,
+ "1 month 15 days 12 hours should be exactly 1.5 months");
+}
+
+{
+ // Month-only part of duration lands on skipped DST hour, should not cause
+ // disambiguation
+ const duration = new Temporal.Duration(0, 1, 0, 15);
+ const relativeTo = new Temporal.ZonedDateTime(
+ 951991200_000_000_000n /* = 2000-03-02T10Z */,
+ timeZone); /* = 2000-03-02T02-08 in local time */
+
+ assert.sameValue(duration.total({ unit: "months", relativeTo }), 1.5,
+ "1 month 15 days should be exactly 1.5 months");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/duration-out-of-range-added-to-relativeto.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/duration-out-of-range-added-to-relativeto.js
index 2abd469541..cf33d39b0e 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/duration-out-of-range-added-to-relativeto.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/duration-out-of-range-added-to-relativeto.js
@@ -8,21 +8,44 @@ description: RangeError thrown when calendar part of duration added to relativeT
features: [Temporal]
info: |
RoundDuration:
- 8.k. Let _isoResult_ be ! AddISODate(_plainRelativeTo_.[[ISOYear]]. _plainRelativeTo_.[[ISOMonth]], _plainRelativeTo_.[[ISODay]], 0, 0, 0, truncate(_fractionalDays_), *"constrain"*).
- l. Let _wholeDaysLater_ be ? CreateTemporalDate(_isoResult_.[[Year]], _isoResult_.[[Month]], _isoResult_.[[Day]], _calendar_).
+ 10.h. Let _isoResult_ be ! AddISODate(_plainRelativeTo_.[[ISOYear]]. _plainRelativeTo_.[[ISOMonth]], _plainRelativeTo_.[[ISODay]], 0, 0, 0, truncate(_fractionalDays_), *"constrain"*).
+ i. Let _wholeDaysLater_ be ? CreateTemporalDate(_isoResult_.[[Year]], _isoResult_.[[Month]], _isoResult_.[[Day]], _calendar_).
+ ...
+ 11.h. Let _isoResult_ be ! AddISODate(_plainRelativeTo_.[[ISOYear]]. _plainRelativeTo_.[[ISOMonth]], _plainRelativeTo_.[[ISODay]], 0, 0, 0, truncate(_fractionalDays_), *"constrain"*).
+ i. Let _wholeDaysLater_ be ? CreateTemporalDate(_isoResult_.[[Year]], _isoResult_.[[Month]], _isoResult_.[[Day]], _calendar_).
+ ...
+ 12.a. Let _isoResult_ be ! AddISODate(_plainRelativeTo_.[[ISOYear]]. _plainRelativeTo_.[[ISOMonth]], _plainRelativeTo_.[[ISODay]], 0, 0, 0, truncate(_fractionalDays_), *"constrain"*).
+ b. Let _wholeDaysLater_ be ? CreateTemporalDate(_isoResult_.[[Year]], _isoResult_.[[Month]], _isoResult_.[[Day]], _calendar_).
+
+ UnbalanceDateDurationRelative:
+ 11. Let _yearsMonthsWeeksDuration_ be ! CreateTemporalDuration(_years_, _months_, _weeks_, 0, 0, 0, 0, 0, 0, 0).
+ 12. Let _later_ be ? CalendarDateAdd(_calendaRec_, _plainRelativeTo_, _yearsMonthsWeeksDuration_).
+ 13. Let _yearsMonthsWeeksInDays_ be DaysUntil(_plainRelativeTo_, _later_).
+ 14. Return ? CreateDateDurationRecord(0, 0, 0, _days_ + _yearsMonthsWeeksInDays_).
---*/
// Based on a test case by André Bargull <andre.bargull@gmail.com>
-const instance = new Temporal.Duration(0, 0, 0, /* days = */ 500_000_000);
const relativeTo = new Temporal.PlainDate(2000, 1, 1);
-assert.throws(RangeError, () => instance.total({relativeTo, unit: "years"}));
-assert.throws(RangeError, () => instance.total({relativeTo, unit: "months"}));
-assert.throws(RangeError, () => instance.total({relativeTo, unit: "weeks"}));
-
-const negInstance = new Temporal.Duration(0, 0, 0, /* days = */ -500_000_000);
-assert.throws(RangeError, () => negInstance.total({relativeTo, unit: "years"}));
-assert.throws(RangeError, () => negInstance.total({relativeTo, unit: "months"}));
-assert.throws(RangeError, () => negInstance.total({relativeTo, unit: "weeks"}));
+
+{
+ const instance = new Temporal.Duration(0, 0, 0, /* days = */ 500_000_000);
+ assert.throws(RangeError, () => instance.total({relativeTo, unit: "years"}), "days out of range, positive, unit years");
+ assert.throws(RangeError, () => instance.total({relativeTo, unit: "months"}), "days out of range, positive, unit months");
+ assert.throws(RangeError, () => instance.total({relativeTo, unit: "weeks"}), "days out of range, positive, unit weeks");
+
+ const negInstance = new Temporal.Duration(0, 0, 0, /* days = */ -500_000_000);
+ assert.throws(RangeError, () => negInstance.total({relativeTo, unit: "years"}), "days out of range, negative, unit years");
+ assert.throws(RangeError, () => negInstance.total({relativeTo, unit: "months"}), "days out of range, negative, unit months");
+ assert.throws(RangeError, () => negInstance.total({relativeTo, unit: "weeks"}), "days out of range, negative, unit weeks");
+}
+
+{
+ const instance = new Temporal.Duration(0, 0, /* weeks = */ 1, /* days = */ Math.trunc((2 ** 53) / 86_400));
+ assert.throws(RangeError, () => instance.total({relativeTo, unit: "days"}), "weeks + days out of range, positive");
+
+ const negInstance = new Temporal.Duration(0, 0, /* weeks = */ -1, /* days = */ -Math.trunc((2 ** 53) / 86_400));
+ assert.throws(RangeError, () => instance.total({relativeTo, unit: "days"}), "weeks + days out of range, negative");
+}
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/normalized-time-duration-to-days-loop-arbitrarily.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/normalized-time-duration-to-days-loop-arbitrarily.js
index 72260abd68..c4d685113d 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/normalized-time-duration-to-days-loop-arbitrarily.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/normalized-time-duration-to-days-loop-arbitrarily.js
@@ -5,76 +5,42 @@
/*---
esid: sec-temporal.duration.prototype.total
description: >
- NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer
+ NormalizedTimeDurationToDays should not be able to loop arbitrarily.
info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
...
- 21. Repeat, while done is false,
- a. Let oneDayFarther be ? AddDaysToZonedDateTime(relativeResult.[[Instant]],
- relativeResult.[[DateTime]], timeZoneRec, zonedRelativeTo.[[Calendar]], sign).
- b. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(oneDayFarther.[[EpochNanoseconds]],
- relativeResult.[[EpochNanoseconds]]).
- c. Let oneDayLess be ? SubtractNormalizedTimeDuration(norm, dayLengthNs).
- c. If NormalizedTimeDurationSign(oneDayLess) × sign ≥ 0, then
- i. Set norm to oneDayLess.
- ii. Set relativeResult to oneDayFarther.
- iii. Set days to days + sign.
- d. Else,
- i. Set done to true.
-includes: [temporalHelpers.js]
+ 22. If NormalizedTimeDurationSign(_oneDayLess_) × _sign_ ≥ 0, then
+ a. Set _norm_ to _oneDayLess_.
+ b. Set _relativeResult_ to _oneDayFarther_.
+ c. Set _days_ to _days_ + _sign_.
+ d. Set _oneDayFarther_ to ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_).
+ e. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther.[[EpochNanoseconds]], relativeResult.[[EpochNanoseconds]]).
+ f. If NormalizedTimeDurationSign(? SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_)) × _sign_ ≥ 0, then
+ i. Throw a *RangeError* exception.
features: [Temporal]
---*/
-const calls = [];
const duration = Temporal.Duration.from({ days: 1 });
-function createRelativeTo(count) {
- const dayLengthNs = 86400000000000n;
- const dayInstant = new Temporal.Instant(dayLengthNs);
- const substitutions = [];
- const timeZone = new Temporal.TimeZone("UTC");
- // Return constant value for first _count_ calls
- TemporalHelpers.substituteMethod(
- timeZone,
- "getPossibleInstantsFor",
- substitutions
- );
- substitutions.length = count;
- let i = 0;
- for (i = 0; i < substitutions.length; i++) {
- // (this value)
- substitutions[i] = [dayInstant];
+const dayLengthNs = 86400000000000n;
+const dayInstant = new Temporal.Instant(dayLengthNs);
+let calls = 0;
+const timeZone = new class extends Temporal.TimeZone {
+ getPossibleInstantsFor() {
+ calls++;
+ return [dayInstant];
}
- // Record calls in calls[]
- TemporalHelpers.observeMethod(calls, timeZone, "getPossibleInstantsFor");
- return new Temporal.ZonedDateTime(0n, timeZone);
-}
+}("UTC");
-let zdt = createRelativeTo(50);
-calls.splice(0); // Reset calls list after ZonedDateTime construction
-duration.total({
- unit: "day",
- relativeTo: zdt,
-});
-assert.sameValue(
- calls.length,
- 50 + 2,
- "Expected duration.total to call getPossibleInstantsFor correct number of times"
-);
+const relativeTo = new Temporal.ZonedDateTime(0n, timeZone);
-zdt = createRelativeTo(100);
-calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
-duration.total({
- unit: "day",
- relativeTo: zdt,
-});
-assert.sameValue(
- calls.length,
- 100 + 2,
- "Expected duration.total to call getPossibleInstantsFor correct number of times"
-);
-
-zdt = createRelativeTo(106);
-assert.throws(RangeError, () => duration.total({ unit: "day", relativeTo: zdt }), "106-1 days > 2⁵³ ns");
+assert.throws(RangeError, () => duration.total({ unit: "days", relativeTo }), "indefinite loop is prevented");
+assert.sameValue(calls, 4, "getPossibleInstantsFor is not called indefinitely");
+ // Expected calls:
+ // AddZonedDateTime (1)
+ // NormalizedTimeDurationToDays ->
+ // AddDaysToZonedDateTime (2, step 12)
+ // AddDaysToZonedDateTime (3, step 15)
+ // AddDaysToZonedDateTime (4, step 18.d)
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-3.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-3.js
index ab3fd772cc..9f00233078 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-3.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-3.js
@@ -71,7 +71,7 @@ function f64Repr(f) {
const tz = new (class extends Temporal.TimeZone {
getPossibleInstantsFor() {
- // Called in NormalizedTimeDurationToDays 21.a from RoundDuration 7.b.
+ // Called in NormalizedTimeDurationToDays 19 from RoundDuration 7.b.
// Sets _result_.[[DayLength]] to 2⁵³ - 1 ns, its largest possible value
return [new Temporal.Instant(-86400_0000_0000_000_000_000n + 2n ** 53n - 1n)];
}
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..83878fb1b2
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-backward-offset-shift.js
@@ -0,0 +1,50 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.total
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.Duration(1, 0, 0, 0, 24);
+assert.throws(RangeError, () => instance.total({ unit: "days", relativeTo }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..0012871d85
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-forward-offset-shift.js
@@ -0,0 +1,45 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.total
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.Duration(1, 0, 0, 0, 24);
+assert.throws(RangeError, () => instance.total({ unit: "days", relativeTo }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js
index d415117f89..3150c81852 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js
@@ -8,12 +8,12 @@ description: >
RangeErrors.
info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
- 22. If days < 0 and sign = 1, throw a RangeError exception.
- 23. If days > 0 and sign = -1, throw a RangeError exception.
+ 23. If days < 0 and sign = 1, throw a RangeError exception.
+ 24. If days > 0 and sign = -1, throw a RangeError exception.
...
- 25. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception.
+ 26. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception.
...
- 28. If dayLength ≥ 2⁵³, throw a RangeError exception.
+ 29. If dayLength ≥ 2⁵³, throw a RangeError exception.
features: [Temporal, BigInt]
includes: [temporalHelpers.js]
---*/
@@ -41,7 +41,7 @@ function timeZoneSubstituteValues(
return tz;
}
-// Step 22: days < 0 and sign = 1
+// Step 23: days < 0 and sign = 1
let zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0
timeZoneSubstituteValues(
@@ -62,7 +62,7 @@ assert.throws(RangeError, () =>
"RangeError when days < 0 and sign = 1"
);
-// Step 23: days > 0 and sign = -1
+// Step 24: days > 0 and sign = -1
zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0
timeZoneSubstituteValues(
@@ -83,13 +83,13 @@ assert.throws(RangeError, () =>
"RangeError when days > 0 and sign = -1"
);
-// Step 25: nanoseconds > 0 and sign = -1
+// Step 26: nanoseconds > 0 and sign = -1
zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0
timeZoneSubstituteValues(
[
[new Temporal.Instant(-2n)], // Returned in step 16, setting _relativeResult_
- [new Temporal.Instant(-4n)], // Returned in step 21.a, setting _oneDayFarther_
+ [new Temporal.Instant(-4n)], // Returned in step 19, setting _oneDayFarther_
],
[
TemporalHelpers.SUBSTITUTE_SKIP, // pre-conversion in Duration.p.total
@@ -107,12 +107,12 @@ assert.throws(RangeError, () =>
"RangeError when nanoseconds > 0 and sign = -1"
);
-// Step 28: day length is an unsafe integer
+// Step 29: day length is an unsafe integer
zdt = new Temporal.ZonedDateTime(
0n,
timeZoneSubstituteValues(
// Not called in step 16 because _days_ = 0
- // Returned in step 21.a, making _oneDayFarther_ 2^53 ns later than _relativeResult_
+ // Returned in step 19, making _oneDayFarther_ 2^53 ns later than _relativeResult_
[[new Temporal.Instant(2n ** 53n)]],
[]
)
diff --git a/js/src/tests/test262/built-ins/Temporal/Instant/compare/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/Instant/compare/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..a0863e12f6
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Instant/compare/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,32 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.instant.compare
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const epoch = new Temporal.Instant(0n);
+
+const invalidStrings = [
+ ["1970-01-01T00:00Z[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00Z[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00Z[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => Temporal.Instant.compare(arg, epoch),
+ `annotation keys must be lowercase: ${arg} - ${descr} (first argument)`
+ );
+ assert.throws(
+ RangeError,
+ () => Temporal.Instant.compare(epoch, arg),
+ `annotation keys must be lowercase: ${arg} - ${descr} (second argument)`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Instant/from/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/Instant/from/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..635ffb4ed9
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Instant/from/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.instant.from
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00Z[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00Z[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00Z[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => Temporal.Instant.from(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Instant/prototype/equals/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/Instant/prototype/equals/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..7ee8e3fc30
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Instant/prototype/equals/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.instant.prototype.equals
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00Z[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00Z[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00Z[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.Instant(0n);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.equals(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Instant/prototype/since/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/Instant/prototype/since/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..946f44ffa9
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Instant/prototype/since/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.instant.prototype.since
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00Z[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00Z[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00Z[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.Instant(0n);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.since(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Instant/prototype/toZonedDateTime/calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/Instant/prototype/toZonedDateTime/calendar-iso-string.js
new file mode 100644
index 0000000000..b067a5b62c
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Instant/prototype/toZonedDateTime/calendar-iso-string.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.instant.prototype.tozoneddatetime
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Instant(1_000_000_000_000_000_000n);
+
+for (const arg of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const result = instance.toZonedDateTime({ calendar: arg, timeZone: "UTC" });
+ assert.sameValue(result.getISOFields().calendar, "iso8601", `Calendar created from string "${arg}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Instant/prototype/until/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/Instant/prototype/until/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..4e9df646e2
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Instant/prototype/until/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.instant.prototype.until
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00Z[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00Z[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00Z[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.Instant(0n);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.until(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Now/plainDate/calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/Now/plainDate/calendar-iso-string.js
new file mode 100644
index 0000000000..1414eb207b
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Now/plainDate/calendar-iso-string.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.now.plaindate
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+for (const arg of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const result = Temporal.Now.plainDate(arg);
+ assert.sameValue(result.getISOFields().calendar, "iso8601", `Calendar created from string "${arg}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Now/plainDateTime/calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/Now/plainDateTime/calendar-iso-string.js
new file mode 100644
index 0000000000..d093635a30
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Now/plainDateTime/calendar-iso-string.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.now.plaindatetime
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+for (const arg of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const result = Temporal.Now.plainDateTime(arg);
+ assert.sameValue(result.getISOFields().calendar, "iso8601", `Calendar created from string "${arg}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Now/zonedDateTime/calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/Now/zonedDateTime/calendar-iso-string.js
new file mode 100644
index 0000000000..09da5820f7
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Now/zonedDateTime/calendar-iso-string.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.now.zoneddatetime
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+for (const arg of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const result = Temporal.Now.zonedDateTime(arg);
+ assert.sameValue(result.getISOFields().calendar, "iso8601", `Calendar created from string "${arg}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/compare/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/compare/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..81bf074b6b
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/compare/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.compare
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result1 = Temporal.PlainDate.compare(arg, new Temporal.PlainDate(1976, 11, 18));
+ assert.sameValue(result1, 0, `Calendar created from string "${calendar}" (first argument)`);
+ const result2 = Temporal.PlainDate.compare(new Temporal.PlainDate(1976, 11, 18), arg);
+ assert.sameValue(result2, 0, `Calendar created from string "${calendar}" (second argument)`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/compare/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/compare/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..909b9e4348
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/compare/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.compare
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => Temporal.PlainDate.compare(arg, new Temporal.PlainDate(1976, 11, 18)),
+ `annotation keys must be lowercase: ${arg} - ${descr} (first argument)`
+ );
+ assert.throws(
+ RangeError,
+ () => Temporal.PlainDate.compare(new Temporal.PlainDate(1976, 11, 18), arg),
+ `annotation keys must be lowercase: ${arg} - ${descr} (second argument)`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/from/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/from/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..b635736960
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/from/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.from
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = Temporal.PlainDate.from(arg);
+ TemporalHelpers.assertPlainDate(result, 1976, 11, "M11", 18, `Calendar created from string "${calendar}"`);
+ assert.sameValue(result.getISOFields().calendar, "iso8601", "calendar slot stores a string");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/from/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/from/argument-propertybag-calendar-year-zero.js
index fc65d8fd12..8f7002c4e6 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainDate/from/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/from/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => Temporal.PlainDate.from(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/from/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/from/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..9cbfff7fb8
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/from/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.from
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => Temporal.PlainDate.from(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/equals/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/equals/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..6c6f568ceb
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/equals/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.equals
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDate(1976, 11, 18);
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.equals(arg);
+ assert.sameValue(result, true, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/equals/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/equals/argument-propertybag-calendar-year-zero.js
index dc0a12c58a..a9e5c6d9d7 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/equals/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/equals/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.PlainDate(2000, 5, 2);
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.equals(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/equals/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/equals/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..50b0ad9b55
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/equals/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.equals
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.PlainDate(2000, 5, 2);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.equals(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/since/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/since/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..720267d753
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/since/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.since
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDate(1976, 11, 18);
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.since(arg);
+ TemporalHelpers.assertDuration(result, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/since/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/since/argument-propertybag-calendar-year-zero.js
index 13b4a25387..d5eda02a0e 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/since/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/since/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.PlainDate(2000, 5, 2);
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.since(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/since/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/since/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..d804238767
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/since/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.since
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.PlainDate(2000, 5, 2);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.since(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/since/wrapping-at-end-of-month.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/since/wrapping-at-end-of-month.js
new file mode 100644
index 0000000000..11d3bef1b7
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/since/wrapping-at-end-of-month.js
@@ -0,0 +1,120 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.since
+description: Tests balancing of days to months at end of month
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+// Difference between end of longer month to end of following shorter month
+{
+ const end = new Temporal.PlainDate(1970, 2, 28);
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 1, 28).since(end, { largestUnit }),
+ 0, -1, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Jan 28th to Feb 28th is one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 1, 29).since(end, { largestUnit }),
+ 0, 0, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Feb 28th is 30 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 1, 30).since(end, { largestUnit }),
+ 0, 0, 0, -29, 0, 0, 0, 0, 0, 0,
+ "Jan 30th to Feb 28th is 29 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 1, 31).since(end, { largestUnit }),
+ 0, 0, 0, -28, 0, 0, 0, 0, 0, 0,
+ "Jan 31st to Feb 28th is 28 days, not one month"
+ );
+ }
+}
+
+// Difference between end of leap-year January to end of leap-year February
+{
+ const end = new Temporal.PlainDate(1972, 2, 29);
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1972, 1, 29).since(end, { largestUnit }),
+ 0, -1, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Feb 29th is one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1972, 1, 30).since(end, { largestUnit }),
+ 0, 0, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Jan 30th to Feb 29th is 30 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1972, 1, 31).since(end, { largestUnit }),
+ 0, 0, 0, -29, 0, 0, 0, 0, 0, 0,
+ "Jan 31st to Feb 29th is 29 days, not one month"
+ );
+ }
+}
+
+// Difference between end of longer month to end of not-immediately-following
+// shorter month
+{
+ const end = new Temporal.PlainDate(1970, 11, 30);
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 8, 30).since(end, { largestUnit }),
+ 0, -3, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Aug 30th to Nov 30th is 3 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 8, 31).since(end, { largestUnit }),
+ 0, -2, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Aug 31st to Nov 30th is 2 months 30 days, not 3 months"
+ );
+ }
+}
+
+// Difference between end of longer month in one year to shorter month in
+// later year
+{
+ const end = new Temporal.PlainDate(1973, 4, 30);
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 12, 30).since(end, { largestUnit: "months" }),
+ 0, -28, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 28 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 12, 30).since(end, { largestUnit: "years" }),
+ -2, -4, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 2 years, 4 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 12, 31).since(end, { largestUnit: "months" }),
+ 0, -27, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 27 months, 30 days, not 28 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 12, 31).since(end, { largestUnit: "years" }),
+ -2, -3, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 2 years, 3 months, 30 days, not 2 years 4 months"
+ );
+}
+
+// Difference where months passes through a month that's the same length or
+// shorter than either the start or end month
+{
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 1, 29).since(new Temporal.PlainDate(1970, 3, 28), { largestUnit: "months" }),
+ 0, -1, 0, -28, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Mar 28th is 1 month 28 days, not 58 days"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 1, 31).since(new Temporal.PlainDate(1971, 5, 30), { largestUnit: "years" }),
+ -1, -3, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Jan 31st 1970 to May 30th 1971 is 1 year, 3 months, 30 days, not 1 year, 2 months, 60 days"
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toPlainDateTime/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toPlainDateTime/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..81f05806f1
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toPlainDateTime/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.toplaindatetime
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["00:00[U-CA=iso8601]", "invalid capitalized key, time-only"],
+ ["T00:00[U-CA=iso8601]", "invalid capitalized key, time designator"],
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["00:00[u-CA=iso8601]", "invalid partially-capitalized key, time-only"],
+ ["T00:00[u-CA=iso8601]", "invalid partially-capitalized key, time designator"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["00:00[FOO=bar]", "invalid capitalized unrecognized key, time-only"],
+ ["T00:00[FOO=bar]", "invalid capitalized unrecognized key, time designator"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.PlainDate(2000, 5, 2);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.toPlainDateTime(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..20fcdb44c0
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.tozoneddatetime
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["00:00[U-CA=iso8601]", "invalid capitalized key, time-only"],
+ ["T00:00[U-CA=iso8601]", "invalid capitalized key, time designator"],
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["00:00[u-CA=iso8601]", "invalid partially-capitalized key, time-only"],
+ ["T00:00[u-CA=iso8601]", "invalid partially-capitalized key, time designator"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["00:00[FOO=bar]", "invalid capitalized unrecognized key, time-only"],
+ ["T00:00[FOO=bar]", "invalid capitalized unrecognized key, time designator"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.PlainDate(2000, 5, 2);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.toZonedDateTime({ plainTime: arg, timeZone: "UTC" }),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js
new file mode 100644
index 0000000000..31ca94435d
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.tozoneddatetime
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants can be at most 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+let calls = 0;
+
+class Shift24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ calls++;
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12 })];
+ }
+}
+
+const timeZone = new Shift24Hour();
+
+const instance = new Temporal.PlainDate(1970, 1, 1);
+instance.toZonedDateTime({ timeZone, plainTime: new Temporal.PlainTime(12) });
+
+assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..1ab2bc7239
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js
@@ -0,0 +1,44 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.tozoneddatetime
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+
+const instance = new Temporal.PlainDate(1970, 1, 1);
+assert.throws(RangeError, () => instance.toZonedDateTime({ timeZone, plainTime: new Temporal.PlainTime(12) }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js
new file mode 100644
index 0000000000..015a36daf9
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js
@@ -0,0 +1,53 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.tozoneddatetime
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+let calls = 0;
+
+class Shift24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ calls++;
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12 }),
+ utcInstant.add({ hours: 12 })
+ ];
+ }
+}
+
+const timeZone = new Shift24Hour();
+
+const instance = new Temporal.PlainDate(1970, 1, 1);
+instance.toZonedDateTime({ timeZone, plainTime: new Temporal.PlainTime(12) });
+
+assert(calls >= 1, "getPossibleInstantsFor should be called at least once");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..9d6efeab88
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.tozoneddatetime
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+
+const instance = new Temporal.PlainDate(1970, 1, 1);
+assert.throws(RangeError, () => instance.toZonedDateTime({ timeZone, plainTime: new Temporal.PlainTime(12) }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..2ec1c255b3
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDate(1976, 11, 18);
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.until(arg);
+ TemporalHelpers.assertDuration(result, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-year-zero.js
index abe100487e..5111e057ee 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.PlainDate(2000, 5, 2);
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.until(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..38bba39b1d
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.PlainDate(2000, 5, 2);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.until(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-higher-units.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-higher-units.js
index a8f8812d9d..5962cf31bc 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-higher-units.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/largestunit-higher-units.js
@@ -20,10 +20,10 @@ TemporalHelpers.assertDuration(feb20.until(feb21, { largestUnit: "years" }), /*
TemporalHelpers.assertDuration(feb20.until(feb21, { largestUnit: "months" }), 0, /* months = */ 12, 0, 0, 0, 0, 0, 0, 0, 0, "start of February, months");
TemporalHelpers.assertDuration(feb20.until(feb21, { largestUnit: "weeks" }), 0, 0, /* weeks = */ 52, /* days = */ 2, 0, 0, 0, 0, 0, 0, "start of February, weeks");
-const lastFeb20 = Temporal.PlainDate.from("2020-02-29");
-const lastFeb21 = Temporal.PlainDate.from("2021-02-28");
-TemporalHelpers.assertDuration(lastFeb20.until(lastFeb21, { largestUnit: "years" }), /* years = */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, "end of February, years");
-TemporalHelpers.assertDuration(lastFeb20.until(lastFeb21, { largestUnit: "months" }), 0, /* months = */ 12, 0, 0, 0, 0, 0, 0, 0, 0, "end of February, months");
-TemporalHelpers.assertDuration(lastFeb20.until(lastFeb21, { largestUnit: "weeks" }), 0, 0, /* weeks = */ 52, /* days = */ 1, 0, 0, 0, 0, 0, 0, "end of February, weeks");
+const lastFeb21 = new Temporal.PlainDate(2021, 2, 28);
+const lastFeb22 = new Temporal.PlainDate(2022, 2, 28);
+TemporalHelpers.assertDuration(lastFeb21.until(lastFeb22, { largestUnit: "years" }), /* years = */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, "end of February, years");
+TemporalHelpers.assertDuration(lastFeb21.until(lastFeb22, { largestUnit: "months" }), 0, /* months = */ 12, 0, 0, 0, 0, 0, 0, 0, 0, "end of February, months");
+TemporalHelpers.assertDuration(lastFeb21.until(lastFeb22, { largestUnit: "weeks" }), 0, 0, /* weeks = */ 52, /* days = */ 1, 0, 0, 0, 0, 0, 0, "end of February, weeks");
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/wrapping-at-end-of-month.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/wrapping-at-end-of-month.js
new file mode 100644
index 0000000000..082ba05283
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/until/wrapping-at-end-of-month.js
@@ -0,0 +1,120 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.until
+description: Tests balancing of days to months at end of month
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+// Difference between end of longer month to end of following shorter month
+{
+ const end = new Temporal.PlainDate(1970, 2, 28);
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 1, 28).until(end, { largestUnit }),
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Jan 28th to Feb 28th is one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 1, 29).until(end, { largestUnit }),
+ 0, 0, 0, 30, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Feb 28th is 30 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 1, 30).until(end, { largestUnit }),
+ 0, 0, 0, 29, 0, 0, 0, 0, 0, 0,
+ "Jan 30th to Feb 28th is 29 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 1, 31).until(end, { largestUnit }),
+ 0, 0, 0, 28, 0, 0, 0, 0, 0, 0,
+ "Jan 31st to Feb 28th is 28 days, not one month"
+ );
+ }
+}
+
+// Difference between end of leap-year January to end of leap-year February
+{
+ const end = new Temporal.PlainDate(1972, 2, 29);
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1972, 1, 29).until(end, { largestUnit }),
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Feb 29th is one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1972, 1, 30).until(end, { largestUnit }),
+ 0, 0, 0, 30, 0, 0, 0, 0, 0, 0,
+ "Jan 30th to Feb 29th is 30 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1972, 1, 31).until(end, { largestUnit }),
+ 0, 0, 0, 29, 0, 0, 0, 0, 0, 0,
+ "Jan 31st to Feb 29th is 29 days, not one month"
+ );
+ }
+}
+
+// Difference between end of longer month to end of not-immediately-following
+// shorter month
+{
+ const end = new Temporal.PlainDate(1970, 11, 30);
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 8, 30).until(end, { largestUnit }),
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Aug 30th to Nov 30th is 3 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 8, 31).until(end, { largestUnit }),
+ 0, 2, 0, 30, 0, 0, 0, 0, 0, 0,
+ "Aug 31st to Nov 30th is 2 months 30 days, not 3 months"
+ );
+ }
+}
+
+// Difference between end of longer month in one year to shorter month in
+// later year
+{
+ const end = new Temporal.PlainDate(1973, 4, 30);
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 12, 30).until(end, { largestUnit: "months" }),
+ 0, 28, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 28 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 12, 30).until(end, { largestUnit: "years" }),
+ 2, 4, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 2 years, 4 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 12, 31).until(end, { largestUnit: "months" }),
+ 0, 27, 0, 30, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 27 months, 30 days, not 28 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 12, 31).until(end, { largestUnit: "years" }),
+ 2, 3, 0, 30, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 2 years, 3 months, 30 days, not 2 years 4 months"
+ );
+}
+
+// Difference where months passes through a month that's the same length or
+// shorter than either the start or end month
+{
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 1, 29).until(new Temporal.PlainDate(1970, 3, 28), { largestUnit: "months" }),
+ 0, 1, 0, 28, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Mar 28th is 1 month 28 days, not 58 days"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDate(1970, 1, 31).until(new Temporal.PlainDate(1971, 5, 30), { largestUnit: "years" }),
+ 1, 3, 0, 30, 0, 0, 0, 0, 0, 0,
+ "Jan 31st 1970 to May 30th 1971 is 1 year, 3 months, 30 days, not 1 year, 2 months, 60 days"
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/weekOfYear/custom-calendar-weekofyear.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/weekOfYear/custom-calendar-weekofyear.js
new file mode 100644
index 0000000000..724eaa4ccf
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/weekOfYear/custom-calendar-weekofyear.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.weekofyear
+description: >
+ Temporal.PlainDate.prototype.weekOfYear returns undefined for all
+ custom calendars where weekOfYear() returns undefined.
+features: [Temporal]
+---*/
+
+class CustomCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ weekOfYear() {
+ return undefined;
+ }
+}
+
+const calendar = new CustomCalendar();
+const customCalendarDate = new Temporal.PlainDate(2024, 1, 1, calendar);
+assert.sameValue(customCalendarDate.weekOfYear, undefined);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/weekOfYear/validate-calendar-value.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/weekOfYear/validate-calendar-value.js
index 7836bbff10..62faff1a15 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/weekOfYear/validate-calendar-value.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/weekOfYear/validate-calendar-value.js
@@ -9,7 +9,6 @@ features: [Temporal]
---*/
const badResults = [
- [undefined, TypeError],
[null, TypeError],
[false, TypeError],
[Infinity, RangeError],
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/withCalendar/calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/withCalendar/calendar-iso-string.js
new file mode 100644
index 0000000000..e17e975f46
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/withCalendar/calendar-iso-string.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.withcalendar
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDate(1976, 11, 18, {
+ dateAdd() {},
+ dateFromFields() {},
+ dateUntil() {},
+ day() {},
+ dayOfWeek() {},
+ dayOfYear() {},
+ daysInMonth() {},
+ daysInWeek() {},
+ daysInYear() {},
+ fields() {},
+ id: "replace-me",
+ inLeapYear() {},
+ mergeFields() {},
+ month() {},
+ monthCode() {},
+ monthDayFromFields() {},
+ monthsInYear() {},
+ weekOfYear() {},
+ year() {},
+ yearMonthFromFields() {},
+ yearOfWeek() {},
+});
+
+for (const arg of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const result = instance.withCalendar(arg);
+ assert.sameValue(result.getISOFields().calendar, "iso8601", `Calendar created from string "${arg}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/yearOfWeek/custom-calendar-weekofyear.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/yearOfWeek/custom-calendar-weekofyear.js
new file mode 100644
index 0000000000..9e37a85bd7
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/yearOfWeek/custom-calendar-weekofyear.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.yearofweek
+description: >
+ Temporal.PlainDate.prototype.yearOfWeek returns undefined for all
+ custom calendars where yearOfWeek() returns undefined.
+features: [Temporal]
+---*/
+
+class CustomCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ yearOfWeek() {
+ return undefined;
+ }
+}
+
+const calendar = new CustomCalendar();
+const customCalendarDate = new Temporal.PlainDate(2024, 1, 1, calendar);
+assert.sameValue(customCalendarDate.yearOfWeek, undefined);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/yearOfWeek/validate-calendar-value.js b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/yearOfWeek/validate-calendar-value.js
index ec291bd0ff..e00a2cda10 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/yearOfWeek/validate-calendar-value.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/yearOfWeek/validate-calendar-value.js
@@ -9,7 +9,6 @@ features: [Temporal]
---*/
const badResults = [
- [undefined, TypeError],
[Infinity, RangeError],
[-Infinity, RangeError],
[Symbol("foo"), TypeError],
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/compare/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/compare/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..182ef7beac
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/compare/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.compare
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result1 = Temporal.PlainDateTime.compare(arg, new Temporal.PlainDateTime(1976, 11, 18));
+ assert.sameValue(result1, 0, `Calendar created from string "${calendar}" (first argument)`);
+ const result2 = Temporal.PlainDateTime.compare(new Temporal.PlainDateTime(1976, 11, 18), arg);
+ assert.sameValue(result2, 0, `Calendar created from string "${calendar}" (second argument)`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/compare/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/compare/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..fa4f9e080c
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/compare/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.compare
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => Temporal.PlainDateTime.compare(arg, new Temporal.PlainDateTime(1976, 11, 18)),
+ `annotation keys must be lowercase: ${arg} - ${descr} (first argument)`
+ );
+ assert.throws(
+ RangeError,
+ () => Temporal.PlainDateTime.compare(new Temporal.PlainDateTime(1976, 11, 18), arg),
+ `annotation keys must be lowercase: ${arg} - ${descr} (second argument)`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/from/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/from/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..f1e298bf4b
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/from/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.from
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = Temporal.PlainDateTime.from(arg);
+ TemporalHelpers.assertPlainDateTime(result, 1976, 11, "M11", 18, 0, 0, 0, 0, 0, 0, `Calendar created from string "${calendar}"`);
+ assert.sameValue(result.getISOFields().calendar, "iso8601", "calendar slot stores a string");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/from/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/from/argument-propertybag-calendar-year-zero.js
index 8e5bc48a68..bfde3b1c4e 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/from/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/from/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => Temporal.PlainDateTime.from(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/from/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/from/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..1b69a116d3
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/from/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.from
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => Temporal.PlainDateTime.from(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/equals/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/equals/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..eaa94f9313
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/equals/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.equals
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDateTime(1976, 11, 18);
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.equals(arg);
+ assert.sameValue(result, true, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/equals/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/equals/argument-propertybag-calendar-year-zero.js
index 967839b97e..3abda8c843 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/equals/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/equals/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321);
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.equals(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/equals/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/equals/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..7c1070bcf7
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/equals/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.equals
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.equals(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..f23d5b2eb0
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.since
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDateTime(1976, 11, 18);
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.since(arg);
+ TemporalHelpers.assertDuration(result, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/argument-propertybag-calendar-number.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/argument-propertybag-calendar-number.js
index 9c7f65ec05..93932c89d5 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/argument-propertybag-calendar-number.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/argument-propertybag-calendar-number.js
@@ -8,7 +8,7 @@ description: A number as calendar in a property bag is not accepted
features: [Temporal]
---*/
-const instance = new Temporal.PlainDate(1976, 11, 18);
+const instance = new Temporal.PlainDateTime(1976, 11, 18);
const numbers = [
1,
@@ -16,6 +16,7 @@ const numbers = [
-19970327,
1234567890,
];
+
for (const calendar of numbers) {
const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
assert.throws(
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/argument-propertybag-calendar-year-zero.js
index 6cd66acaf2..e869362840 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321);
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.since(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..b5b7f9ce2c
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.since
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.since(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/result-mixed-sign.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/result-mixed-sign.js
new file mode 100644
index 0000000000..838de26784
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/result-mixed-sign.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.since
+description: >
+ RangeError when inconsistent custom calendar method causes mixed signs of
+ Duration components
+features: [Temporal]
+---*/
+
+// Test case provided by André Bargull
+
+const cal = new (class extends Temporal.Calendar {
+ dateAdd(date, duration, options) {
+ return super.dateAdd(date, duration.negated(), options);
+ }
+})("iso8601");
+
+const one = new Temporal.PlainDateTime(2000, 1, 1, 0, 0, 0, 0, 0, 0, cal);
+const two = new Temporal.PlainDateTime(2020, 5, 10, 12, 12, 0, 0, 0, 0, cal);
+
+assert.throws(RangeError, () => two.since(one, {
+ largestUnit: "years",
+ smallestUnit: "hours"
+}));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/wrapping-at-end-of-month.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/wrapping-at-end-of-month.js
new file mode 100644
index 0000000000..23cecf8d1a
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/since/wrapping-at-end-of-month.js
@@ -0,0 +1,120 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.since
+description: Tests balancing of days to months at end of month
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+// Difference between end of longer month to end of following shorter month
+{
+ const end = new Temporal.PlainDateTime(1970, 2, 28);
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 1, 28).since(end, { largestUnit }),
+ 0, -1, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Jan 28th to Feb 28th is one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 1, 29).since(end, { largestUnit }),
+ 0, 0, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Feb 28th is 30 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 1, 30).since(end, { largestUnit }),
+ 0, 0, 0, -29, 0, 0, 0, 0, 0, 0,
+ "Jan 30th to Feb 28th is 29 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 1, 31).since(end, { largestUnit }),
+ 0, 0, 0, -28, 0, 0, 0, 0, 0, 0,
+ "Jan 31st to Feb 28th is 28 days, not one month"
+ );
+ }
+}
+
+// Difference between end of leap-year January to end of leap-year February
+{
+ const end = new Temporal.PlainDateTime(1972, 2, 29);
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1972, 1, 29).since(end, { largestUnit }),
+ 0, -1, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Feb 29th is one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1972, 1, 30).since(end, { largestUnit }),
+ 0, 0, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Jan 30th to Feb 29th is 30 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1972, 1, 31).since(end, { largestUnit }),
+ 0, 0, 0, -29, 0, 0, 0, 0, 0, 0,
+ "Jan 31st to Feb 29th is 29 days, not one month"
+ );
+ }
+}
+
+// Difference between end of longer month to end of not-immediately-following
+// shorter month
+{
+ const end = new Temporal.PlainDateTime(1970, 11, 30);
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 8, 30).since(end, { largestUnit }),
+ 0, -3, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Aug 30th to Nov 30th is 3 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 8, 31).since(end, { largestUnit }),
+ 0, -2, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Aug 31st to Nov 30th is 2 months 30 days, not 3 months"
+ );
+ }
+}
+
+// Difference between end of longer month in one year to shorter month in
+// later year
+{
+ const end = new Temporal.PlainDateTime(1973, 4, 30);
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 12, 30).since(end, { largestUnit: "months" }),
+ 0, -28, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 28 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 12, 30).since(end, { largestUnit: "years" }),
+ -2, -4, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 2 years, 4 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 12, 31).since(end, { largestUnit: "months" }),
+ 0, -27, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 27 months, 30 days, not 28 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 12, 31).since(end, { largestUnit: "years" }),
+ -2, -3, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 2 years, 3 months, 30 days, not 2 years 4 months"
+ );
+}
+
+// Difference where months passes through a month that's the same length or
+// shorter than either the start or end month
+{
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 1, 29).since(new Temporal.PlainDateTime(1970, 3, 28), { largestUnit: "months" }),
+ 0, -1, 0, -28, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Mar 28th is 1 month 28 days, not 58 days"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 1, 31).since(new Temporal.PlainDateTime(1971, 5, 30), { largestUnit: "years" }),
+ -1, -3, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Jan 31st 1970 to May 30th 1971 is 1 year, 3 months, 30 days, not 1 year, 2 months, 60 days"
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js
new file mode 100644
index 0000000000..af7f128a9c
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.tozoneddatetime
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants can be at most 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+let calls = 0;
+
+class Shift24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ calls++;
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12 })];
+ }
+}
+
+const timeZone = new Shift24Hour();
+
+const instance = new Temporal.PlainDateTime(1970, 1, 1, 12);
+instance.toZonedDateTime(timeZone);
+
+assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..3fe9f52dbf
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js
@@ -0,0 +1,44 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.tozoneddatetime
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+
+const instance = new Temporal.PlainDateTime(1970, 1, 1, 12);
+assert.throws(RangeError, () => instance.toZonedDateTime(timeZone), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js
new file mode 100644
index 0000000000..a1a8898838
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js
@@ -0,0 +1,53 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.tozoneddatetime
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+let calls = 0;
+
+class Shift24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ calls++;
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12 }),
+ utcInstant.add({ hours: 12 })
+ ];
+ }
+}
+
+const timeZone = new Shift24Hour();
+
+const instance = new Temporal.PlainDateTime(1970, 1, 1, 12);
+instance.toZonedDateTime(timeZone);
+
+assert(calls >= 1, "getPossibleInstantsFor should be called at least once");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..29703ed59f
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.tozoneddatetime
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+
+const instance = new Temporal.PlainDateTime(1970, 1, 1, 12);
+assert.throws(RangeError, () => instance.toZonedDateTime(timeZone), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..0f06b77fa8
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.until
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDateTime(1976, 11, 18);
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.until(arg);
+ TemporalHelpers.assertDuration(result, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/argument-propertybag-calendar-number.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/argument-propertybag-calendar-number.js
index cf43c5a168..ab3cea6b6c 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/argument-propertybag-calendar-number.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/argument-propertybag-calendar-number.js
@@ -8,7 +8,7 @@ description: A number as calendar in a property bag is not accepted
features: [Temporal]
---*/
-const instance = new Temporal.PlainDate(1976, 11, 18);
+const instance = new Temporal.PlainDateTime(1976, 11, 18);
const numbers = [
1,
@@ -16,6 +16,7 @@ const numbers = [
-19970327,
1234567890,
];
+
for (const calendar of numbers) {
const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
assert.throws(
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/argument-propertybag-calendar-year-zero.js
index 099de48b51..b1d165cc6e 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321);
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.until(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..94dcaf9e99
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.until
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.until(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/no-unnecessary-units.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/no-unnecessary-units.js
index 7e311f8dca..8966496ca9 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/no-unnecessary-units.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/no-unnecessary-units.js
@@ -9,17 +9,17 @@ features: [Temporal]
includes: [temporalHelpers.js]
---*/
-const feb29 = new Temporal.PlainDateTime(2020, 2, 29, 0, 0);
-const feb28 = new Temporal.PlainDateTime(2021, 2, 28, 0, 0);
+const lastFeb21 = new Temporal.PlainDateTime(2021, 2, 28);
+const lastFeb22 = new Temporal.PlainDateTime(2022, 2, 28);
TemporalHelpers.assertDuration(
- feb29.until(feb28, { largestUnit: "months" }),
+ lastFeb21.until(lastFeb22, { largestUnit: "months" }),
0, 12, 0, 0, 0, 0, 0, 0, 0, 0,
"does not include higher units than necessary (largest unit = months)"
);
TemporalHelpers.assertDuration(
- feb29.until(feb28, { largestUnit: "years" }),
+ lastFeb21.until(lastFeb22, { largestUnit: "years" }),
1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
"does not include higher units than necessary (largest unit = years)"
);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/result-mixed-sign.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/result-mixed-sign.js
new file mode 100644
index 0000000000..7287c706b9
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/result-mixed-sign.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.until
+description: >
+ RangeError when inconsistent custom calendar method causes mixed signs of
+ Duration components
+features: [Temporal]
+---*/
+
+// Test case provided by André Bargull
+
+const cal = new (class extends Temporal.Calendar {
+ dateAdd(date, duration, options) {
+ return super.dateAdd(date, duration.negated(), options);
+ }
+})("iso8601");
+
+const one = new Temporal.PlainDateTime(2000, 1, 1, 0, 0, 0, 0, 0, 0, cal);
+const two = new Temporal.PlainDateTime(2020, 5, 10, 12, 12, 0, 0, 0, 0, cal);
+
+assert.throws(RangeError, () => one.until(two, {
+ largestUnit: "years",
+ smallestUnit: "hours"
+}));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/wrapping-at-end-of-month.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/wrapping-at-end-of-month.js
new file mode 100644
index 0000000000..3a83eb2138
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/until/wrapping-at-end-of-month.js
@@ -0,0 +1,120 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.until
+description: Tests balancing of days to months at end of month
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+// Difference between end of longer month to end of following shorter month
+{
+ const end = new Temporal.PlainDateTime(1970, 2, 28);
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 1, 28).until(end, { largestUnit }),
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Jan 28th to Feb 28th is one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 1, 29).until(end, { largestUnit }),
+ 0, 0, 0, 30, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Feb 28th is 30 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 1, 30).until(end, { largestUnit }),
+ 0, 0, 0, 29, 0, 0, 0, 0, 0, 0,
+ "Jan 30th to Feb 28th is 29 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 1, 31).until(end, { largestUnit }),
+ 0, 0, 0, 28, 0, 0, 0, 0, 0, 0,
+ "Jan 31st to Feb 28th is 28 days, not one month"
+ );
+ }
+}
+
+// Difference between end of leap-year January to end of leap-year February
+{
+ const end = new Temporal.PlainDateTime(1972, 2, 29);
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1972, 1, 29).until(end, { largestUnit }),
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Feb 29th is one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1972, 1, 30).until(end, { largestUnit }),
+ 0, 0, 0, 30, 0, 0, 0, 0, 0, 0,
+ "Jan 30th to Feb 29th is 30 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1972, 1, 31).until(end, { largestUnit }),
+ 0, 0, 0, 29, 0, 0, 0, 0, 0, 0,
+ "Jan 31st to Feb 29th is 29 days, not one month"
+ );
+ }
+}
+
+// Difference between end of longer month to end of not-immediately-following
+// shorter month
+{
+ const end = new Temporal.PlainDateTime(1970, 11, 30);
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 8, 30).until(end, { largestUnit }),
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Aug 30th to Nov 30th is 3 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 8, 31).until(end, { largestUnit }),
+ 0, 2, 0, 30, 0, 0, 0, 0, 0, 0,
+ "Aug 31st to Nov 30th is 2 months 30 days, not 3 months"
+ );
+ }
+}
+
+// Difference between end of longer month in one year to shorter month in
+// later year
+{
+ const end = new Temporal.PlainDateTime(1973, 4, 30);
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 12, 30).until(end, { largestUnit: "months" }),
+ 0, 28, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 28 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 12, 30).until(end, { largestUnit: "years" }),
+ 2, 4, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 2 years, 4 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 12, 31).until(end, { largestUnit: "months" }),
+ 0, 27, 0, 30, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 27 months, 30 days, not 28 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 12, 31).until(end, { largestUnit: "years" }),
+ 2, 3, 0, 30, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 2 years, 3 months, 30 days, not 2 years 4 months"
+ );
+}
+
+// Difference where months passes through a month that's the same length or
+// shorter than either the start or end month
+{
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 1, 29).until(new Temporal.PlainDateTime(1970, 3, 28), { largestUnit: "months" }),
+ 0, 1, 0, 28, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Mar 28th is 1 month 28 days, not 58 days"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.PlainDateTime(1970, 1, 31).until(new Temporal.PlainDateTime(1971, 5, 30), { largestUnit: "years" }),
+ 1, 3, 0, 30, 0, 0, 0, 0, 0, 0,
+ "Jan 31st 1970 to May 30th 1971 is 1 year, 3 months, 30 days, not 1 year, 2 months, 60 days"
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/weekOfYear/custom-calendar-weekofyear.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/weekOfYear/custom-calendar-weekofyear.js
new file mode 100644
index 0000000000..97242a19c4
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/weekOfYear/custom-calendar-weekofyear.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.weekofyear
+description: >
+ Temporal.PlainDateTime.prototype.weekOfYear returns undefined for all
+ custom calendars where weekOfYear() returns undefined.
+features: [Temporal]
+---*/
+
+class CustomCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ weekOfYear() {
+ return undefined;
+ }
+}
+
+const calendar = new CustomCalendar();
+const customCalendarDate = new Temporal.PlainDateTime(2024, 1, 1, 12, 34, 56, 987, 654, 321, calendar);
+assert.sameValue(customCalendarDate.weekOfYear, undefined);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/weekOfYear/validate-calendar-value.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/weekOfYear/validate-calendar-value.js
index d1624e1617..89d47a3566 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/weekOfYear/validate-calendar-value.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/weekOfYear/validate-calendar-value.js
@@ -9,7 +9,6 @@ features: [Temporal]
---*/
const badResults = [
- [undefined, TypeError],
[null, TypeError],
[false, TypeError],
[Infinity, RangeError],
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withCalendar/calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withCalendar/calendar-iso-string.js
new file mode 100644
index 0000000000..7ba0d5a207
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withCalendar/calendar-iso-string.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.withcalendar
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789, {
+ dateAdd() {},
+ dateFromFields() {},
+ dateUntil() {},
+ day() {},
+ dayOfWeek() {},
+ dayOfYear() {},
+ daysInMonth() {},
+ daysInWeek() {},
+ daysInYear() {},
+ fields() {},
+ id: "replace-me",
+ inLeapYear() {},
+ mergeFields() {},
+ month() {},
+ monthCode() {},
+ monthDayFromFields() {},
+ monthsInYear() {},
+ weekOfYear() {},
+ year() {},
+ yearMonthFromFields() {},
+ yearOfWeek() {},
+});
+
+for (const arg of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const result = instance.withCalendar(arg);
+ assert.sameValue(result.getISOFields().calendar, "iso8601", `Calendar created from string "${arg}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..e514b4b7d3
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.withplaindate
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321);
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.withPlainDate(arg);
+ TemporalHelpers.assertPlainDateTime(result, 1976, 11, "M11", 18, 12, 34, 56, 987, 654, 321, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-propertybag-calendar-number.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-propertybag-calendar-number.js
index cbd1950207..57c77ae669 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-propertybag-calendar-number.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-propertybag-calendar-number.js
@@ -8,7 +8,7 @@ description: A number as calendar in a property bag is not accepted
features: [Temporal]
---*/
-const instance = new Temporal.PlainDate(1976, 11, 18);
+const instance = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321);
const numbers = [
1,
@@ -16,6 +16,7 @@ const numbers = [
-19970327,
1234567890,
];
+
for (const calendar of numbers) {
const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
assert.throws(
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-propertybag-calendar-year-zero.js
index d39551d616..201aba871b 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321);
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.withPlainDate(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..8f9827733c
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.withplaindate
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.withPlainDate(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainTime/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainTime/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..5d63e53dc4
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/withPlainTime/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.withplaintime
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["00:00[U-CA=iso8601]", "invalid capitalized key, time-only"],
+ ["T00:00[U-CA=iso8601]", "invalid capitalized key, time designator"],
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["00:00[u-CA=iso8601]", "invalid partially-capitalized key, time-only"],
+ ["T00:00[u-CA=iso8601]", "invalid partially-capitalized key, time designator"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["00:00[FOO=bar]", "invalid capitalized unrecognized key, time-only"],
+ ["T00:00[FOO=bar]", "invalid capitalized unrecognized key, time designator"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.withPlainTime(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/yearOfWeek/custom-calendar-weekofyear.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/yearOfWeek/custom-calendar-weekofyear.js
new file mode 100644
index 0000000000..81b9ae2086
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/yearOfWeek/custom-calendar-weekofyear.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.yearofweek
+description: >
+ Temporal.PlainDateTime.prototype.yearOfWeek returns undefined for all
+ custom calendars where yearOfWeek() returns undefined.
+features: [Temporal]
+---*/
+
+class CustomCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ yearOfWeek() {
+ return undefined;
+ }
+}
+
+const calendar = new CustomCalendar();
+const customCalendarDate = new Temporal.PlainDateTime(2024, 1, 1, 12, 34, 56, 987, 654, 321, calendar);
+assert.sameValue(customCalendarDate.yearOfWeek, undefined);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/yearOfWeek/validate-calendar-value.js b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/yearOfWeek/validate-calendar-value.js
index cd43fd4474..8c67a02491 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/yearOfWeek/validate-calendar-value.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/yearOfWeek/validate-calendar-value.js
@@ -9,7 +9,6 @@ features: [Temporal]
---*/
const badResults = [
- [undefined, TypeError],
[Infinity, RangeError],
[-Infinity, RangeError],
[Symbol("foo"), TypeError],
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/from/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/from/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..0f227aa4be
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/from/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainmonthday.from
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { monthCode: "M11", day: 18, calendar };
+ const result = Temporal.PlainMonthDay.from(arg);
+ TemporalHelpers.assertPlainMonthDay(result, "M11", 18, `Calendar created from string "${calendar}"`);
+ assert.sameValue(result.getISOFields().calendar, "iso8601", "calendar slot stores a string");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/from/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/from/argument-propertybag-calendar-year-zero.js
index 19fb53023f..0b5268347c 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/from/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/from/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => Temporal.PlainMonthDay.from(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/from/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/from/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..6fce02f500
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/from/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainmonthday.from
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => Temporal.PlainMonthDay.from(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/prototype/equals/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/prototype/equals/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..756ea33cf2
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/prototype/equals/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainmonthday.prototype.equals
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainMonthDay(11, 18);
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { monthCode: "M11", day: 18, calendar };
+ const result = instance.equals(arg);
+ assert.sameValue(result, true, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/prototype/equals/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/prototype/equals/argument-propertybag-calendar-year-zero.js
index e3303ba1f5..86756fe089 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/prototype/equals/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/prototype/equals/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.PlainMonthDay(5, 2);
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.equals(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/prototype/equals/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/prototype/equals/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..bc74d401db
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/prototype/equals/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainmonthday.prototype.equals
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.PlainMonthDay(5, 2);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.equals(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/prototype/toPlainDate/calendar-fromfields-called-with-undefined-options.js b/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/prototype/toPlainDate/calendar-fromfields-called-with-undefined-options.js
new file mode 100644
index 0000000000..996909c664
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainMonthDay/prototype/toPlainDate/calendar-fromfields-called-with-undefined-options.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainmonthday.prototype.toplaindate
+description: Calendar.dateFromFields method is called with undefined options
+features: [Temporal]
+---*/
+
+let count = 0;
+
+const calendar = new class extends Temporal.Calendar {
+ dateFromFields(fields, options) {
+ count++;
+ assert.sameValue(options, undefined, "dateFromFields should be called with undefined options");
+ return super.dateFromFields(fields, options);
+ }
+}("iso8601");
+
+const instance = new Temporal.PlainMonthDay(5, 2, calendar);
+instance.toPlainDate({ year: 2019 });
+assert.sameValue(count, 1, "dateFromFields should have been called on the calendar");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainTime/compare/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainTime/compare/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..8667a8bf8f
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainTime/compare/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,36 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaintime.compare
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["00:00[U-CA=iso8601]", "invalid capitalized key, time-only"],
+ ["T00:00[U-CA=iso8601]", "invalid capitalized key, time designator"],
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["00:00[u-CA=iso8601]", "invalid partially-capitalized key, time-only"],
+ ["T00:00[u-CA=iso8601]", "invalid partially-capitalized key, time designator"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["00:00[FOO=bar]", "invalid capitalized unrecognized key, time-only"],
+ ["T00:00[FOO=bar]", "invalid capitalized unrecognized key, time designator"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => Temporal.PlainTime.compare(arg, new Temporal.PlainTime(12, 34, 56, 987, 654, 321)),
+ `annotation keys must be lowercase: ${arg} - ${descr} (first argument)`
+ );
+ assert.throws(
+ RangeError,
+ () => Temporal.PlainTime.compare(new Temporal.PlainTime(12, 34, 56, 987, 654, 321), arg),
+ `annotation keys must be lowercase: ${arg} - ${descr} (second argument)`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainTime/from/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainTime/from/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..886c8b6af8
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainTime/from/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaintime.from
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["00:00[U-CA=iso8601]", "invalid capitalized key, time-only"],
+ ["T00:00[U-CA=iso8601]", "invalid capitalized key, time designator"],
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["00:00[u-CA=iso8601]", "invalid partially-capitalized key, time-only"],
+ ["T00:00[u-CA=iso8601]", "invalid partially-capitalized key, time designator"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["00:00[FOO=bar]", "invalid capitalized unrecognized key, time-only"],
+ ["T00:00[FOO=bar]", "invalid capitalized unrecognized key, time designator"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => Temporal.PlainTime.from(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/equals/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/equals/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..258b4b5c77
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/equals/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaintime.prototype.equals
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["00:00[U-CA=iso8601]", "invalid capitalized key, time-only"],
+ ["T00:00[U-CA=iso8601]", "invalid capitalized key, time designator"],
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["00:00[u-CA=iso8601]", "invalid partially-capitalized key, time-only"],
+ ["T00:00[u-CA=iso8601]", "invalid partially-capitalized key, time designator"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["00:00[FOO=bar]", "invalid capitalized unrecognized key, time-only"],
+ ["T00:00[FOO=bar]", "invalid capitalized unrecognized key, time designator"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.PlainTime(12, 34, 56, 987, 654, 321);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.equals(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/since/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/since/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..1e2b960f01
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/since/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaintime.prototype.since
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["00:00[U-CA=iso8601]", "invalid capitalized key, time-only"],
+ ["T00:00[U-CA=iso8601]", "invalid capitalized key, time designator"],
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["00:00[u-CA=iso8601]", "invalid partially-capitalized key, time-only"],
+ ["T00:00[u-CA=iso8601]", "invalid partially-capitalized key, time designator"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["00:00[FOO=bar]", "invalid capitalized unrecognized key, time-only"],
+ ["T00:00[FOO=bar]", "invalid capitalized unrecognized key, time designator"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.PlainTime(12, 34, 56, 987, 654, 321);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.since(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..8f2089e661
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaintime.prototype.toplaindatetime
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainTime(12, 34, 56, 987, 654, 321);
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.toPlainDateTime(arg);
+ TemporalHelpers.assertPlainDateTime(result, 1976, 11, "M11", 18, 12, 34, 56, 987, 654, 321, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-propertybag-calendar-number.js b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-propertybag-calendar-number.js
index 407773adb4..56d2ffce44 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-propertybag-calendar-number.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-propertybag-calendar-number.js
@@ -8,7 +8,7 @@ description: A number as calendar in a property bag is not accepted
features: [Temporal]
---*/
-const instance = new Temporal.PlainDate(1976, 11, 18);
+const instance = new Temporal.PlainTime(12, 34, 56, 987, 654, 321);
const numbers = [
1,
@@ -16,6 +16,7 @@ const numbers = [
-19970327,
1234567890,
];
+
for (const calendar of numbers) {
const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
assert.throws(
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-propertybag-calendar-year-zero.js
index f9ba398d5f..2bf5796cc3 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.PlainTime(12, 34, 56, 987, 654, 321);
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.toPlainDateTime(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..84f457f314
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaintime.prototype.toplaindatetime
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.PlainTime(12, 34, 56, 987, 654, 321);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.toPlainDateTime(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..512dc81228
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaintime.prototype.tozoneddatetime
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainTime(12, 34, 56, 987, 654, 321);
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.toZonedDateTime({ plainDate: arg, timeZone: "UTC" });
+ assert.sameValue(result.epochNanoseconds, 217_168_496_987_654_321n, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-propertybag-calendar-year-zero.js
index 2acf24b30d..ae338a02ba 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.PlainTime(12, 34, 56, 987, 654, 321);
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.toZonedDateTime({ plainDate: arg, timeZone: "UTC" }),
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..6a37c4b726
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaintime.prototype.tozoneddatetime
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.PlainTime(12, 34, 56, 987, 654, 321);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.toZonedDateTime({ plainDate: arg, timeZone: "UTC" }),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js
new file mode 100644
index 0000000000..cbafe771e2
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaintime.prototype.tozoneddatetime
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants can be at most 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+let calls = 0;
+
+class Shift24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ calls++;
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12 })];
+ }
+}
+
+const timeZone = new Shift24Hour();
+
+const instance = new Temporal.PlainTime(12);
+instance.toZonedDateTime({ timeZone, plainDate: new Temporal.PlainDate(1970, 1, 1) });
+
+assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..139efa0dcc
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js
@@ -0,0 +1,44 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaintime.prototype.tozoneddatetime
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+
+const instance = new Temporal.PlainTime(12);
+assert.throws(RangeError, () => instance.toZonedDateTime({ timeZone, plainDate: new Temporal.PlainDate(1970, 1, 1) }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js
new file mode 100644
index 0000000000..200423dd52
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js
@@ -0,0 +1,53 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaintime.prototype.tozoneddatetime
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+let calls = 0;
+
+class Shift24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ calls++;
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12 }),
+ utcInstant.add({ hours: 12 })
+ ];
+ }
+}
+
+const timeZone = new Shift24Hour();
+
+const instance = new Temporal.PlainTime(12);
+instance.toZonedDateTime({ timeZone, plainDate: new Temporal.PlainDate(1970, 1, 1) });
+
+assert(calls >= 1, "getPossibleInstantsFor should be called at least once");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..968957a05e
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaintime.prototype.tozoneddatetime
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+
+const instance = new Temporal.PlainTime(12);
+assert.throws(RangeError, () => instance.toZonedDateTime({ timeZone, plainDate: new Temporal.PlainDate(1970, 1, 1) }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/until/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/until/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..48c425a3ef
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainTime/prototype/until/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaintime.prototype.until
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["00:00[U-CA=iso8601]", "invalid capitalized key, time-only"],
+ ["T00:00[U-CA=iso8601]", "invalid capitalized key, time designator"],
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["00:00[u-CA=iso8601]", "invalid partially-capitalized key, time-only"],
+ ["T00:00[u-CA=iso8601]", "invalid partially-capitalized key, time designator"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["00:00[FOO=bar]", "invalid capitalized unrecognized key, time-only"],
+ ["T00:00[FOO=bar]", "invalid capitalized unrecognized key, time designator"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.PlainTime(12, 34, 56, 987, 654, 321);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.until(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/compare/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/compare/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..42192ffb4f
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/compare/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainyearmonth.compare
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 2019, monthCode: "M06", calendar };
+ const result1 = Temporal.PlainYearMonth.compare(arg, new Temporal.PlainYearMonth(2019, 6));
+ assert.sameValue(result1, 0, `Calendar created from string "${calendar}" (first argument)`);
+ const result2 = Temporal.PlainYearMonth.compare(new Temporal.PlainYearMonth(2019, 6), arg);
+ assert.sameValue(result2, 0, `Calendar created from string "${calendar}" (second argument)`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/compare/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/compare/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..705204f82b
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/compare/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainyearmonth.compare
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => Temporal.PlainYearMonth.compare(arg, new Temporal.PlainYearMonth(2019, 6)),
+ `annotation keys must be lowercase: ${arg} - ${descr} (first argument)`
+ );
+ assert.throws(
+ RangeError,
+ () => Temporal.PlainYearMonth.compare(new Temporal.PlainYearMonth(2019, 6), arg),
+ `annotation keys must be lowercase: ${arg} - ${descr} (second argument)`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/from/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/from/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..a94d59d88f
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/from/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainyearmonth.from
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 2019, monthCode: "M06", calendar };
+ const result = Temporal.PlainYearMonth.from(arg);
+ TemporalHelpers.assertPlainYearMonth(result, 2019, 6, "M06", `Calendar created from string "${calendar}"`);
+ assert.sameValue(result.getISOFields().calendar, "iso8601", "calendar slot stores a string");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/from/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/from/argument-propertybag-calendar-year-zero.js
index 9d039d2365..03225ab174 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/from/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/from/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => Temporal.PlainYearMonth.from(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/from/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/from/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..15629e5f98
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/from/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainyearmonth.from
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => Temporal.PlainYearMonth.from(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/equals/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/equals/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..005d750ff9
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/equals/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainyearmonth.prototype.equals
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainYearMonth(2019, 6);
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 2019, monthCode: "M06", calendar };
+ const result = instance.equals(arg);
+ assert.sameValue(result, true, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/equals/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/equals/argument-propertybag-calendar-year-zero.js
index 0e652fd79b..be30091e98 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/equals/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/equals/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.PlainYearMonth(2000, 5);
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.equals(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/equals/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/equals/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..98808648b2
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/equals/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainyearmonth.prototype.equals
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.PlainYearMonth(2000, 5);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.equals(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/since/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/since/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..7e7ed39768
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/since/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainyearmonth.prototype.since
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainYearMonth(2019, 6);
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 2019, monthCode: "M06", calendar };
+ const result = instance.since(arg);
+ TemporalHelpers.assertDuration(result, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/since/argument-propertybag-calendar-number.js b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/since/argument-propertybag-calendar-number.js
index 08a1caa46b..b458e6d42b 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/since/argument-propertybag-calendar-number.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/since/argument-propertybag-calendar-number.js
@@ -8,7 +8,7 @@ description: A number as calendar in a property bag is not accepted
features: [Temporal]
---*/
-const instance = new Temporal.PlainDate(1976, 11, 18);
+const instance = new Temporal.PlainYearMonth(2019, 6);
const numbers = [
1,
@@ -16,6 +16,7 @@ const numbers = [
-19970327,
1234567890,
];
+
for (const calendar of numbers) {
const arg = { year: 2019, monthCode: "M06", calendar };
assert.throws(
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/since/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/since/argument-propertybag-calendar-year-zero.js
index 6c0b70f03b..ddca0eaedc 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/since/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/since/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.PlainYearMonth(2000, 5);
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.since(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/since/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/since/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..1746c55e7a
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/since/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainyearmonth.prototype.since
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.PlainYearMonth(2000, 5);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.since(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/toPlainDate/calendar-fromfields-called-with-undefined-options.js b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/toPlainDate/calendar-fromfields-called-with-undefined-options.js
new file mode 100644
index 0000000000..0edd3b289f
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/toPlainDate/calendar-fromfields-called-with-undefined-options.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainyearmonth.prototype.toplaindate
+description: Calendar.dateFromFields method is called with undefined options
+features: [Temporal]
+---*/
+
+let count = 0;
+
+const calendar = new class extends Temporal.Calendar {
+ dateFromFields(fields, options) {
+ count++;
+ assert.sameValue(options, undefined, "dateFromFields should be called with undefined options");
+ return super.dateFromFields(fields, options);
+ }
+}("iso8601");
+
+const instance = new Temporal.PlainYearMonth(2000, 5, calendar);
+instance.toPlainDate({ day: 24 });
+assert.sameValue(count, 1, "dateFromFields should have been called on the calendar");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/until/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/until/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..cddfd13793
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/until/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainyearmonth.prototype.until
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainYearMonth(2019, 6);
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 2019, monthCode: "M06", calendar };
+ const result = instance.until(arg);
+ TemporalHelpers.assertDuration(result, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/until/argument-propertybag-calendar-number.js b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/until/argument-propertybag-calendar-number.js
index 363d158d7a..ce758e146e 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/until/argument-propertybag-calendar-number.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/until/argument-propertybag-calendar-number.js
@@ -8,7 +8,7 @@ description: A number as calendar in a property bag is not accepted
features: [Temporal]
---*/
-const instance = new Temporal.PlainDate(1976, 11, 18);
+const instance = new Temporal.PlainYearMonth(2019, 6);
const numbers = [
1,
@@ -16,6 +16,7 @@ const numbers = [
-19970327,
1234567890,
];
+
for (const calendar of numbers) {
const arg = { year: 2019, monthCode: "M06", calendar };
assert.throws(
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/until/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/until/argument-propertybag-calendar-year-zero.js
index 71400ef76d..35789455ba 100644
--- a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/until/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/until/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.PlainYearMonth(2000, 5);
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.until(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/until/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/until/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..d2f40af228
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/until/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainyearmonth.prototype.until
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.PlainYearMonth(2000, 5);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.until(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..bf672c3f86
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getinstantfor
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.TimeZone("UTC");
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.getInstantFor(arg);
+ assert.sameValue(result.epochNanoseconds, 217_123_200_000_000_000n, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-propertybag-calendar-year-zero.js
index 8065dd55fe..f5cef07a0c 100644
--- a/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.TimeZone("UTC");
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.getInstantFor(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..5e34035059
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getinstantfor
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.TimeZone("UTC");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.getInstantFor(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/getoffsetnanosecondsfor-maximum-forward-offset-shift.js
new file mode 100644
index 0000000000..18a309bc43
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/getoffsetnanosecondsfor-maximum-forward-offset-shift.js
@@ -0,0 +1,51 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getinstantfor
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants can be at most 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+let calls = 0;
+
+class Shift24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ calls++;
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12 })];
+ }
+}
+
+const timeZone = new Shift24Hour();
+
+for (const disambiguation of ["earlier", "later", "compatible"]) {
+ timeZone.getInstantFor(new Temporal.PlainDateTime(1970, 1, 1, 12), { disambiguation });
+
+ assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice");
+ calls = 0;
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..020151a0d8
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js
@@ -0,0 +1,46 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getinstantfor
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+
+for (const disambiguation of ["earlier", "later", "compatible"]) {
+ assert.throws(RangeError, () => timeZone.getInstantFor(new Temporal.PlainDateTime(1970, 1, 1, 12), { disambiguation }), "RangeError should be thrown");
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/getpossibleinstantsfor-maximum-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/getpossibleinstantsfor-maximum-backward-offset-shift.js
new file mode 100644
index 0000000000..db12836dba
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/getpossibleinstantsfor-maximum-backward-offset-shift.js
@@ -0,0 +1,55 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getinstantfor
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+let calls = 0;
+
+class Shift24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ calls++;
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12 }),
+ utcInstant.add({ hours: 12 })
+ ];
+ }
+}
+
+const timeZone = new Shift24Hour();
+
+for (const disambiguation of ["earlier", "later", "compatible"]) {
+ timeZone.getInstantFor(new Temporal.PlainDateTime(1970, 1, 1, 12), { disambiguation });
+
+ assert(calls >= 1, "getPossibleInstantsFor should be called at least once");
+ calls = 0;
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/getpossibleinstantsfor-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..c8ad86aa14
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getInstantFor/getpossibleinstantsfor-out-of-range-backward-offset-shift.js
@@ -0,0 +1,50 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getinstantfor
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+
+for (const disambiguation of ["earlier", "later", "compatible"]) {
+ assert.throws(RangeError, () => timeZone.getInstantFor(new Temporal.PlainDateTime(1970, 1, 1, 12), { disambiguation }), "RangeError should be thrown");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getNextTransition/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getNextTransition/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..fd32a4f029
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getNextTransition/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getnexttransition
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00Z[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00Z[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00Z[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.TimeZone("UTC");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.getNextTransition(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..47d9b20d5a
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getoffsetnanosecondsfor
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00Z[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00Z[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00Z[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.TimeZone("UTC");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.getOffsetNanosecondsFor(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getOffsetStringFor/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getOffsetStringFor/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..280857ae31
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getOffsetStringFor/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getoffsetstringfor
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00Z[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00Z[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00Z[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.TimeZone("UTC");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.getOffsetStringFor(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..069c805881
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getplaindatetimefor
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00Z[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00Z[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00Z[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.TimeZone("UTC");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.getPlainDateTimeFor(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/calendar-iso-string.js
new file mode 100644
index 0000000000..5f66016904
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/calendar-iso-string.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getplaindatetimefor
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.TimeZone("UTC");
+
+for (const arg of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const result = instance.getPlainDateTimeFor(new Temporal.Instant(0n), arg);
+ assert.sameValue(result.getISOFields().calendar, "iso8601", `Calendar created from string "${arg}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..0ffa57e472
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getpossibleinstantsfor
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+includes: [compareArray.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.TimeZone("UTC");
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.getPossibleInstantsFor(arg);
+ assert.compareArray(result.map(i => i.epochNanoseconds), [217_123_200_000_000_000n], `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-propertybag-calendar-year-zero.js
index 0afcf6f978..d112ab2be6 100644
--- a/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
const instance = new Temporal.TimeZone("UTC");
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.getPossibleInstantsFor(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..21cfb938e4
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getpossibleinstantsfor
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.TimeZone("UTC");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.getPossibleInstantsFor(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPreviousTransition/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPreviousTransition/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..96c81db475
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/TimeZone/prototype/getPreviousTransition/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getprevioustransition
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00Z[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00Z[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00Z[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.TimeZone("UTC");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.getPreviousTransition(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..3a440b4994
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.compare
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const datetime = new Temporal.ZonedDateTime(0n, "UTC");
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1970, monthCode: "M01", day: 1, timeZone: "UTC", calendar };
+ const result1 = Temporal.ZonedDateTime.compare(arg, datetime);
+ assert.sameValue(result1, 0, `Calendar created from string "${calendar}" (first argument)`);
+ const result2 = Temporal.ZonedDateTime.compare(datetime, arg);
+ assert.sameValue(result2, 0, `Calendar created from string "${calendar}" (second argument)`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..9e09864074
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-backward-offset-shift.js
@@ -0,0 +1,51 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.compare
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+const datetime = new Temporal.ZonedDateTime(0n, timeZone);
+
+assert.throws(RangeError, () => Temporal.ZonedDateTime.compare(arg, datetime), "RangeError should be thrown (first argument)");
+assert.throws(RangeError, () => Temporal.ZonedDateTime.compare(datetime, arg), "RangeError should be thrown (second argument)");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..d2fe4ab99f
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-forward-offset-shift.js
@@ -0,0 +1,46 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.compare
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+const datetime = new Temporal.ZonedDateTime(0n, timeZone);
+
+assert.throws(RangeError, () => Temporal.ZonedDateTime.compare(arg, datetime), "RangeError should be thrown (first argument)");
+assert.throws(RangeError, () => Temporal.ZonedDateTime.compare(datetime, arg), "RangeError should be thrown (second argument)");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..a3f48aa25a
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/compare/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,32 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.compare
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const datetime = new Temporal.ZonedDateTime(0n, "UTC");
+
+const invalidStrings = [
+ ["1970-01-01T00:00[UTC][U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00[UTC][u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00[UTC][FOO=bar]", "invalid capitalized unrecognized key"],
+];
+
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => Temporal.ZonedDateTime.compare(arg, datetime),
+ `annotation keys must be lowercase: ${arg} - ${descr} (first argument)`
+ );
+ assert.throws(
+ RangeError,
+ () => Temporal.ZonedDateTime.compare(datetime, arg),
+ `annotation keys must be lowercase: ${arg} - ${descr} (second argument)`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..ea5074b440
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.from
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const timeZone = new Temporal.TimeZone("UTC");
+const arg = { year: 1970, monthCode: "M01", day: 1, timeZone, calendar };
+ const result = Temporal.ZonedDateTime.from(arg);
+ assert.sameValue(result.calendarId, "iso8601", `Calendar created from string "${calendar}"`);
+ assert.sameValue(result.getISOFields().calendar, "iso8601", "calendar slot stores a string");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-calendar-year-zero.js
index d736edeb21..2741d61844 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-calendar-year-zero.js
@@ -16,7 +16,8 @@ const invalidStrings = [
"-000000-10-31T17:45+00:00[UTC]",
];
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => Temporal.ZonedDateTime.from(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..1c4da44c1b
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-backward-offset-shift.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.from
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+assert.throws(RangeError, () => Temporal.ZonedDateTime.from(arg), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..fad0037aae
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-forward-offset-shift.js
@@ -0,0 +1,44 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.from
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+assert.throws(RangeError, () => Temporal.ZonedDateTime.from(arg), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..1ea7b9aea4
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/from/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,32 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.from
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00[UTC][U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00[UTC][u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00[UTC][FOO=bar]", "invalid capitalized unrecognized key"],
+];
+
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => Temporal.ZonedDateTime.from(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+ for (const offset of ["use", "prefer", "ignore", "reject"]) {
+ assert.throws(
+ RangeError,
+ () => Temporal.ZonedDateTime.from(arg, { offset }),
+ `annotation keys must be lowercase: ${arg} - ${descr} (offset ${offset})`
+ );
+ }
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..4ee800c7bc
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.equals
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const timeZone = new Temporal.TimeZone("UTC");
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1970, monthCode: "M01", day: 1, timeZone, calendar };
+ const result = instance.equals(arg);
+ assert.sameValue(result, true, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-calendar-year-zero.js
index c0230f2f3e..196e992b08 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-calendar-year-zero.js
@@ -17,7 +17,8 @@ const invalidStrings = [
];
const timeZone = new Temporal.TimeZone("UTC");
const instance = new Temporal.ZonedDateTime(0n, timeZone);
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.equals(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..bf311b856d
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-backward-offset-shift.js
@@ -0,0 +1,50 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.equals
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+assert.throws(RangeError, () => instance.equals(arg), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..8b27e78eff
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-forward-offset-shift.js
@@ -0,0 +1,45 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.equals
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+assert.throws(RangeError, () => instance.equals(arg), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..89b0d4d509
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.equals
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00[UTC][U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00[UTC][u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00[UTC][FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const instance = new Temporal.ZonedDateTime(0n, "UTC");
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.equals(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js
new file mode 100644
index 0000000000..f41e0be158
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.hoursinday
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants can be at most 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+let calls = 0;
+
+class Shift24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ calls++;
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12 })];
+ }
+}
+
+const timeZone = new Shift24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+instance.hoursInDay;
+
+assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..7a41b9456e
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js
@@ -0,0 +1,44 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.hoursinday
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+assert.throws(RangeError, () => instance.hoursInDay, "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-maximum-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-maximum-backward-offset-shift.js
new file mode 100644
index 0000000000..8312ef765b
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-maximum-backward-offset-shift.js
@@ -0,0 +1,53 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.hoursinday
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+let calls = 0;
+
+class Shift24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ calls++;
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12 }),
+ utcInstant.add({ hours: 12 })
+ ];
+ }
+}
+
+const timeZone = new Shift24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+instance.hoursInDay;
+
+assert(calls >= 1, "getPossibleInstantsFor should be called at least once");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..566b61b2b0
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.hoursinday
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+assert.throws(RangeError, () => instance.hoursInDay, "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/div-zero.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/div-zero.js
index 776cb7454f..dd912f46d4 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/div-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/div-zero.js
@@ -18,10 +18,8 @@ class TimeZone extends Temporal.TimeZone {
}
}
-const units = ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"];
-for (const smallestUnit of units) {
- const zdt = new Temporal.ZonedDateTime(0n, new TimeZone("UTC"));
- assert.throws(RangeError, () => zdt.round({ smallestUnit, roundingIncrement: 2 }), `zero day-length with smallestUnit ${smallestUnit}`);
-}
+const zdt = new Temporal.ZonedDateTime(0n, new TimeZone("UTC"));
+
+assert.throws(RangeError, () => zdt.round({ smallestUnit: "day" }), `zero day-length with smallestUnit 'day'`);
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getoffsetnanosecondsfor-maximum-forward-offset-shift.js
new file mode 100644
index 0000000000..1d3a5425d0
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getoffsetnanosecondsfor-maximum-forward-offset-shift.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.round
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants can be at most 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+let calls = 0;
+
+class Shift24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ calls++;
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12 })];
+ }
+}
+
+const timeZone = new Shift24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+instance.round({ smallestUnit: "hours" });
+
+assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-called-with-iso8601-calendar.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-called-with-iso8601-calendar.js
index 0c622df167..91c8ec7bc2 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-called-with-iso8601-calendar.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-called-with-iso8601-calendar.js
@@ -51,8 +51,8 @@ const nonBuiltinISOCalendar = new Temporal.Calendar("iso8601");
const timeZone = new SkippedDateTime();
const instance = new Temporal.ZonedDateTime(0n, timeZone, nonBuiltinISOCalendar);
-instance.round({ smallestUnit: "hours" });
+instance.round({ smallestUnit: "day" });
-assert.sameValue(timeZone.calls, 6, "getPossibleInstantsFor should have been called 6 times");
+assert.sameValue(timeZone.calls, 4, "getPossibleInstantsFor should have been called 4 times");
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-maximum-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-maximum-backward-offset-shift.js
new file mode 100644
index 0000000000..0c60d44311
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-maximum-backward-offset-shift.js
@@ -0,0 +1,53 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.round
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+let calls = 0;
+
+class Shift24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ calls++;
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12 }),
+ utcInstant.add({ hours: 12 })
+ ];
+ }
+}
+
+const timeZone = new Shift24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+instance.round({ smallestUnit: "hours" });
+
+assert(calls >= 1, "getPossibleInstantsFor should be called at least once");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..dabcfe8ed6
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-out-of-range-backward-offset-shift.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.round
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+assert.throws(RangeError, () => instance.round({ smallestUnit: "hours" }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/order-of-operations.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/order-of-operations.js
index 48ca638b39..65fac9036c 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/order-of-operations.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/order-of-operations.js
@@ -24,10 +24,6 @@ const expected = [
"get this.timeZone.getPossibleInstantsFor",
// GetPlainDateTimeFor on receiver's instant
"call this.timeZone.getOffsetNanosecondsFor",
- // GetInstantFor on preceding midnight
- "call this.timeZone.getPossibleInstantsFor",
- // AddDaysToZonedDateTime
- "call this.timeZone.getPossibleInstantsFor",
// InterpretISODateTimeOffset
"call this.timeZone.getPossibleInstantsFor",
"call this.timeZone.getOffsetNanosecondsFor",
@@ -84,36 +80,8 @@ beforeFallBackInstance.round(nextHourOptions);
assert.compareArray(actual, expected, "order of operations with rounding result at repeated wall-clock time");
actual.splice(0); // clear
-const expectedSkippedDateTime = [
- "get options.roundingIncrement",
- "get options.roundingIncrement.valueOf",
- "call options.roundingIncrement.valueOf",
- "get options.roundingMode",
- "get options.roundingMode.toString",
- "call options.roundingMode.toString",
- "get options.smallestUnit",
- "get options.smallestUnit.toString",
- "call options.smallestUnit.toString",
- // lookup
- "get this.timeZone.getOffsetNanosecondsFor",
- "get this.timeZone.getPossibleInstantsFor",
- // GetPlainDateTimeFor on receiver's instant
- "call this.timeZone.getOffsetNanosecondsFor",
- // GetInstantFor on preceding midnight
- "call this.timeZone.getPossibleInstantsFor",
- // DisambiguatePossibleInstants
- "call this.timeZone.getOffsetNanosecondsFor",
- "call this.timeZone.getOffsetNanosecondsFor",
- "call this.timeZone.getPossibleInstantsFor",
- // AddZonedDateTime
- "call this.timeZone.getPossibleInstantsFor",
- // InterpretISODateTimeOffset
- "call this.timeZone.getPossibleInstantsFor",
- "call this.timeZone.getOffsetNanosecondsFor",
-];
-
springForwardInstance.round(options);
-assert.compareArray(actual, expectedSkippedDateTime, "order of operations with preceding midnight at skipped wall-clock time");
+assert.compareArray(actual, expected, "order of operations with preceding midnight at skipped wall-clock time");
actual.splice(0); // clear
const expectedSkippedResult = [
@@ -131,14 +99,6 @@ const expectedSkippedResult = [
"get this.timeZone.getPossibleInstantsFor",
// GetPlainDateTimeFor on receiver's instant
"call this.timeZone.getOffsetNanosecondsFor",
- // GetInstantFor on preceding midnight
- "call this.timeZone.getPossibleInstantsFor",
- // AddDaysToZonedDateTime
- "call this.timeZone.getPossibleInstantsFor",
- // DisambiguatePossibleInstants
- "call this.timeZone.getOffsetNanosecondsFor",
- "call this.timeZone.getOffsetNanosecondsFor",
- "call this.timeZone.getPossibleInstantsFor",
// InterpretISODateTimeOffset
"call this.timeZone.getPossibleInstantsFor",
// DisambiguatePossibleInstants
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/smallest-unit-day-rounding-modes.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/smallest-unit-day-rounding-modes.js
index f694712457..435bfa16d8 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/smallest-unit-day-rounding-modes.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/smallest-unit-day-rounding-modes.js
@@ -41,21 +41,22 @@ class TimeZone extends Temporal.TimeZone {
#count = 0;
#nanoseconds;
- constructor(nanoseconds) {
+ constructor(todayEpochNanoseconds, tomorrowEpochNanoseconds) {
super("UTC");
- this.#nanoseconds = nanoseconds;
+ this.#nanoseconds = [todayEpochNanoseconds, tomorrowEpochNanoseconds];
}
getPossibleInstantsFor(dateTime) {
- if (++this.#count === 2) {
- return [new Temporal.Instant(this.#nanoseconds)];
+ const nanoseconds = this.#nanoseconds[this.#count++];
+ if (nanoseconds === undefined) {
+ return super.getPossibleInstantsFor(dateTime);
}
- return super.getPossibleInstantsFor(dateTime);
+ return [new Temporal.Instant(nanoseconds)];
}
}
-function test(epochNanoseconds, tomorrowEpochNanoseconds, testCases) {
+function test(epochNanoseconds, todayEpochNanoseconds, tomorrowEpochNanoseconds, testCases) {
for (let [roundingMode, expected] of Object.entries(testCases)) {
- let timeZone = new TimeZone(tomorrowEpochNanoseconds);
+ let timeZone = new TimeZone(todayEpochNanoseconds, tomorrowEpochNanoseconds);
let zoned = new Temporal.ZonedDateTime(epochNanoseconds, timeZone);
let result = zoned.round({ smallestUnit: "days", roundingMode });
assert.sameValue(result.epochNanoseconds, expected);
@@ -64,31 +65,35 @@ function test(epochNanoseconds, tomorrowEpochNanoseconds, testCases) {
const oneDay = 24n * 60n * 60n * 1000n * 1000n * 1000n;
-// Test positive divisor (dayLengthNs).
-test(3n, 10n, {
- ceil: oneDay,
+test(3n, undefined, 10n, {
+ ceil: 10n, // end-of-day according to TimeZone protocol
floor: 0n,
trunc: 0n,
halfExpand: 0n,
});
-test(-3n, 10n, {
- ceil: 0n,
+test(-3n, undefined, 10n, {
+ ceil: 10n, // end-of-day according to TimeZone protocol
floor: -oneDay,
trunc: -oneDay,
- halfExpand: 0n,
+ halfExpand: 10n, // end-of-day according to TimeZone protocol
});
-test(-3n, -10n, {
- ceil: oneDay,
- floor: 0n,
- trunc: 0n,
- halfExpand: 0n,
-});
+assert.throws(RangeError, () => {
+ test(-3n, 0n, 10n, { ceil: undefined });
+}, "instant is before TimeZone protocol's start-of-day");
+
+assert.throws(RangeError, () => {
+ test(-3n, undefined, -10n, { ceil: undefined });
+}, "instant is after TimeZone protocol's end-of-day");
+
+assert.throws(RangeError, () => {
+ test(0n, 0n, 0n, { ceil: undefined });
+}, "instant is within zero-duration day");
// Test values at int64 boundaries.
-test(3n, /*INT64_MAX=*/ 9223372036854775807n, {
- ceil: oneDay,
+test(3n, undefined, /*INT64_MAX=*/ 9223372036854775807n, {
+ ceil: /*INT64_MAX=*/ 9223372036854775807n, // end-of-day according to TimeZone protocol
floor: 0n,
trunc: 0n,
halfExpand: 0n,
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/timezone-getpossibleinstantsfor-iterable.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/timezone-getpossibleinstantsfor-iterable.js
index 922b114e0b..28c064e24d 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/timezone-getpossibleinstantsfor-iterable.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/round/timezone-getpossibleinstantsfor-iterable.js
@@ -23,8 +23,6 @@ features: [Temporal]
---*/
const expected = [
- "2001-09-09T00:00:00", // called once on midnight of the input datetime
- "2001-09-10T00:00:00", // called once on the previous value plus one calendar day
"2001-09-09T02:00:00", // called once on the rounding result
];
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..6eeb874839
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const timeZone = new Temporal.TimeZone("UTC");
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1970, monthCode: "M01", day: 1, timeZone, calendar };
+ const result = instance.since(arg);
+ TemporalHelpers.assertDuration(result, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-year-zero.js
index 52a3aee0f5..b5dc42f348 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-year-zero.js
@@ -17,7 +17,8 @@ const invalidStrings = [
];
const timeZone = new Temporal.TimeZone("UTC");
const instance = new Temporal.ZonedDateTime(0n, timeZone);
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.since(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..c5181e85e0
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-backward-offset-shift.js
@@ -0,0 +1,50 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+assert.throws(RangeError, () => instance.since(arg), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..d6f7970e8a
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-forward-offset-shift.js
@@ -0,0 +1,45 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+assert.throws(RangeError, () => instance.since(arg), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..5d0053cad5
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00[UTC][U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00[UTC][u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00[UTC][FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const timeZone = new Temporal.TimeZone("UTC");
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.since(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/calendar-dateadd-called-with-options-undefined.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/calendar-dateadd-called-with-options-undefined.js
index 7a5367bf29..932847c329 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/calendar-dateadd-called-with-options-undefined.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/calendar-dateadd-called-with-options-undefined.js
@@ -14,27 +14,17 @@ features: [Temporal]
const calendar = TemporalHelpers.calendarDateAddUndefinedOptions();
const timeZone = TemporalHelpers.oneShiftTimeZone(new Temporal.Instant(0n), 3600e9);
const earlier = new Temporal.ZonedDateTime(0n, timeZone, calendar);
-
-// Basic difference with largestUnit larger than days.
-// The call comes from this path:
-// ZonedDateTime.since() -> DifferenceZonedDateTime -> AddZonedDateTime -> calendar.dateAdd()
-
-const later1 = new Temporal.ZonedDateTime(1_213_200_000_000_000n, timeZone, calendar);
-later1.since(earlier, { largestUnit: "weeks" });
-assert.sameValue(calendar.dateAddCallCount, 1, "basic difference with largestUnit >days");
+const later = new Temporal.ZonedDateTime(1_213_200_000_000_000n, timeZone, calendar);
// Difference with rounding, with smallestUnit a calendar unit.
// The calls come from these paths:
// ZonedDateTime.since() ->
-// DifferenceZonedDateTime -> AddZonedDateTime -> calendar.dateAdd()
// RoundDuration ->
// MoveRelativeZonedDateTime -> AddZonedDateTime -> calendar.dateAdd()
// MoveRelativeDate -> calendar.dateAdd()
// BalanceDurationRelative -> MoveRelativeDate -> calendar.dateAdd()
-calendar.dateAddCallCount = 0;
-
-later1.since(earlier, { smallestUnit: "weeks" });
-assert.sameValue(calendar.dateAddCallCount, 4, "rounding difference with calendar smallestUnit");
+later.since(earlier, { smallestUnit: "weeks" });
+assert.sameValue(calendar.dateAddCallCount, 3, "rounding difference with calendar smallestUnit");
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/differencezoneddatetime-inconsistent-custom-calendar.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/differencezoneddatetime-inconsistent-custom-calendar.js
new file mode 100644
index 0000000000..391f978f0d
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/differencezoneddatetime-inconsistent-custom-calendar.js
@@ -0,0 +1,56 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: >
+ Throws a RangeError when custom calendar method returns inconsistent result
+info: |
+ DifferenceZonedDateTime ( ... )
+ 8. Repeat 3 times:
+ ...
+ g. If _sign_ = 0, or _timeSign_ = 0, or _sign_ = _timeSign_, then
+ ...
+ viii. Return ? CreateNormalizedDurationRecord(_dateDifference_.[[Years]],
+ _dateDifference_.[[Months]], _dateDifference_.[[Weeks]],
+ _dateDifference_.[[Days]], _norm_).
+ h. Set _dayCorrection_ to _dayCorrection_ + 1.
+ 9. NOTE: This step is only reached when custom calendar or time zone methods
+ return inconsistent values.
+ 10. Throw a *RangeError* exception.
+features: [Temporal]
+---*/
+
+// Based partly on a test case by André Bargull
+
+const fiftyDays12Hours = 50n * 86400_000_000_000n + 12n * 3600_000_000_000n;
+
+{
+ const tz = new (class extends Temporal.TimeZone {
+ getPossibleInstantsFor(dateTime) {
+ return super.getPossibleInstantsFor(dateTime.subtract({ days: 3 }));
+ }
+ })("UTC");
+
+ const zdt1 = new Temporal.ZonedDateTime(0n, tz);
+ const zdt2 = new Temporal.ZonedDateTime(fiftyDays12Hours, tz);
+
+ assert.throws(RangeError, () => zdt2.since(zdt1, { largestUnit: "weeks" }),
+ "Calendar calculation where more than 2 days correction is needed should cause RangeError");
+}
+
+{
+ const cal = new (class extends Temporal.Calendar {
+ dateUntil(one, two, options) {
+ return super.dateUntil(one, two, options).negated();
+ }
+ })("iso8601");
+
+ const zdt1 = new Temporal.ZonedDateTime(0n, "UTC", cal);
+ const zdt2 = new Temporal.ZonedDateTime(fiftyDays12Hours, "UTC", cal);
+
+ assert.throws(RangeError, () => zdt2.since(zdt1, { largestUnit: "weeks" }),
+ "Calendar calculation causing mixed-sign values should cause RangeError");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-month-day-boundary.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-month-day-boundary.js
new file mode 100644
index 0000000000..c45ebd5aa2
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-month-day-boundary.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: >
+ Difference with the endpoint being the end of a skipped hour, chooses the
+ smaller of two possible durations
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+// Based on a test case by Adam Shaw
+
+const timeZone = TemporalHelpers.springForwardFallBackTimeZone();
+
+const d1 = new Temporal.ZonedDateTime(957258000_000_000_000n /* = 2000-05-02T02:00-07:00 */, timeZone);
+const d2 = new Temporal.ZonedDateTime(954669600_000_000_000n /* = 2000-04-02T03:00-07:00 */, timeZone);
+// NOTE: nonexistent hour just before d2
+
+const result = d1.since(d2, { largestUnit: "months" });
+
+TemporalHelpers.assertDuration(
+ result, 0, 0, 0, 29, 23, 0, 0, 0, 0, 0,
+ "Result should not balance up to months, but pick the smaller of two possible durations"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-rounding-result.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-rounding-result.js
new file mode 100644
index 0000000000..c3a0f3f3a2
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-rounding-result.js
@@ -0,0 +1,48 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: >
+ Rounding the resulting duration takes the time zone's UTC offset shifts
+ into account
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+// Based on a test case by Adam Shaw
+
+const timeZone = TemporalHelpers.springForwardFallBackTimeZone();
+
+{
+ // Month-only part of duration lands on skipped DST hour, should not cause
+ // disambiguation
+ const start = new Temporal.ZonedDateTime(
+ 950868000_000_000_000n /* = 2000-02-18T10Z */,
+ timeZone); /* = 2000-02-18T02-08 in local time */
+ const end = new Temporal.ZonedDateTime(
+ 954709200_000_000_000n /* = 2000-04-02T21Z */,
+ timeZone); /* = 2000-04-02T14-07 in local time */
+
+ const duration = start.since(end, { largestUnit: "months" });
+ TemporalHelpers.assertDuration(duration, 0, -1, 0, -15, -11, 0, 0, 0, 0, 0,
+ "1-month rounding window is shortened by DST");
+}
+
+
+{
+ // Month-only part of duration lands on skipped DST hour, should not cause
+ // disambiguation
+ const start = new Temporal.ZonedDateTime(
+ 951991200_000_000_000n /* = 2000-03-02T10Z */,
+ timeZone); /* = 2000-03-02T02-08 in local time */
+ const end = new Temporal.ZonedDateTime(
+ 956005200_000_000_000n /* = 2000-04-17T21Z */,
+ timeZone); /* = 2000-04-17T14-07 in local time */
+
+ const duration = start.since(end, { largestUnit: "months" });
+ TemporalHelpers.assertDuration(duration, 0, -1, 0, -15, -12, 0, 0, 0, 0, 0,
+ "1-month rounding window is not shortened by DST");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/find-intermediate-instant.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/find-intermediate-instant.js
new file mode 100644
index 0000000000..7ef187ce3b
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/find-intermediate-instant.js
@@ -0,0 +1,84 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.zoneddatetime.prototype.until
+description: >
+ Up to 3 intermediate instants may be tried when calculating ZonedDateTime
+ difference
+includes: [compareArray.js, temporalHelpers.js]
+features: [BigInt, Temporal]
+---*/
+
+const calls = [];
+
+const springFallZone = TemporalHelpers.springForwardFallBackTimeZone();
+TemporalHelpers.observeMethod(calls, springFallZone, "getPossibleInstantsFor");
+
+const dateLineZone = TemporalHelpers.crossDateLineTimeZone();
+TemporalHelpers.observeMethod(calls, dateLineZone, "getPossibleInstantsFor");
+
+const zdt2 = new Temporal.ZonedDateTime(946722600_000_000_000n /* = 2000-01-01T02:30 local */, springFallZone);
+
+// Future -> past, without wall-clock overshoot
+// Expects valid intermediate Instant WITHOUT day correction (computed once)
+{
+ const zdt1 = new Temporal.ZonedDateTime(949442400_000_000_000n /* = 2000-02-01T14:00 local */, springFallZone);
+ const result = zdt1.since(zdt2, { largestUnit: "years" });
+ TemporalHelpers.assertDuration(result, 0, 1, 0, 0, 11, 30, 0, 0, 0, 0, "no wall-clock overshoot, no DST");
+ assert.compareArray(calls, [
+ "call getPossibleInstantsFor", // first intermediate in DifferenceZonedDateTime
+ ], "one intermediate should be tried");
+}
+
+calls.splice(0); // clear
+
+// Future -> past, WITH wall-clock overshoot
+// Expects valid intermediate Instant with guaranteed 1-DAY correction (computed once)
+{
+ const zdt1 = new Temporal.ZonedDateTime(949395600_000_000_000n /* = 2000-02-01T01:00 local */, springFallZone);
+ const result = zdt1.since(zdt2, { largestUnit: "years" });
+ TemporalHelpers.assertDuration(result, 0, 0, 0, 30, 22, 30, 0, 0, 0, 0, "wall-clock overshoot, no DST");
+ assert.compareArray(calls, [
+ "call getPossibleInstantsFor", // first intermediate in DifferenceZonedDateTime
+ ], "one intermediate should be tried");
+}
+
+calls.splice(0); // clear
+
+// Future -> past, WITH wall-clock overshoot
+// Expects valid intermediate Instant with guaranteed 1-DAY correction (computed once)
+// Intermediate Instant falls within spring DST gap and gets pushed forward,
+// but since moving from future -> past, not possible to exacerbate overflow,
+// so no other day corrections.
+{
+ const end = new Temporal.ZonedDateTime(957258000_000_000_000n /* = 2000-05-02T02:00 local */, springFallZone);
+ const start = new Temporal.ZonedDateTime(954671400_000_000_000n /* = 2000-04-02T03:30-07:00 local */, springFallZone);
+ const result = end.since(start, { largestUnit: "years" });
+ TemporalHelpers.assertDuration(result, 0, 0, 0, 29, 22, 30, 0, 0, 0, 0, "wall-clock overshoot, inconsiquential DST");
+ assert.compareArray(calls, [
+ "call getPossibleInstantsFor", // first intermediate in DifferenceZonedDateTime
+ ], "one intermediate should be tried");
+}
+
+calls.splice(0); // clear
+
+// Past -> future, WITH wall-clock overshoot
+// Tries intermediate Instant with 1-DAY correction (first compute)
+// Then, ANOTHER day correction because updated intermediate Instant falls within dateline DST gap,
+// pushing it forward, causing wall-clock overshoot again
+// (Not possible when going backwards)
+// (This test is just the same as the corresponding one in until(), but negative)
+{
+ const start = new Temporal.ZonedDateTime(1325102400_000_000_000n /* = 2011-12-28T10:00 local */, dateLineZone);
+ const end = new Temporal.ZonedDateTime(1325257200_000_000_000n /* = 2011-12-31T05:00 local */, dateLineZone);
+ const result = start.since(end, { largestUnit: "days" });
+ TemporalHelpers.assertDuration(result, 0, 0, 0, -1, -19, 0, 0, 0, 0, 0, "wall-clock overshoot, consiquential DST");
+ assert.compareArray(calls, [
+ "call getPossibleInstantsFor", // first intermediate in DifferenceZonedDateTime
+ "call getPossibleInstantsFor", // second intermediate in DifferenceZonedDateTime
+ "call getPossibleInstantsFor", // DisambiguatePossibleInstants on second intermediate
+ ], "two intermediates should be tried, with disambiguation");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js
index 23f993f25f..454d5d216a 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js
@@ -5,74 +5,42 @@
/*---
esid: sec-temporal.zoneddatetime.prototype.since
description: >
- NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer
+ NormalizedTimeDurationToDays should not be able to loop arbitrarily.
info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
...
- 21. Repeat, while done is false,
- a. Let oneDayFarther be ? AddDaysToZonedDateTime(relativeResult.[[Instant]],
- relativeResult.[[DateTime]], timeZoneRec, zonedRelativeTo.[[Calendar]], sign).
- b. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(oneDayFarther.[[EpochNanoseconds]],
- relativeResult.[[EpochNanoseconds]]).
- c. Let oneDayLess be ? SubtractNormalizedTimeDuration(norm, dayLengthNs).
- c. If NormalizedTimeDurationSign(oneDayLess) × sign ≥ 0, then
- i. Set norm to oneDayLess.
- ii. Set relativeResult to oneDayFarther.
- iii. Set days to days + sign.
- d. Else,
- i. Set done to true.
-includes: [temporalHelpers.js]
+ 22. If NormalizedTimeDurationSign(_oneDayLess_) × _sign_ ≥ 0, then
+ a. Set _norm_ to _oneDayLess_.
+ b. Set _relativeResult_ to _oneDayFarther_.
+ c. Set _days_ to _days_ + _sign_.
+ d. Set _oneDayFarther_ to ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_).
+ e. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther.[[EpochNanoseconds]], relativeResult.[[EpochNanoseconds]]).
+ f. If NormalizedTimeDurationSign(? SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_)) × _sign_ ≥ 0, then
+ i. Throw a *RangeError* exception.
features: [Temporal]
---*/
-const calls = [];
const dayLengthNs = 86400000000000n;
-const other = new Temporal.ZonedDateTime(dayLengthNs, "UTC", "iso8601");
-
-function createRelativeTo(count) {
- const dayInstant = new Temporal.Instant(dayLengthNs);
- const substitutions = [];
- const timeZone = new Temporal.TimeZone("UTC");
- // Return constant value for first _count_ calls
- TemporalHelpers.substituteMethod(
- timeZone,
- "getPossibleInstantsFor",
- substitutions
- );
- substitutions.length = count;
- let i = 0;
- for (i = 0; i < substitutions.length; i++) {
- // (this value)
- substitutions[i] = [dayInstant];
+const dayInstant = new Temporal.Instant(dayLengthNs);
+let calls = 0;
+const timeZone = new class extends Temporal.TimeZone {
+ getPossibleInstantsFor() {
+ calls++;
+ return [dayInstant];
}
- // Record calls in calls[]
- TemporalHelpers.observeMethod(calls, timeZone, "getPossibleInstantsFor");
- return new Temporal.ZonedDateTime(0n, timeZone);
-}
-
-let zdt = createRelativeTo(50);
-calls.splice(0); // Reset calls list after ZonedDateTime construction
-zdt.since(other, {
- largestUnit: "day",
-});
-assert.sameValue(
- calls.length,
- 50 + 1,
- "Expected ZonedDateTime.since to call getPossibleInstantsFor correct number of times"
-);
+}("UTC");
-zdt = createRelativeTo(100);
-calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
-zdt.since(other, {
- largestUnit: "day",
-});
-assert.sameValue(
- calls.length,
- 100 + 1,
- "Expected ZonedDateTime.since to call getPossibleInstantsFor correct number of times"
-);
+const zdt = new Temporal.ZonedDateTime(0n, timeZone);
+const other = new Temporal.ZonedDateTime(dayLengthNs * 2n, "UTC", "iso8601");
-zdt = createRelativeTo(105);
-assert.throws(RangeError, () => zdt.since(other, { largestUnit: "day" }), "105 days > 2⁵³ ns");
+assert.throws(RangeError, () => zdt.since(other, { largestUnit: "day", smallestUnit: "second" }), "indefinite loop is prevented");
+assert.sameValue(calls, 4, "getPossibleInstantsFor is not called indefinitely");
+ // Expected calls:
+ // DifferenceTemporalZonedDateTime ->
+ // DifferenceZonedDateTime -> GetInstantFor (1)
+ // NormalizedTimeDurationToDays ->
+ // AddDaysToZonedDateTime (2, step 12)
+ // AddDaysToZonedDateTime (3, step 15)
+ // AddDaysToZonedDateTime (4, step 18.d)
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js
index 0c0b07f5dc..a6af0a69bc 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js
@@ -8,12 +8,12 @@ description: >
RangeErrors.
info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
- 22. If days < 0 and sign = 1, throw a RangeError exception.
- 23. If days > 0 and sign = -1, throw a RangeError exception.
+ 23. If days < 0 and sign = 1, throw a RangeError exception.
+ 24. If days > 0 and sign = -1, throw a RangeError exception.
...
- 25. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception.
+ 26. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception.
...
- 28. If dayLength ≥ 2⁵³, throw a RangeError exception.
+ 29. If dayLength ≥ 2⁵³, throw a RangeError exception.
features: [Temporal, BigInt]
includes: [temporalHelpers.js]
---*/
@@ -40,13 +40,16 @@ const dayNs = 86_400_000_000_000;
const zeroZDT = new Temporal.ZonedDateTime(0n, "UTC");
const oneZDT = new Temporal.ZonedDateTime(1n, "UTC");
const epochInstant = new Temporal.Instant(0n);
-const options = { largestUnit: "days" };
+const options = { largestUnit: "days", smallestUnit: "seconds", roundingMode: "expand" };
-// Step 22: days < 0 and sign = 1
+// Step 23: days < 0 and sign = 1
let start = new Temporal.ZonedDateTime(
0n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
- [[epochInstant]], // Returned in step 16, setting _relativeResult_
+ [
+ TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally in DifferenceZonedDateTime
+ [epochInstant], // Returned in step 16, setting _relativeResult_
+ ],
[
// Behave normally in 2 calls made prior to NormalizedTimeDurationToDays
TemporalHelpers.SUBSTITUTE_SKIP,
@@ -60,14 +63,18 @@ assert.throws(RangeError, () =>
start.since(
oneZDT, // Sets DifferenceZonedDateTime _ns2_
options
- )
+ ),
+ "days < 0 and sign = 1"
);
-// Step 23: days > 0 and sign = -1
+// Step 24: days > 0 and sign = -1
start = new Temporal.ZonedDateTime(
1n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
- [[epochInstant]], // Returned in step 16, setting _relativeResult_
+ [
+ TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally in DifferenceZonedDateTime
+ [epochInstant], // Returned in step 16, setting _relativeResult_
+ ],
[
// Behave normally in 2 calls made prior to NormalizedTimeDurationToDays
TemporalHelpers.SUBSTITUTE_SKIP,
@@ -81,14 +88,18 @@ assert.throws(RangeError, () =>
start.since(
zeroZDT, // Sets DifferenceZonedDateTime _ns2_
options
- )
+ ),
+ "days > 0 and sign = -1"
);
-// Step 25: nanoseconds > 0 and sign = -1
+// Step 26: nanoseconds > 0 and sign = -1
start = new Temporal.ZonedDateTime(
1n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
- [[new Temporal.Instant(-1n)]], // Returned in step 16, setting _relativeResult_
+ [
+ TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally in DifferenceZonedDateTime
+ [new Temporal.Instant(-2_000_000_000n)], // Returned in step 16, setting _relativeResult_
+ ],
[
// Behave normally in 2 calls made prior to NormalizedTimeDurationToDays
TemporalHelpers.SUBSTITUTE_SKIP,
@@ -102,16 +113,20 @@ assert.throws(RangeError, () =>
start.since(
zeroZDT, // Sets DifferenceZonedDateTime _ns2_
options
- )
+ ),
+ "norm > 0 and sign = -1"
);
-// Step 28: day length is an unsafe integer
+// Step 29: day length is an unsafe integer
start = new Temporal.ZonedDateTime(
0n,
timeZoneSubstituteValues(
- // Not called in step 16 because _days_ = 0
- // Returned in step 21.a, making _oneDayFarther_ 2^53 ns later than _relativeResult_
- [[new Temporal.Instant(2n ** 53n)]],
+ [
+ TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally in DifferenceZonedDateTime
+ // Not called in step 16 because _days_ = 0
+ // Returned in step 19, making _oneDayFarther_ 2^53 ns later than _relativeResult_
+ [new Temporal.Instant(2n ** 53n)],
+ ],
[]
)
);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/order-of-operations.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/order-of-operations.js
index 566a1ce192..90fc1e0475 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/order-of-operations.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/order-of-operations.js
@@ -306,10 +306,6 @@ assert.compareArray(actual, [
// DifferenceZonedDateTime
"call this.timeZone.getOffsetNanosecondsFor",
"call this.timeZone.getOffsetNanosecondsFor",
- // NanosecondsToDays
- "call this.timeZone.getOffsetNanosecondsFor",
- "call this.timeZone.getOffsetNanosecondsFor",
- // NanosecondsToDays → AddDaysToZonedDateTime
"call this.timeZone.getPossibleInstantsFor",
], "order of operations with identical wall-clock times and largestUnit a calendar unit");
actual.splice(0); // clear
@@ -328,17 +324,8 @@ const expectedOpsForCalendarDifference = [
"call this.timeZone.getOffsetNanosecondsFor",
// DifferenceZonedDateTime
"call this.timeZone.getOffsetNanosecondsFor",
- // DifferenceISODateTime
- "call this.calendar.dateUntil",
- // AddZonedDateTime
- "call this.calendar.dateAdd",
- "call this.timeZone.getPossibleInstantsFor",
- // NanosecondsToDays
- "call this.timeZone.getOffsetNanosecondsFor",
- "call this.timeZone.getOffsetNanosecondsFor",
- // NanosecondsToDays → AddDaysToZonedDateTime
- "call this.timeZone.getPossibleInstantsFor",
"call this.timeZone.getPossibleInstantsFor",
+ "call this.calendar.dateUntil",
];
const expectedOpsForCalendarRounding = [
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/wrapping-at-end-of-month.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/wrapping-at-end-of-month.js
new file mode 100644
index 0000000000..3fddf0d09b
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/wrapping-at-end-of-month.js
@@ -0,0 +1,120 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: Tests balancing of days to months at end of month
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+// Difference between end of longer month to end of following shorter month
+{
+ const end = new Temporal.ZonedDateTime(5011200_000_000_000n /* = 1970-02-28T00Z */, "UTC");
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(2332800_000_000_000n /* = 1970-01-28T00Z */, "UTC").since(end, { largestUnit }),
+ 0, -1, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Jan 28th to Feb 28th is one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(2419200_000_000_000n /* = 1970-01-29T00Z */, "UTC").since(end, { largestUnit }),
+ 0, 0, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Feb 28th is 30 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(2505600_000_000_000n /* = 1970-01-30T00Z */, "UTC").since(end, { largestUnit }),
+ 0, 0, 0, -29, 0, 0, 0, 0, 0, 0,
+ "Jan 30th to Feb 28th is 29 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(2592000_000_000_000n /* = 1970-01-31T00Z */, "UTC").since(end, { largestUnit }),
+ 0, 0, 0, -28, 0, 0, 0, 0, 0, 0,
+ "Jan 31st to Feb 28th is 28 days, not one month"
+ );
+ }
+}
+
+// Difference between end of leap-year January to end of leap-year February
+{
+ const end = new Temporal.ZonedDateTime(68169600_000_000_000n /* = 1972-02-29T00Z */, "UTC");
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(65491200_000_000_000n /* = 1972-01-29T00Z */, "UTC").since(end, { largestUnit }),
+ 0, -1, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Feb 29th is one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(65577600_000_000_000n /* = 1972-01-30T00Z */, "UTC").since(end, { largestUnit }),
+ 0, 0, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Jan 30th to Feb 29th is 30 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(65664000_000_000_000n /* = 1972-01-31T00Z */, "UTC").since(end, { largestUnit }),
+ 0, 0, 0, -29, 0, 0, 0, 0, 0, 0,
+ "Jan 31st to Feb 29th is 29 days, not one month"
+ );
+ }
+}
+
+// Difference between end of longer month to end of not-immediately-following
+// shorter month
+{
+ const end = new Temporal.ZonedDateTime(28771200_000_000_000n /* = 1970-11-30T00Z */, "UTC");
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(20822400_000_000_000n /* = 1970-08-30T00Z */, "UTC").since(end, { largestUnit }),
+ 0, -3, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Aug 30th to Nov 30th is 3 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(20908800_000_000_000n /* = 1970-08-31T00Z */, "UTC").since(end, { largestUnit }),
+ 0, -2, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Aug 31st to Nov 30th is 2 months 30 days, not 3 months"
+ );
+ }
+}
+
+// Difference between end of longer month in one year to shorter month in
+// later year
+{
+ const end = new Temporal.ZonedDateTime(104976000_000_000_000n /* = 1973-04-30T00Z */, "UTC");
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(31363200_000_000_000n /* = 1970-12-30T00Z */, "UTC").since(end, { largestUnit: "months" }),
+ 0, -28, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 28 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(31363200_000_000_000n /* = 1970-12-30T00Z */, "UTC").since(end, { largestUnit: "years" }),
+ -2, -4, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 2 years, 4 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(31449600_000_000_000n /* = 1970-12-31T00Z */, "UTC").since(end, { largestUnit: "months" }),
+ 0, -27, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 27 months, 30 days, not 28 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(31449600_000_000_000n /* = 1970-12-31T00Z */, "UTC").since(end, { largestUnit: "years" }),
+ -2, -3, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 2 years, 3 months, 30 days, not 2 years 4 months"
+ );
+}
+
+// Difference where months passes through a month that's the same length or
+// shorter than either the start or end month
+{
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(2419200_000_000_000n /* = 1970-01-29T00Z */, "UTC").since(new Temporal.ZonedDateTime(7430400_000_000_000n /* = 1970-03-28T00Z */, "UTC"), { largestUnit: "months" }),
+ 0, -1, 0, -28, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Mar 28th is 1 month 28 days, not 58 days"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(2592000_000_000_000n /* = 1970-01-31T00Z */, "UTC").since(new Temporal.ZonedDateTime(44409600_000_000_000n /* = 1971-05-30T00Z */, "UTC"), { largestUnit: "years" }),
+ -1, -3, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Jan 31st 1970 to May 30th 1971 is 1 year, 3 months, 30 days, not 1 year, 2 months, 60 days"
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js
new file mode 100644
index 0000000000..d71f6be08e
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.startofday
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants can be at most 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+let calls = 0;
+
+class Shift24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 0n
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ calls++;
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12 })];
+ }
+}
+
+const timeZone = new Shift24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+instance.startOfDay();
+
+assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..82e86c4ee5
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js
@@ -0,0 +1,44 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.startofday
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 0n
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+assert.throws(RangeError, () => instance.startOfDay(), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-maximum-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-maximum-backward-offset-shift.js
new file mode 100644
index 0000000000..f1342dfe2d
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-maximum-backward-offset-shift.js
@@ -0,0 +1,53 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.startofday
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+let calls = 0;
+
+class Shift24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ calls++;
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12 }),
+ utcInstant.add({ hours: 12 })
+ ];
+ }
+}
+
+const timeZone = new Shift24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+instance.startOfDay();
+
+assert(calls >= 1, "getPossibleInstantsFor should be called at least once");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..06055c287e
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.startofday
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+assert.throws(RangeError, () => instance.startOfDay(), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..b01b28a4bd
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.until
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const timeZone = new Temporal.TimeZone("UTC");
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1970, monthCode: "M01", day: 1, timeZone, calendar };
+ const result = instance.until(arg);
+ TemporalHelpers.assertDuration(result, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-calendar-year-zero.js
index f0c915198e..f69f22d2b8 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-calendar-year-zero.js
@@ -17,7 +17,8 @@ const invalidStrings = [
];
const timeZone = new Temporal.TimeZone("UTC");
const instance = new Temporal.ZonedDateTime(0n, timeZone);
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.until(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..bf1b4089f0
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-backward-offset-shift.js
@@ -0,0 +1,50 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.until
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+assert.throws(RangeError, () => instance.until(arg), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..891307c99d
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-forward-offset-shift.js
@@ -0,0 +1,45 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.until
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+assert.throws(RangeError, () => instance.until(arg), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..7226f0cb8c
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.until
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00[UTC][U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00[UTC][u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00[UTC][FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const timeZone = new Temporal.TimeZone("UTC");
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.until(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/calendar-dateadd-called-with-options-undefined.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/calendar-dateadd-called-with-options-undefined.js
index 5710b9c2b4..14a26aa8ef 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/calendar-dateadd-called-with-options-undefined.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/calendar-dateadd-called-with-options-undefined.js
@@ -14,27 +14,17 @@ features: [Temporal]
const calendar = TemporalHelpers.calendarDateAddUndefinedOptions();
const timeZone = TemporalHelpers.oneShiftTimeZone(new Temporal.Instant(0n), 3600e9);
const earlier = new Temporal.ZonedDateTime(0n, timeZone, calendar);
-
-// Basic difference with largestUnit larger than days.
-// The call comes from this path:
-// ZonedDateTime.until() -> DifferenceZonedDateTime -> AddZonedDateTime -> calendar.dateAdd()
-
-const later1 = new Temporal.ZonedDateTime(1_213_200_000_000_000n, timeZone, calendar);
-earlier.until(later1, { largestUnit: "weeks" });
-assert.sameValue(calendar.dateAddCallCount, 1, "basic difference with largestUnit >days");
+const later = new Temporal.ZonedDateTime(1_213_200_000_000_000n, timeZone, calendar);
// Difference with rounding, with smallestUnit a calendar unit.
// The calls come from these paths:
// ZonedDateTime.until() ->
-// DifferenceZonedDateTime -> AddZonedDateTime -> calendar.dateAdd()
// RoundDuration ->
// MoveRelativeZonedDateTime -> AddZonedDateTime -> calendar.dateAdd()
// MoveRelativeDate -> calendar.dateAdd()
// BalanceDurationRelative -> MoveRelativeDate -> calendar.dateAdd()
-calendar.dateAddCallCount = 0;
-
-earlier.until(later1, { smallestUnit: "weeks" });
-assert.sameValue(calendar.dateAddCallCount, 4, "rounding difference with calendar smallestUnit");
+earlier.until(later, { smallestUnit: "weeks" });
+assert.sameValue(calendar.dateAddCallCount, 3, "rounding difference with calendar smallestUnit");
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/differencezoneddatetime-inconsistent-custom-calendar.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/differencezoneddatetime-inconsistent-custom-calendar.js
new file mode 100644
index 0000000000..7b77608492
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/differencezoneddatetime-inconsistent-custom-calendar.js
@@ -0,0 +1,56 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.zoneddatetime.prototype.until
+description: >
+ Throws a RangeError when custom calendar method returns inconsistent result
+info: |
+ DifferenceZonedDateTime ( ... )
+ 8. Repeat 3 times:
+ ...
+ g. If _sign_ = 0, or _timeSign_ = 0, or _sign_ = _timeSign_, then
+ ...
+ viii. Return ? CreateNormalizedDurationRecord(_dateDifference_.[[Years]],
+ _dateDifference_.[[Months]], _dateDifference_.[[Weeks]],
+ _dateDifference_.[[Days]], _norm_).
+ h. Set _dayCorrection_ to _dayCorrection_ + 1.
+ 9. NOTE: This step is only reached when custom calendar or time zone methods
+ return inconsistent values.
+ 10. Throw a *RangeError* exception.
+features: [Temporal]
+---*/
+
+// Based partly on a test case by André Bargull
+
+const fiftyDays12Hours = 50n * 86400_000_000_000n + 12n * 3600_000_000_000n;
+
+{
+ const tz = new (class extends Temporal.TimeZone {
+ getPossibleInstantsFor(dateTime) {
+ return super.getPossibleInstantsFor(dateTime.add({ days: 3 }));
+ }
+ })("UTC");
+
+ const zdt1 = new Temporal.ZonedDateTime(0n, tz);
+ const zdt2 = new Temporal.ZonedDateTime(fiftyDays12Hours, tz);
+
+ assert.throws(RangeError, () => zdt1.until(zdt2, { largestUnit: "weeks" }),
+ "Calendar calculation where more than 2 days correction is needed should cause RangeError");
+}
+
+{
+ const cal = new (class extends Temporal.Calendar {
+ dateUntil(one, two, options) {
+ return super.dateUntil(one, two, options).negated();
+ }
+ })("iso8601");
+
+ const zdt1 = new Temporal.ZonedDateTime(0n, "UTC", cal);
+ const zdt2 = new Temporal.ZonedDateTime(fiftyDays12Hours, "UTC", cal);
+
+ assert.throws(RangeError, () => zdt1.until(zdt2, { largestUnit: "weeks" }),
+ "Calendar calculation causing mixed-sign values should cause RangeError");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/dst-month-day-boundary.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/dst-month-day-boundary.js
new file mode 100644
index 0000000000..73596e06c5
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/dst-month-day-boundary.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.until
+description: >
+ Difference with the endpoint being the end of a skipped hour, chooses the
+ smaller of two possible durations
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+// Based on a test case by Adam Shaw
+
+const timeZone = TemporalHelpers.springForwardFallBackTimeZone();
+
+const d1 = new Temporal.ZonedDateTime(957258000_000_000_000n /* = 2000-05-02T02:00-07:00 */, timeZone);
+const d2 = new Temporal.ZonedDateTime(954669600_000_000_000n /* = 2000-04-02T03:00-07:00 */, timeZone);
+// NOTE: nonexistent hour just before d2
+
+const result = d1.until(d2, { largestUnit: "months" });
+
+TemporalHelpers.assertDuration(
+ result, 0, 0, 0, -29, -23, 0, 0, 0, 0, 0,
+ "Result should not balance up to months, but pick the smaller of two possible durations"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/dst-rounding-result.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/dst-rounding-result.js
new file mode 100644
index 0000000000..e6faed63f3
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/dst-rounding-result.js
@@ -0,0 +1,48 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.zoneddatetime.prototype.until
+description: >
+ Rounding the resulting duration takes the time zone's UTC offset shifts
+ into account
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+// Based on a test case by Adam Shaw
+
+const timeZone = TemporalHelpers.springForwardFallBackTimeZone();
+
+{
+ // Month-only part of duration lands on skipped DST hour, should not cause
+ // disambiguation
+ const start = new Temporal.ZonedDateTime(
+ 950868000_000_000_000n /* = 2000-02-18T10Z */,
+ timeZone); /* = 2000-02-18T02-08 in local time */
+ const end = new Temporal.ZonedDateTime(
+ 954709200_000_000_000n /* = 2000-04-02T21Z */,
+ timeZone); /* = 2000-04-02T14-07 in local time */
+
+ const duration = start.until(end, { largestUnit: "months" });
+ TemporalHelpers.assertDuration(duration, 0, 1, 0, 15, 11, 0, 0, 0, 0, 0,
+ "1-month rounding window is shortened by DST");
+}
+
+
+{
+ // Month-only part of duration lands on skipped DST hour, should not cause
+ // disambiguation
+ const start = new Temporal.ZonedDateTime(
+ 951991200_000_000_000n /* = 2000-03-02T10Z */,
+ timeZone); /* = 2000-03-02T02-08 in local time */
+ const end = new Temporal.ZonedDateTime(
+ 956005200_000_000_000n /* = 2000-04-17T21Z */,
+ timeZone); /* = 2000-04-17T14-07 in local time */
+
+ const duration = start.until(end, { largestUnit: "months" });
+ TemporalHelpers.assertDuration(duration, 0, 1, 0, 15, 12, 0, 0, 0, 0, 0,
+ "1-month rounding window is not shortened by DST");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/find-intermediate-instant.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/find-intermediate-instant.js
new file mode 100644
index 0000000000..9440f950a6
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/find-intermediate-instant.js
@@ -0,0 +1,85 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.zoneddatetime.prototype.until
+description: >
+ Up to 3 intermediate instants may be tried when calculating ZonedDateTime
+ difference
+includes: [compareArray.js, temporalHelpers.js]
+features: [BigInt, Temporal]
+---*/
+
+const calls = [];
+
+const springFallZone = TemporalHelpers.springForwardFallBackTimeZone();
+TemporalHelpers.observeMethod(calls, springFallZone, "getPossibleInstantsFor");
+
+const dateLineZone = TemporalHelpers.crossDateLineTimeZone();
+TemporalHelpers.observeMethod(calls, dateLineZone, "getPossibleInstantsFor");
+
+const zdt1 = new Temporal.ZonedDateTime(946722600_000_000_000n /* = 2000-01-01T02:30 local */, springFallZone);
+
+// Past -> future, without wall-clock overshoot
+// Expects valid intermediate Instant WITHOUT day correction (computed once)
+{
+ const zdt2 = new Temporal.ZonedDateTime(949442400_000_000_000n /* = 2000-02-01T14:00 local */, springFallZone);
+ const result = zdt1.until(zdt2, { largestUnit: "years" });
+ TemporalHelpers.assertDuration(result, 0, 1, 0, 0, 11, 30, 0, 0, 0, 0, "no wall-clock overshoot, no DST");
+ assert.compareArray(calls, [
+ "call getPossibleInstantsFor", // first intermediate in DifferenceZonedDateTime
+ ], "one intermediate should be tried");
+}
+
+calls.splice(0); // clear
+
+// Past -> future, WITH wall-clock overshoot
+// Expects valid intermediate Instant with guaranteed 1-DAY correction (computed once)
+{
+ const zdt2 = new Temporal.ZonedDateTime(949395600_000_000_000n /* = 2000-02-01T01:00 local */, springFallZone);
+ const result = zdt1.until(zdt2, { largestUnit: "years" });
+ TemporalHelpers.assertDuration(result, 0, 0, 0, 30, 22, 30, 0, 0, 0, 0, "wall-clock overshoot, no DST");
+ assert.compareArray(calls, [
+ "call getPossibleInstantsFor", // first intermediate in DifferenceZonedDateTime
+ ], "one intermediate should be tried");
+}
+
+calls.splice(0); // clear
+
+// Past -> future, WITH wall-clock overshoot
+// Tries intermediate Instant with 1-DAY correction (first compute)
+// Then, ANOTHER correction because updated intermediate Instant falls within spring DST gap,
+// pushing it forward, causing wall-clock overshoot again
+// (Not possible when going backwards)
+{
+ const zdt2 = new Temporal.ZonedDateTime(954669600_000_000_000n /* = 2000-04-02T02:00 local */, springFallZone);
+ const result = zdt1.until(zdt2, { largestUnit: "years" });
+ TemporalHelpers.assertDuration(result, 0, 3, 0, 0, 23, 30, 0, 0, 0, 0, "wall-clock overshoot, small consiquential DST");
+ assert.compareArray(calls, [
+ "call getPossibleInstantsFor", // first intermediate in DifferenceZonedDateTime
+ "call getPossibleInstantsFor", // DisambiguatePossibleInstants on first intermediate
+ "call getPossibleInstantsFor", // second intermediate in DifferenceZonedDateTime
+ ], "two intermediates should be tried, with disambiguation");
+}
+
+calls.splice(0); // clear
+
+// Past -> future, WITH wall-clock overshoot
+// Tries intermediate Instant with 1-DAY correction (first compute)
+// Then, ANOTHER correction because updated intermediate Instant falls within dateline DST gap,
+// pushing it forward, causing wall-clock overshoot again
+// (Not possible when going backwards)
+// (This test is just the same as the corresponding one in since(), but negative)
+{
+ const start = new Temporal.ZonedDateTime(1325102400_000_000_000n /* = 2011-12-28T10:00 local */, dateLineZone);
+ const end = new Temporal.ZonedDateTime(1325257200_000_000_000n /* = 2011-12-31T05:00 local */, dateLineZone);
+ const result = start.until(end, { largestUnit: "days" });
+ TemporalHelpers.assertDuration(result, 0, 0, 0, 1, 19, 0, 0, 0, 0, 0, "wall-clock overshoot, big consiquential DST");
+ assert.compareArray(calls, [
+ "call getPossibleInstantsFor", // first intermediate in DifferenceZonedDateTime
+ "call getPossibleInstantsFor", // second intermediate in DifferenceZonedDateTime
+ "call getPossibleInstantsFor", // DisambiguatePossibleInstants on second intermediate
+ ], "two intermediates should be tried, with disambiguation");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-loop-arbitrarily.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-loop-arbitrarily.js
index f057d733d9..77886a2042 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-loop-arbitrarily.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-loop-arbitrarily.js
@@ -5,74 +5,42 @@
/*---
esid: sec-temporal.zoneddatetime.prototype.until
description: >
- NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer
+ NormalizedTimeDurationToDays should not be able to loop arbitrarily.
info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
...
- 21. Repeat, while done is false,
- a. Let oneDayFarther be ? AddDaysToZonedDateTime(relativeResult.[[Instant]],
- relativeResult.[[DateTime]], timeZoneRec, zonedRelativeTo.[[Calendar]], sign).
- b. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(oneDayFarther.[[EpochNanoseconds]],
- relativeResult.[[EpochNanoseconds]]).
- c. Let oneDayLess be ? SubtractNormalizedTimeDuration(norm, dayLengthNs).
- c. If NormalizedTimeDurationSign(oneDayLess) × sign ≥ 0, then
- i. Set norm to oneDayLess.
- ii. Set relativeResult to oneDayFarther.
- iii. Set days to days + sign.
- d. Else,
- i. Set done to true.
-includes: [temporalHelpers.js]
+ 22. If NormalizedTimeDurationSign(_oneDayLess_) × _sign_ ≥ 0, then
+ a. Set _norm_ to _oneDayLess_.
+ b. Set _relativeResult_ to _oneDayFarther_.
+ c. Set _days_ to _days_ + _sign_.
+ d. Set _oneDayFarther_ to ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_).
+ e. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther.[[EpochNanoseconds]], relativeResult.[[EpochNanoseconds]]).
+ f. If NormalizedTimeDurationSign(? SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_)) × _sign_ ≥ 0, then
+ i. Throw a *RangeError* exception.
features: [Temporal]
---*/
-const calls = [];
const dayLengthNs = 86400000000000n;
-const other = new Temporal.ZonedDateTime(dayLengthNs, "UTC", "iso8601");
-
-function createRelativeTo(count) {
- const dayInstant = new Temporal.Instant(dayLengthNs);
- const substitutions = [];
- const timeZone = new Temporal.TimeZone("UTC");
- // Return constant value for first _count_ calls
- TemporalHelpers.substituteMethod(
- timeZone,
- "getPossibleInstantsFor",
- substitutions
- );
- substitutions.length = count;
- let i = 0;
- for (i = 0; i < substitutions.length; i++) {
- // (this value)
- substitutions[i] = [dayInstant];
+const dayInstant = new Temporal.Instant(dayLengthNs);
+let calls = 0;
+const timeZone = new class extends Temporal.TimeZone {
+ getPossibleInstantsFor() {
+ calls++;
+ return [dayInstant];
}
- // Record calls in calls[]
- TemporalHelpers.observeMethod(calls, timeZone, "getPossibleInstantsFor");
- return new Temporal.ZonedDateTime(0n, timeZone);
-}
-
-let zdt = createRelativeTo(50);
-calls.splice(0); // Reset calls list after ZonedDateTime construction
-zdt.until(other, {
- largestUnit: "day",
-});
-assert.sameValue(
- calls.length,
- 50 + 1,
- "Expected ZonedDateTime.until to call getPossibleInstantsFor correct number of times"
-);
+}("UTC");
-zdt = createRelativeTo(100);
-calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
-zdt.until(other, {
- largestUnit: "day",
-});
-assert.sameValue(
- calls.length,
- 100 + 1,
- "Expected ZonedDateTime.until to call getPossibleInstantsFor correct number of times"
-);
+const zdt = new Temporal.ZonedDateTime(0n, timeZone);
+const other = new Temporal.ZonedDateTime(dayLengthNs * 2n, "UTC", "iso8601");
-zdt = createRelativeTo(105);
-assert.throws(RangeError, () => zdt.until(other, { largestUnit: "day" }), "105 days > 2⁵³ ns");
+assert.throws(RangeError, () => zdt.until(other, { largestUnit: "day", smallestUnit: "second" }), "indefinite loop is prevented");
+assert.sameValue(calls, 4, "getPossibleInstantsFor is not called indefinitely");
+ // Expected calls:
+ // DifferenceTemporalZonedDateTime ->
+ // DifferenceZonedDateTime -> GetInstantFor (1)
+ // NormalizedTimeDurationToDays ->
+ // AddDaysToZonedDateTime (2, step 12)
+ // AddDaysToZonedDateTime (3, step 15)
+ // AddDaysToZonedDateTime (4, step 18.d)
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-range-errors.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-range-errors.js
index 178b1c981b..479dcf0b0c 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-range-errors.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-range-errors.js
@@ -8,12 +8,12 @@ description: >
RangeErrors.
info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
- 22. If days < 0 and sign = 1, throw a RangeError exception.
- 23. If days > 0 and sign = -1, throw a RangeError exception.
+ 23. If days < 0 and sign = 1, throw a RangeError exception.
+ 24. If days > 0 and sign = -1, throw a RangeError exception.
...
- 25. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception.
+ 26. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception.
...
- 28. If dayLength ≥ 2⁵³, throw a RangeError exception.
+ 29. If dayLength ≥ 2⁵³, throw a RangeError exception.
features: [Temporal, BigInt]
includes: [temporalHelpers.js]
---*/
@@ -40,13 +40,16 @@ const dayNs = 86_400_000_000_000;
const zeroZDT = new Temporal.ZonedDateTime(0n, "UTC");
const oneZDT = new Temporal.ZonedDateTime(1n, "UTC");
const epochInstant = new Temporal.Instant(0n);
-const options = { largestUnit: "days" };
+const options = { largestUnit: "days", smallestUnit: "seconds", roundingMode: "expand" };
-// Step 22: days < 0 and sign = 1
+// Step 23: days < 0 and sign = 1
let start = new Temporal.ZonedDateTime(
0n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
- [[epochInstant]], // Returned in step 16, setting _relativeResult_
+ [
+ TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally in DifferenceZonedDateTime
+ [epochInstant], // Returned in step 16, setting _relativeResult_
+ ],
[
// Behave normally in 2 calls made prior to NormalizedTimeDurationToDays
TemporalHelpers.SUBSTITUTE_SKIP,
@@ -60,14 +63,18 @@ assert.throws(RangeError, () =>
start.until(
oneZDT, // Sets DifferenceZonedDateTime _ns2_
options
- )
+ ),
+ "days < 0 and sign = 1"
);
-// Step 23: days > 0 and sign = -1
+// Step 24: days > 0 and sign = -1
start = new Temporal.ZonedDateTime(
1n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
- [[epochInstant]], // Returned in step 16, setting _relativeResult_
+ [
+ TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally in DifferenceZonedDateTime
+ [epochInstant], // Returned in step 16, setting _relativeResult_
+ ],
[
// Behave normally in 2 calls made prior to NormalizedTimeDurationToDays
TemporalHelpers.SUBSTITUTE_SKIP,
@@ -81,14 +88,18 @@ assert.throws(RangeError, () =>
start.until(
zeroZDT, // Sets DifferenceZonedDateTime _ns2_
options
- )
+ ),
+ "days > 0 and sign = -1"
);
-// Step 25: nanoseconds > 0 and sign = -1
+// Step 26: nanoseconds > 0 and sign = -1
start = new Temporal.ZonedDateTime(
1n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
- [[new Temporal.Instant(-1n)]], // Returned in step 16, setting _relativeResult_
+ [
+ TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally in DifferenceZonedDateTime
+ [new Temporal.Instant(-2_000_000_000n)], // Returned in step 16, setting _relativeResult_
+ ],
[
// Behave normally in 2 calls made prior to NormalizedTimeDurationToDays
TemporalHelpers.SUBSTITUTE_SKIP,
@@ -102,16 +113,20 @@ assert.throws(RangeError, () =>
start.until(
zeroZDT, // Sets DifferenceZonedDateTime _ns2_
options
- )
+ ),
+ "norm > 0 and sign = -1"
);
-// Step 28: day length is an unsafe integer
+// Step 29: day length is an unsafe integer
start = new Temporal.ZonedDateTime(
0n,
timeZoneSubstituteValues(
- // Not called in step 16 because _days_ = 0
- // Returned in step 21.a, making _oneDayFarther_ 2^53 ns later than _relativeResult_
- [[new Temporal.Instant(2n ** 53n)]],
+ [
+ TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally in DifferenceZonedDateTime
+ // Not called in step 16 because _days_ = 0
+ // Returned in step 19, making _oneDayFarther_ 2^53 ns later than _relativeResult_
+ [new Temporal.Instant(2n ** 53n)],
+ ],
[]
)
);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/order-of-operations.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/order-of-operations.js
index 6a233dc989..c88ecfd0d6 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/order-of-operations.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/order-of-operations.js
@@ -306,10 +306,6 @@ assert.compareArray(actual, [
// DifferenceZonedDateTime
"call this.timeZone.getOffsetNanosecondsFor",
"call this.timeZone.getOffsetNanosecondsFor",
- // NanosecondsToDays
- "call this.timeZone.getOffsetNanosecondsFor",
- "call this.timeZone.getOffsetNanosecondsFor",
- // NanosecondsToDays → AddDaysToZonedDateTime
"call this.timeZone.getPossibleInstantsFor",
], "order of operations with identical wall-clock times and largestUnit a calendar unit");
actual.splice(0); // clear
@@ -328,17 +324,8 @@ const expectedOpsForCalendarDifference = [
"call this.timeZone.getOffsetNanosecondsFor",
// DifferenceZonedDateTime
"call this.timeZone.getOffsetNanosecondsFor",
- // DifferenceISODateTime
- "call this.calendar.dateUntil",
- // AddZonedDateTime
- "call this.calendar.dateAdd",
- "call this.timeZone.getPossibleInstantsFor",
- // NanosecondsToDays
- "call this.timeZone.getOffsetNanosecondsFor",
- "call this.timeZone.getOffsetNanosecondsFor",
- // NanosecondsToDays → AddDaysToZonedDateTime
- "call this.timeZone.getPossibleInstantsFor",
"call this.timeZone.getPossibleInstantsFor",
+ "call this.calendar.dateUntil",
];
const expectedOpsForCalendarRounding = [
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/wrapping-at-end-of-month.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/wrapping-at-end-of-month.js
new file mode 100644
index 0000000000..50b40393f1
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/until/wrapping-at-end-of-month.js
@@ -0,0 +1,120 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.until
+description: Tests balancing of days to months at end of month
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+// Difference between end of longer month to end of following shorter month
+{
+ const end = new Temporal.ZonedDateTime(5011200_000_000_000n /* = 1970-02-28T00Z */, "UTC");
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(2332800_000_000_000n /* = 1970-01-28T00Z */, "UTC").until(end, { largestUnit }),
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Jan 28th to Feb 28th is one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(2419200_000_000_000n /* = 1970-01-29T00Z */, "UTC").until(end, { largestUnit }),
+ 0, 0, 0, 30, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Feb 28th is 30 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(2505600_000_000_000n /* = 1970-01-30T00Z */, "UTC").until(end, { largestUnit }),
+ 0, 0, 0, 29, 0, 0, 0, 0, 0, 0,
+ "Jan 30th to Feb 28th is 29 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(2592000_000_000_000n /* = 1970-01-31T00Z */, "UTC").until(end, { largestUnit }),
+ 0, 0, 0, 28, 0, 0, 0, 0, 0, 0,
+ "Jan 31st to Feb 28th is 28 days, not one month"
+ );
+ }
+}
+
+// Difference between end of leap-year January to end of leap-year February
+{
+ const end = new Temporal.ZonedDateTime(68169600_000_000_000n /* = 1972-02-29T00Z */, "UTC");
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(65491200_000_000_000n /* = 1972-01-29T00Z */, "UTC").until(end, { largestUnit }),
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Feb 29th is one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(65577600_000_000_000n /* = 1972-01-30T00Z */, "UTC").until(end, { largestUnit }),
+ 0, 0, 0, 30, 0, 0, 0, 0, 0, 0,
+ "Jan 30th to Feb 29th is 30 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(65664000_000_000_000n /* = 1972-01-31T00Z */, "UTC").until(end, { largestUnit }),
+ 0, 0, 0, 29, 0, 0, 0, 0, 0, 0,
+ "Jan 31st to Feb 29th is 29 days, not one month"
+ );
+ }
+}
+
+// Difference between end of longer month to end of not-immediately-following
+// shorter month
+{
+ const end = new Temporal.ZonedDateTime(28771200_000_000_000n /* = 1970-11-30T00Z */, "UTC");
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(20822400_000_000_000n /* = 1970-08-30T00Z */, "UTC").until(end, { largestUnit }),
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Aug 30th to Nov 30th is 3 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(20908800_000_000_000n /* = 1970-08-31T00Z */, "UTC").until(end, { largestUnit }),
+ 0, 2, 0, 30, 0, 0, 0, 0, 0, 0,
+ "Aug 31st to Nov 30th is 2 months 30 days, not 3 months"
+ );
+ }
+}
+
+// Difference between end of longer month in one year to shorter month in
+// later year
+{
+ const end = new Temporal.ZonedDateTime(104976000_000_000_000n /* = 1973-04-30T00Z */, "UTC");
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(31363200_000_000_000n /* = 1970-12-30T00Z */, "UTC").until(end, { largestUnit: "months" }),
+ 0, 28, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 28 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(31363200_000_000_000n /* = 1970-12-30T00Z */, "UTC").until(end, { largestUnit: "years" }),
+ 2, 4, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 2 years, 4 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(31449600_000_000_000n /* = 1970-12-31T00Z */, "UTC").until(end, { largestUnit: "months" }),
+ 0, 27, 0, 30, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 27 months, 30 days, not 28 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(31449600_000_000_000n /* = 1970-12-31T00Z */, "UTC").until(end, { largestUnit: "years" }),
+ 2, 3, 0, 30, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 2 years, 3 months, 30 days, not 2 years 4 months"
+ );
+}
+
+// Difference where months passes through a month that's the same length or
+// shorter than either the start or end month
+{
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(2419200_000_000_000n /* = 1970-01-29T00Z */, "UTC").until(new Temporal.ZonedDateTime(7430400_000_000_000n /* = 1970-03-28T00Z */, "UTC"), { largestUnit: "months" }),
+ 0, 1, 0, 28, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Mar 28th is 1 month 28 days, not 58 days"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(2592000_000_000_000n /* = 1970-01-31T00Z */, "UTC").until(new Temporal.ZonedDateTime(44409600_000_000_000n /* = 1971-05-30T00Z */, "UTC"), { largestUnit: "years" }),
+ 1, 3, 0, 30, 0, 0, 0, 0, 0, 0,
+ "Jan 31st 1970 to May 30th 1971 is 1 year, 3 months, 30 days, not 1 year, 2 months, 60 days"
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/weekOfYear/custom-calendar-weekofyear.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/weekOfYear/custom-calendar-weekofyear.js
new file mode 100644
index 0000000000..43a7d2ba14
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/weekOfYear/custom-calendar-weekofyear.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.weekofyear
+description: >
+ Temporal.ZonedDateTime.prototype.weekOfYear returns undefined for all
+ custom calendars where weekOfYear() returns undefined.
+features: [Temporal]
+---*/
+
+class CustomCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ weekOfYear() {
+ return undefined;
+ }
+}
+
+const calendar = new CustomCalendar();
+// Epoch Nanoseconds for new Temporal.PlainDateTime(2024, 1, 1, 12, 34, 56, 987, 654, 321, calendar);
+const customCalendarDate = new Temporal.ZonedDateTime(1_704_112_496_987_654_321n, "UTC", calendar);
+assert.sameValue(customCalendarDate.weekOfYear, undefined);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/weekOfYear/validate-calendar-value.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/weekOfYear/validate-calendar-value.js
index 8b18332ce4..b2b83c1d41 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/weekOfYear/validate-calendar-value.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/weekOfYear/validate-calendar-value.js
@@ -9,7 +9,6 @@ features: [Temporal]
---*/
const badResults = [
- [undefined, TypeError],
[null, TypeError],
[false, TypeError],
[Infinity, RangeError],
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-maximum-forward-offset-shift.js
new file mode 100644
index 0000000000..a1c2cec71d
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-maximum-forward-offset-shift.js
@@ -0,0 +1,52 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.with
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants can be at most 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+let calls = 0;
+
+class Shift24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 0n;
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ calls++;
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12 })];
+ }
+}
+
+const timeZone = new Shift24Hour();
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+
+for (const disambiguation of ["earlier", "later", "compatible"]) {
+ instance.with({ day: 1 }, { disambiguation });
+
+ assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice");
+ calls = 0;
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..a93ee7ff32
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js
@@ -0,0 +1,46 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.with
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 0n;
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+for (const disambiguation of ["earlier", "later", "compatible"]) {
+ assert.throws(RangeError, () => instance.with({ day: 1 }, { disambiguation }), "RangeError should be thrown");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-maximum-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-maximum-backward-offset-shift.js
new file mode 100644
index 0000000000..b053782926
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-maximum-backward-offset-shift.js
@@ -0,0 +1,56 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.with
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+let calls = 0;
+
+class Shift24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ calls++;
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12 }),
+ utcInstant.add({ hours: 12 })
+ ];
+ }
+}
+
+const timeZone = new Shift24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+for (const disambiguation of ["earlier", "later", "compatible"]) {
+ instance.with({ day: 1 }, { disambiguation });
+
+ assert(calls >= 1, "getPossibleInstantsFor should be called at least once");
+ calls = 0;
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..7db6925321
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-out-of-range-backward-offset-shift.js
@@ -0,0 +1,51 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.with
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+for (const disambiguation of ["earlier", "later", "compatible"]) {
+ assert.throws(RangeError, () => instance.with({ day: 1 }, { disambiguation }), "RangeError should be thrown");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withCalendar/calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withCalendar/calendar-iso-string.js
new file mode 100644
index 0000000000..834be0ebb6
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withCalendar/calendar-iso-string.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.withcalendar
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", {
+ dateAdd() {},
+ dateFromFields() {},
+ dateUntil() {},
+ day() {},
+ dayOfWeek() {},
+ dayOfYear() {},
+ daysInMonth() {},
+ daysInWeek() {},
+ daysInYear() {},
+ fields() {},
+ id: "replace-me",
+ inLeapYear() {},
+ mergeFields() {},
+ month() {},
+ monthCode() {},
+ monthDayFromFields() {},
+ monthsInYear() {},
+ weekOfYear() {},
+ year() {},
+ yearMonthFromFields() {},
+ yearOfWeek() {},
+});
+
+for (const arg of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const result = instance.withCalendar(arg);
+ assert.sameValue(result.getISOFields().calendar, "iso8601", `Calendar created from string "${arg}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..a0b1fa2928
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.withplaindate
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+features: [Temporal]
+---*/
+
+const timeZone = new Temporal.TimeZone("UTC");
+const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, timeZone);
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ const result = instance.withPlainDate(arg);
+ assert.sameValue(result.epochNanoseconds, 217_129_600_000_000_000n, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-propertybag-calendar-year-zero.js
index 3d7d95215e..52cefeffa3 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-propertybag-calendar-year-zero.js
@@ -17,7 +17,8 @@ const invalidStrings = [
];
const timeZone = new Temporal.TimeZone("UTC");
const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, timeZone);
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.withPlainDate(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..c1f874813b
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.withplaindate
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01[U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const timeZone = new Temporal.TimeZone("UTC");
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.withPlainDate(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-maximum-forward-offset-shift.js
new file mode 100644
index 0000000000..d595cb1952
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-maximum-forward-offset-shift.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.withplaindate
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants can be at most 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+let calls = 0;
+
+class Shift24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ calls++;
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12 })];
+ }
+}
+
+const timeZone = new Shift24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+instance.withPlainDate(new Temporal.PlainDate(1970, 1, 1));
+
+assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..7af0ddc2d1
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js
@@ -0,0 +1,44 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.withplaindate
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+assert.throws(RangeError, () => instance.withPlainDate(new Temporal.PlainDate(1970, 1, 1)), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-maximum-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-maximum-backward-offset-shift.js
new file mode 100644
index 0000000000..2ed4c8f1d9
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-maximum-backward-offset-shift.js
@@ -0,0 +1,53 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.withplaindate
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+let calls = 0;
+
+class Shift24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ calls++;
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12 }),
+ utcInstant.add({ hours: 12 })
+ ];
+ }
+}
+
+const timeZone = new Shift24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+instance.withPlainDate(new Temporal.PlainDate(1970, 1, 1));
+
+assert(calls >= 1, "getPossibleInstantsFor should be called at least once");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..b43bdba300
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-out-of-range-backward-offset-shift.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.withplaindate
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+assert.throws(RangeError, () => instance.withPlainDate(new Temporal.PlainDate(1970, 1, 1)), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..d07df63e1e
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,32 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.withplaintime
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["00:00[U-CA=iso8601]", "invalid capitalized key, time-only"],
+ ["T00:00[U-CA=iso8601]", "invalid capitalized key, time designator"],
+ ["1970-01-01T00:00[U-CA=iso8601]", "invalid capitalized key"],
+ ["00:00[u-CA=iso8601]", "invalid partially-capitalized key, time-only"],
+ ["T00:00[u-CA=iso8601]", "invalid partially-capitalized key, time designator"],
+ ["1970-01-01T00:00[u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["00:00[FOO=bar]", "invalid capitalized unrecognized key, time-only"],
+ ["T00:00[FOO=bar]", "invalid capitalized unrecognized key, time designator"],
+ ["1970-01-01T00:00[FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const timeZone = new Temporal.TimeZone("UTC");
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.withPlainTime(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js
new file mode 100644
index 0000000000..0bf0721c54
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.withplaintime
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants can be at most 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+let calls = 0;
+
+class Shift24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 0n
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ calls++;
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12 })];
+ }
+}
+
+const timeZone = new Shift24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+instance.withPlainTime();
+
+assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..8f2615faae
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js
@@ -0,0 +1,44 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.withplaintime
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 0n
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+assert.throws(RangeError, () => instance.withPlainTime(), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-maximum-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-maximum-backward-offset-shift.js
new file mode 100644
index 0000000000..21dcd8b3ba
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-maximum-backward-offset-shift.js
@@ -0,0 +1,53 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.withplaintime
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+let calls = 0;
+
+class Shift24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ calls++;
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12 }),
+ utcInstant.add({ hours: 12 })
+ ];
+ }
+}
+
+const timeZone = new Shift24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+instance.withPlainTime();
+
+assert(calls >= 1, "getPossibleInstantsFor should be called at least once");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..f2856f0aea
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.withplaintime
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+assert.throws(RangeError, () => instance.withPlainTime(), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/custom-calendar-weekofyear.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/custom-calendar-weekofyear.js
new file mode 100644
index 0000000000..331002f262
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/custom-calendar-weekofyear.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.yearofweek
+description: >
+ Temporal.ZonedDateTime.prototype.yearOfWeek returns undefined for all
+ custom calendars where yearOfWeek() returns undefined.
+features: [Temporal]
+---*/
+
+class CustomCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ yearOfWeek() {
+ return undefined;
+ }
+}
+
+const calendar = new CustomCalendar();
+// Epoch Nanoseconds for new Temporal.PlainDateTime(2024, 1, 1, 12, 34, 56, 987, 654, 321, calendar);
+const customCalendarDate = new Temporal.ZonedDateTime(1_704_112_496_987_654_321n, "UTC", calendar);
+assert.sameValue(customCalendarDate.yearOfWeek, undefined);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/validate-calendar-value.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/validate-calendar-value.js
index 9bd447f9a1..9eb067c70b 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/validate-calendar-value.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/validate-calendar-value.js
@@ -9,7 +9,6 @@ features: [Temporal]
---*/
const badResults = [
- [undefined, TypeError],
[Infinity, RangeError],
[-Infinity, RangeError],
[Symbol("foo"), TypeError],
diff --git a/js/src/tests/test262/built-ins/Temporal/shell.js b/js/src/tests/test262/built-ins/Temporal/shell.js
index 60f74c2518..5265d86aa2 100644
--- a/js/src/tests/test262/built-ins/Temporal/shell.js
+++ b/js/src/tests/test262/built-ins/Temporal/shell.js
@@ -2022,6 +2022,9 @@ var TemporalHelpers = {
"11-18junk",
"11-18[u-ca=gregory]",
"11-18[u-ca=hebrew]",
+ "11-18[U-CA=iso8601]",
+ "11-18[u-CA=iso8601]",
+ "11-18[FOO=bar]",
];
},
@@ -2109,6 +2112,11 @@ var TemporalHelpers = {
plainYearMonthStringsInvalid() {
return [
"2020-13",
+ "1976-11[u-ca=gregory]",
+ "1976-11[u-ca=hebrew]",
+ "1976-11[U-CA=iso8601]",
+ "1976-11[u-CA=iso8601]",
+ "1976-11[FOO=bar]",
];
},