From c731fd4709aadee645d42aae34a0e4d20e5b0a52 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Tue, 20 Jun 2023 13:11:20 +0200 Subject: [PATCH] Temporal: Prevent arbitrary loops in NormalizedTimeDurationToDays Adapts the tests that checked arbitrarily long loops, to now check that an exception is thrown if the loop would happen. Adds tests that exercise the newly added checks on return values of getPossibleInstantsFor and getOffsetNanosecondsFor that limit UTC offset shifts to 24 hours or less. Also updates some step numbers in related tests. --- ...ybag-out-of-range-backward-offset-shift.js | 48 ++++++++++ ...tybag-out-of-range-forward-offset-shift.js | 43 +++++++++ ...-time-duration-to-days-loop-arbitrarily.js | 87 ++++++------------ ...ybag-out-of-range-backward-offset-shift.js | 47 ++++++++++ ...tybag-out-of-range-forward-offset-shift.js | 42 +++++++++ ...ized-time-duration-to-days-range-errors.js | 20 ++--- ...-time-duration-to-days-loop-arbitrarily.js | 88 ++++++------------- ...ybag-out-of-range-backward-offset-shift.js | 47 ++++++++++ ...tybag-out-of-range-forward-offset-shift.js | 42 +++++++++ ...ized-time-duration-to-days-range-errors.js | 20 ++--- ...-time-duration-to-days-loop-arbitrarily.js | 87 ++++++------------ ...ybag-out-of-range-backward-offset-shift.js | 47 ++++++++++ ...tybag-out-of-range-forward-offset-shift.js | 42 +++++++++ ...ized-time-duration-to-days-range-errors.js | 20 ++--- ...-time-duration-to-days-loop-arbitrarily.js | 86 ++++++------------ .../precision-exact-mathematical-values-3.js | 2 +- ...ybag-out-of-range-backward-offset-shift.js | 47 ++++++++++ ...tybag-out-of-range-forward-offset-shift.js | 42 +++++++++ ...ized-time-duration-to-days-range-errors.js | 20 ++--- ...secondsfor-maximum-forward-offset-shift.js | 46 ++++++++++ ...dsfor-out-of-range-forward-offset-shift.js | 41 +++++++++ ...stantsfor-maximum-backward-offset-shift.js | 50 +++++++++++ ...sfor-out-of-range-backward-offset-shift.js | 46 ++++++++++ ...secondsfor-maximum-forward-offset-shift.js | 46 ++++++++++ ...dsfor-out-of-range-forward-offset-shift.js | 41 +++++++++ ...stantsfor-maximum-backward-offset-shift.js | 50 +++++++++++ ...sfor-out-of-range-backward-offset-shift.js | 46 ++++++++++ ...secondsfor-maximum-forward-offset-shift.js | 46 ++++++++++ ...dsfor-out-of-range-forward-offset-shift.js | 41 +++++++++ ...stantsfor-maximum-backward-offset-shift.js | 50 +++++++++++ ...sfor-out-of-range-backward-offset-shift.js | 46 ++++++++++ ...secondsfor-maximum-forward-offset-shift.js | 48 ++++++++++ ...dsfor-out-of-range-forward-offset-shift.js | 43 +++++++++ ...stantsfor-maximum-backward-offset-shift.js | 52 +++++++++++ ...sfor-out-of-range-backward-offset-shift.js | 47 ++++++++++ ...ybag-out-of-range-backward-offset-shift.js | 48 ++++++++++ ...tybag-out-of-range-forward-offset-shift.js | 43 +++++++++ ...ybag-out-of-range-backward-offset-shift.js | 46 ++++++++++ ...tybag-out-of-range-forward-offset-shift.js | 41 +++++++++ ...ybag-out-of-range-backward-offset-shift.js | 47 ++++++++++ ...tybag-out-of-range-forward-offset-shift.js | 42 +++++++++ ...secondsfor-maximum-forward-offset-shift.js | 46 ++++++++++ ...dsfor-out-of-range-forward-offset-shift.js | 41 +++++++++ ...stantsfor-maximum-backward-offset-shift.js | 50 +++++++++++ ...sfor-out-of-range-backward-offset-shift.js | 46 ++++++++++ ...secondsfor-maximum-forward-offset-shift.js | 46 ++++++++++ ...dsfor-out-of-range-forward-offset-shift.js | 41 +++++++++ ...stantsfor-maximum-backward-offset-shift.js | 50 +++++++++++ ...sfor-out-of-range-backward-offset-shift.js | 46 ++++++++++ ...ybag-out-of-range-backward-offset-shift.js | 47 ++++++++++ ...tybag-out-of-range-forward-offset-shift.js | 42 +++++++++ ...-time-duration-to-days-loop-arbitrarily.js | 84 ++++++------------ ...ized-time-duration-to-days-range-errors.js | 18 ++-- ...secondsfor-maximum-forward-offset-shift.js | 46 ++++++++++ ...dsfor-out-of-range-forward-offset-shift.js | 41 +++++++++ ...stantsfor-maximum-backward-offset-shift.js | 50 +++++++++++ ...sfor-out-of-range-backward-offset-shift.js | 46 ++++++++++ ...ybag-out-of-range-backward-offset-shift.js | 47 ++++++++++ ...tybag-out-of-range-forward-offset-shift.js | 42 +++++++++ ...-time-duration-to-days-loop-arbitrarily.js | 84 ++++++------------ ...ized-time-duration-to-days-range-errors.js | 18 ++-- ...secondsfor-maximum-forward-offset-shift.js | 49 +++++++++++ ...dsfor-out-of-range-forward-offset-shift.js | 43 +++++++++ ...stantsfor-maximum-backward-offset-shift.js | 53 +++++++++++ ...sfor-out-of-range-backward-offset-shift.js | 48 ++++++++++ ...secondsfor-maximum-forward-offset-shift.js | 46 ++++++++++ ...dsfor-out-of-range-forward-offset-shift.js | 41 +++++++++ ...stantsfor-maximum-backward-offset-shift.js | 50 +++++++++++ ...sfor-out-of-range-backward-offset-shift.js | 46 ++++++++++ ...secondsfor-maximum-forward-offset-shift.js | 46 ++++++++++ ...dsfor-out-of-range-forward-offset-shift.js | 41 +++++++++ ...stantsfor-maximum-backward-offset-shift.js | 50 +++++++++++ ...sfor-out-of-range-backward-offset-shift.js | 46 ++++++++++ 73 files changed, 2960 insertions(+), 413 deletions(-) create mode 100644 test/built-ins/Temporal/Duration/compare/relativeto-propertybag-out-of-range-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/Duration/compare/relativeto-propertybag-out-of-range-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/TimeZone/prototype/getInstantFor/getoffsetnanosecondsfor-maximum-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/TimeZone/prototype/getInstantFor/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/TimeZone/prototype/getInstantFor/getpossibleinstantsfor-maximum-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/TimeZone/prototype/getInstantFor/getpossibleinstantsfor-out-of-range-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-maximum-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/round/getoffsetnanosecondsfor-maximum-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/round/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-maximum-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-out-of-range-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-maximum-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-maximum-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-maximum-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-out-of-range-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-maximum-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-maximum-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-out-of-range-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-maximum-backward-offset-shift.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js diff --git a/test/built-ins/Temporal/Duration/compare/relativeto-propertybag-out-of-range-backward-offset-shift.js b/test/built-ins/Temporal/Duration/compare/relativeto-propertybag-out-of-range-backward-offset-shift.js new file mode 100644 index 00000000000..90639e19d9a --- /dev/null +++ b/test/built-ins/Temporal/Duration/compare/relativeto-propertybag-out-of-range-backward-offset-shift.js @@ -0,0 +1,48 @@ +// 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"); diff --git a/test/built-ins/Temporal/Duration/compare/relativeto-propertybag-out-of-range-forward-offset-shift.js b/test/built-ins/Temporal/Duration/compare/relativeto-propertybag-out-of-range-forward-offset-shift.js new file mode 100644 index 00000000000..a67d4936a36 --- /dev/null +++ b/test/built-ins/Temporal/Duration/compare/relativeto-propertybag-out-of-range-forward-offset-shift.js @@ -0,0 +1,43 @@ +// 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"); diff --git a/test/built-ins/Temporal/Duration/prototype/add/normalized-time-duration-to-days-loop-arbitrarily.js b/test/built-ins/Temporal/Duration/prototype/add/normalized-time-duration-to-days-loop-arbitrarily.js index 328e79a8d3b..b661510e8f8 100644 --- a/test/built-ins/Temporal/Duration/prototype/add/normalized-time-duration-to-days-loop-arbitrarily.js +++ b/test/built-ins/Temporal/Duration/prototype/add/normalized-time-duration-to-days-loop-arbitrarily.js @@ -4,72 +4,43 @@ /*--- esid: sec-temporal.duration.prototype.add 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.add(duration, { - relativeTo: zdt, -}); -assert.sameValue( - calls.length, - 50 + 1, - "Expected duration.add 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.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"); +assert.throws(RangeError, () => duration.add(duration, { relativeTo }), "arbitrarily long loop is prevented"); +assert.sameValue(calls, 5, "getPossibleInstantsFor is not called in an arbitrarily long loop"); + // Expected calls: + // AddDuration -> + // AddZonedDateTime (1) + // AddZonedDateTime (2) + // DifferenceZonedDateTime -> + // NormalizedTimeDurationToDays -> + // AddDaysToZonedDateTime (3, step 12) + // AddDaysToZonedDateTime (4, step 15) + // AddDaysToZonedDateTime (5, step 18.d) diff --git a/test/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-backward-offset-shift.js b/test/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-backward-offset-shift.js new file mode 100644 index 00000000000..eabbfc9966f --- /dev/null +++ b/test/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-backward-offset-shift.js @@ -0,0 +1,47 @@ +// 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"); diff --git a/test/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-forward-offset-shift.js b/test/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-forward-offset-shift.js new file mode 100644 index 00000000000..5a01e8b4f46 --- /dev/null +++ b/test/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-forward-offset-shift.js @@ -0,0 +1,42 @@ +// 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"); diff --git a/test/built-ins/Temporal/Duration/prototype/add/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js b/test/built-ins/Temporal/Duration/prototype/add/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js index 4f9f2f7e3c2..85901d2f0e4 100644 --- a/test/built-ins/Temporal/Duration/prototype/add/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js +++ b/test/built-ins/Temporal/Duration/prototype/add/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js @@ -7,12 +7,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] ---*/ @@ -39,7 +39,7 @@ function timeZoneSubstituteValues( return tz; } -// Step 22: days < 0 and sign = 1 +// Step 23: days < 0 and sign = 1 let zdt = new Temporal.ZonedDateTime( -1n, // Set DifferenceZonedDateTime _ns1_ timeZoneSubstituteValues( @@ -66,7 +66,7 @@ assert.throws(RangeError, () => "days < 0 and sign = 1" ); -// Step 23: days > 0 and sign = -1 +// Step 24: days > 0 and sign = -1 zdt = new Temporal.ZonedDateTime( 1n, // Set DifferenceZonedDateTime _ns1_ timeZoneSubstituteValues( @@ -93,7 +93,7 @@ assert.throws(RangeError, () => "days > 0 and sign = -1" ); -// Step 25: nanoseconds > 0 and sign = -1 +// Step 26: nanoseconds > 0 and sign = -1 zdt = new Temporal.ZonedDateTime( 0n, // Set DifferenceZonedDateTime _ns1_ timeZoneSubstituteValues( @@ -101,7 +101,7 @@ zdt = new Temporal.ZonedDateTime( 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_ + [new Temporal.Instant(-4n)], // Returned in step 19, setting _oneDayFarther_ ], [ // Behave normally in 3 calls made prior to NanosecondsToDays @@ -121,7 +121,7 @@ assert.throws(RangeError, () => "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( @@ -129,7 +129,7 @@ zdt = new Temporal.ZonedDateTime( 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_ + // Returned in step 19, making _oneDayFarther_ 2^53 ns later than _relativeResult_ [new Temporal.Instant(2n ** 53n + 2n * BigInt(dayNs))], ], [] diff --git a/test/built-ins/Temporal/Duration/prototype/round/normalized-time-duration-to-days-loop-arbitrarily.js b/test/built-ins/Temporal/Duration/prototype/round/normalized-time-duration-to-days-loop-arbitrarily.js index aff6d133f9e..c07cd587f72 100644 --- a/test/built-ins/Temporal/Duration/prototype/round/normalized-time-duration-to-days-loop-arbitrarily.js +++ b/test/built-ins/Temporal/Duration/prototype/round/normalized-time-duration-to-days-loop-arbitrarily.js @@ -3,74 +3,42 @@ /*--- 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) diff --git a/test/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-backward-offset-shift.js b/test/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-backward-offset-shift.js new file mode 100644 index 00000000000..d6ab9ddbb80 --- /dev/null +++ b/test/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-backward-offset-shift.js @@ -0,0 +1,47 @@ +// 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"); diff --git a/test/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-forward-offset-shift.js b/test/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-forward-offset-shift.js new file mode 100644 index 00000000000..4acceb03ae2 --- /dev/null +++ b/test/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-forward-offset-shift.js @@ -0,0 +1,42 @@ +// 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"); diff --git a/test/built-ins/Temporal/Duration/prototype/round/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js b/test/built-ins/Temporal/Duration/prototype/round/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js index 42b1912f513..32552a2608f 100644 --- a/test/built-ins/Temporal/Duration/prototype/round/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js +++ b/test/built-ins/Temporal/Duration/prototype/round/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js @@ -7,12 +7,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,7 +40,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( @@ -61,7 +61,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( @@ -82,13 +82,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 @@ -106,12 +106,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/test/built-ins/Temporal/Duration/prototype/subtract/normalized-time-duration-to-days-loop-arbitrarily.js b/test/built-ins/Temporal/Duration/prototype/subtract/normalized-time-duration-to-days-loop-arbitrarily.js index a4277e0f1c2..4f207894d62 100644 --- a/test/built-ins/Temporal/Duration/prototype/subtract/normalized-time-duration-to-days-loop-arbitrarily.js +++ b/test/built-ins/Temporal/Duration/prototype/subtract/normalized-time-duration-to-days-loop-arbitrarily.js @@ -4,72 +4,43 @@ /*--- esid: sec-temporal.duration.prototype.subtract 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.subtract(duration, { - relativeTo: zdt, -}); -assert.sameValue( - calls.length, - 50 + 1, - "Expected duration.subtract 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.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"); +assert.throws(RangeError, () => duration.subtract(duration, { relativeTo }), "indefinite loop is prevented"); +assert.sameValue(calls, 5, "getPossibleInstantsFor is not called indefinitely"); + // Expected calls: + // AddDuration -> + // AddZonedDateTime (1) + // AddZonedDateTime (2) + // DifferenceZonedDateTime -> + // NormalizedTimeDurationToDays -> + // AddDaysToZonedDateTime (3, step 12) + // AddDaysToZonedDateTime (4, step 15) + // AddDaysToZonedDateTime (5, step 18.d) diff --git a/test/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-backward-offset-shift.js b/test/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-backward-offset-shift.js new file mode 100644 index 00000000000..70ad89002e4 --- /dev/null +++ b/test/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-backward-offset-shift.js @@ -0,0 +1,47 @@ +// 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"); diff --git a/test/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-forward-offset-shift.js b/test/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-forward-offset-shift.js new file mode 100644 index 00000000000..3fb7c91a9cd --- /dev/null +++ b/test/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-forward-offset-shift.js @@ -0,0 +1,42 @@ +// 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"); diff --git a/test/built-ins/Temporal/Duration/prototype/subtract/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js b/test/built-ins/Temporal/Duration/prototype/subtract/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js index 874d569848d..4ba27ff5f23 100644 --- a/test/built-ins/Temporal/Duration/prototype/subtract/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js +++ b/test/built-ins/Temporal/Duration/prototype/subtract/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js @@ -7,12 +7,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] ---*/ @@ -39,7 +39,7 @@ function timeZoneSubstituteValues( return tz; } -// Step 22: days < 0 and sign = 1 +// Step 23: days < 0 and sign = 1 let zdt = new Temporal.ZonedDateTime( -1n, // Set DifferenceZonedDateTime _ns1_ timeZoneSubstituteValues( @@ -65,7 +65,7 @@ assert.throws(RangeError, () => }) ); -// Step 23: days > 0 and sign = -1 +// Step 24: days > 0 and sign = -1 zdt = new Temporal.ZonedDateTime( 1n, // Set DifferenceZonedDateTime _ns1_ timeZoneSubstituteValues( @@ -91,7 +91,7 @@ assert.throws(RangeError, () => }) ); -// Step 25: nanoseconds > 0 and sign = -1 +// Step 26: nanoseconds > 0 and sign = -1 zdt = new Temporal.ZonedDateTime( 0n, // Set DifferenceZonedDateTime _ns1_ timeZoneSubstituteValues( @@ -99,7 +99,7 @@ zdt = new Temporal.ZonedDateTime( 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_ + [new Temporal.Instant(-4n)], // Returned in step 19, setting _oneDayFarther_ ], [ // Behave normally in 3 calls made prior to NanosecondsToDays @@ -118,7 +118,7 @@ assert.throws(RangeError, () => }) ); -// Step 28: day length is an unsafe integer +// Step 29: day length is an unsafe integer zdt = new Temporal.ZonedDateTime( 0n, timeZoneSubstituteValues( @@ -126,7 +126,7 @@ zdt = new Temporal.ZonedDateTime( 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_ + // Returned in step 19, making _oneDayFarther_ 2^53 ns later than _relativeResult_ [new Temporal.Instant(2n ** 53n - 3n * BigInt(dayNs))], ], [] diff --git a/test/built-ins/Temporal/Duration/prototype/total/normalized-time-duration-to-days-loop-arbitrarily.js b/test/built-ins/Temporal/Duration/prototype/total/normalized-time-duration-to-days-loop-arbitrarily.js index 4abac8944ed..4e61345b596 100644 --- a/test/built-ins/Temporal/Duration/prototype/total/normalized-time-duration-to-days-loop-arbitrarily.js +++ b/test/built-ins/Temporal/Duration/prototype/total/normalized-time-duration-to-days-loop-arbitrarily.js @@ -4,74 +4,40 @@ /*--- 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) diff --git a/test/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-3.js b/test/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-3.js index 270f838c163..0e0bb8b9613 100644 --- a/test/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-3.js +++ b/test/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-3.js @@ -70,7 +70,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/test/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-backward-offset-shift.js b/test/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-backward-offset-shift.js new file mode 100644 index 00000000000..96b2d429f9d --- /dev/null +++ b/test/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-backward-offset-shift.js @@ -0,0 +1,47 @@ +// 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"); diff --git a/test/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-forward-offset-shift.js b/test/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-forward-offset-shift.js new file mode 100644 index 00000000000..79d1465cd1e --- /dev/null +++ b/test/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-forward-offset-shift.js @@ -0,0 +1,42 @@ +// 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"); diff --git a/test/built-ins/Temporal/Duration/prototype/total/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js b/test/built-ins/Temporal/Duration/prototype/total/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js index 8905552e369..20e696f3409 100644 --- a/test/built-ins/Temporal/Duration/prototype/total/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js +++ b/test/built-ins/Temporal/Duration/prototype/total/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js @@ -7,12 +7,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,7 +40,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( @@ -61,7 +61,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( @@ -82,13 +82,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 @@ -106,12 +106,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/test/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/test/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js new file mode 100644 index 00000000000..35e8d127932 --- /dev/null +++ b/test/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js @@ -0,0 +1,46 @@ +// 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"); diff --git a/test/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/test/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js new file mode 100644 index 00000000000..f8ec3994ed1 --- /dev/null +++ b/test/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js @@ -0,0 +1,41 @@ +// 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"); diff --git a/test/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js b/test/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js new file mode 100644 index 00000000000..e2c15ec9b9c --- /dev/null +++ b/test/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js @@ -0,0 +1,50 @@ +// 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"); diff --git a/test/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/test/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js new file mode 100644 index 00000000000..eb2e3851c4d --- /dev/null +++ b/test/built-ins/Temporal/PlainDate/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js @@ -0,0 +1,46 @@ +// 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"); diff --git a/test/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/test/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js new file mode 100644 index 00000000000..53556b5f629 --- /dev/null +++ b/test/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js @@ -0,0 +1,46 @@ +// 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"); diff --git a/test/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/test/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js new file mode 100644 index 00000000000..bb1925071a0 --- /dev/null +++ b/test/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js @@ -0,0 +1,41 @@ +// 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"); diff --git a/test/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js b/test/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js new file mode 100644 index 00000000000..e931969de51 --- /dev/null +++ b/test/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js @@ -0,0 +1,50 @@ +// 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"); diff --git a/test/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/test/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js new file mode 100644 index 00000000000..0c0409494ea --- /dev/null +++ b/test/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js @@ -0,0 +1,46 @@ +// 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"); diff --git a/test/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/test/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js new file mode 100644 index 00000000000..1c26db2f2f4 --- /dev/null +++ b/test/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js @@ -0,0 +1,46 @@ +// 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"); diff --git a/test/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/test/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js new file mode 100644 index 00000000000..37516d16635 --- /dev/null +++ b/test/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js @@ -0,0 +1,41 @@ +// 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"); diff --git a/test/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js b/test/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js new file mode 100644 index 00000000000..2a6b9ee08d7 --- /dev/null +++ b/test/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getpossibleinstantsfor-maximum-backward-offset-shift.js @@ -0,0 +1,50 @@ +// 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"); diff --git a/test/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/test/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js new file mode 100644 index 00000000000..cc5d7dd6540 --- /dev/null +++ b/test/built-ins/Temporal/PlainTime/prototype/toZonedDateTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js @@ -0,0 +1,46 @@ +// 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"); diff --git a/test/built-ins/Temporal/TimeZone/prototype/getInstantFor/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/test/built-ins/Temporal/TimeZone/prototype/getInstantFor/getoffsetnanosecondsfor-maximum-forward-offset-shift.js new file mode 100644 index 00000000000..2aa879b2b50 --- /dev/null +++ b/test/built-ins/Temporal/TimeZone/prototype/getInstantFor/getoffsetnanosecondsfor-maximum-forward-offset-shift.js @@ -0,0 +1,48 @@ +// 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; +} diff --git a/test/built-ins/Temporal/TimeZone/prototype/getInstantFor/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/test/built-ins/Temporal/TimeZone/prototype/getInstantFor/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js new file mode 100644 index 00000000000..2b8308c2c0d --- /dev/null +++ b/test/built-ins/Temporal/TimeZone/prototype/getInstantFor/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js @@ -0,0 +1,43 @@ +// 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"); +} + diff --git a/test/built-ins/Temporal/TimeZone/prototype/getInstantFor/getpossibleinstantsfor-maximum-backward-offset-shift.js b/test/built-ins/Temporal/TimeZone/prototype/getInstantFor/getpossibleinstantsfor-maximum-backward-offset-shift.js new file mode 100644 index 00000000000..dd3b08227dc --- /dev/null +++ b/test/built-ins/Temporal/TimeZone/prototype/getInstantFor/getpossibleinstantsfor-maximum-backward-offset-shift.js @@ -0,0 +1,52 @@ +// 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; +} diff --git a/test/built-ins/Temporal/TimeZone/prototype/getInstantFor/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/test/built-ins/Temporal/TimeZone/prototype/getInstantFor/getpossibleinstantsfor-out-of-range-backward-offset-shift.js new file mode 100644 index 00000000000..ba6fb520be1 --- /dev/null +++ b/test/built-ins/Temporal/TimeZone/prototype/getInstantFor/getpossibleinstantsfor-out-of-range-backward-offset-shift.js @@ -0,0 +1,47 @@ +// 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"); +} diff --git a/test/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-backward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-backward-offset-shift.js new file mode 100644 index 00000000000..58148c6f7f2 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-backward-offset-shift.js @@ -0,0 +1,48 @@ +// 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)"); diff --git a/test/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-forward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-forward-offset-shift.js new file mode 100644 index 00000000000..69de9ee843a --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-out-of-range-forward-offset-shift.js @@ -0,0 +1,43 @@ +// 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)"); diff --git a/test/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-backward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-backward-offset-shift.js new file mode 100644 index 00000000000..a2acd7be7bb --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-backward-offset-shift.js @@ -0,0 +1,46 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-forward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-forward-offset-shift.js new file mode 100644 index 00000000000..107d0f62d26 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/from/argument-propertybag-out-of-range-forward-offset-shift.js @@ -0,0 +1,41 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-backward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-backward-offset-shift.js new file mode 100644 index 00000000000..64e0756027d --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-backward-offset-shift.js @@ -0,0 +1,47 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-forward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-forward-offset-shift.js new file mode 100644 index 00000000000..6da3547568b --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-out-of-range-forward-offset-shift.js @@ -0,0 +1,42 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js new file mode 100644 index 00000000000..6cab9948529 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js @@ -0,0 +1,46 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js new file mode 100644 index 00000000000..a09c761ba4c --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js @@ -0,0 +1,41 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-maximum-backward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-maximum-backward-offset-shift.js new file mode 100644 index 00000000000..3b72e208df0 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-maximum-backward-offset-shift.js @@ -0,0 +1,50 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js new file mode 100644 index 00000000000..f0e539eaf3c --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js @@ -0,0 +1,46 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/round/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/round/getoffsetnanosecondsfor-maximum-forward-offset-shift.js new file mode 100644 index 00000000000..71083e6456d --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/round/getoffsetnanosecondsfor-maximum-forward-offset-shift.js @@ -0,0 +1,46 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/round/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/round/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js new file mode 100644 index 00000000000..9ef300c3a31 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/round/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js @@ -0,0 +1,41 @@ +// 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 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.round({ smallestUnit: "hours" }), "RangeError should be thrown"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-maximum-backward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-maximum-backward-offset-shift.js new file mode 100644 index 00000000000..1e554269a56 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-maximum-backward-offset-shift.js @@ -0,0 +1,50 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-out-of-range-backward-offset-shift.js new file mode 100644 index 00000000000..9edd6c4fdd0 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-out-of-range-backward-offset-shift.js @@ -0,0 +1,46 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-backward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-backward-offset-shift.js new file mode 100644 index 00000000000..25b2533adb3 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-backward-offset-shift.js @@ -0,0 +1,47 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-forward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-forward-offset-shift.js new file mode 100644 index 00000000000..1ccc59c6c9b --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-forward-offset-shift.js @@ -0,0 +1,42 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js b/test/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js index a611711b585..5328a887e85 100644 --- a/test/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js @@ -4,72 +4,38 @@ /*--- 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); -} +}("UTC"); -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" -); - -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, "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" }), "indefinite loop is prevented"); +assert.sameValue(calls, 3, "getPossibleInstantsFor is not called indefinitely"); + // Expected calls: + // DifferenceZonedDateTime -> NormalizedTimeDurationToDays -> + // AddDaysToZonedDateTime (3, step 12) + // AddDaysToZonedDateTime (4, step 15) + // AddDaysToZonedDateTime (5, step 18.d) diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js b/test/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js index 5c7bd9df031..99025931b31 100644 --- a/test/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js @@ -7,12 +7,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 @@ const oneZDT = new Temporal.ZonedDateTime(1n, "UTC"); const epochInstant = new Temporal.Instant(0n); const options = { largestUnit: "days" }; -// Step 22: days < 0 and sign = 1 +// Step 23: days < 0 and sign = 1 let start = new Temporal.ZonedDateTime( 0n, // Sets DifferenceZonedDateTime _ns1_ timeZoneSubstituteValues( @@ -62,7 +62,7 @@ assert.throws(RangeError, () => ) ); -// Step 23: days > 0 and sign = -1 +// Step 24: days > 0 and sign = -1 start = new Temporal.ZonedDateTime( 1n, // Sets DifferenceZonedDateTime _ns1_ timeZoneSubstituteValues( @@ -83,7 +83,7 @@ assert.throws(RangeError, () => ) ); -// Step 25: nanoseconds > 0 and sign = -1 +// Step 26: nanoseconds > 0 and sign = -1 start = new Temporal.ZonedDateTime( 1n, // Sets DifferenceZonedDateTime _ns1_ timeZoneSubstituteValues( @@ -104,12 +104,12 @@ assert.throws(RangeError, () => ) ); -// 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_ + // Returned in step 19, making _oneDayFarther_ 2^53 ns later than _relativeResult_ [[new Temporal.Instant(2n ** 53n)]], [] ) diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js new file mode 100644 index 00000000000..b0989c59e2e --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-maximum-forward-offset-shift.js @@ -0,0 +1,46 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js new file mode 100644 index 00000000000..dc8f94a11a6 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js @@ -0,0 +1,41 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-maximum-backward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-maximum-backward-offset-shift.js new file mode 100644 index 00000000000..b9d10d40bf0 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-maximum-backward-offset-shift.js @@ -0,0 +1,50 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js new file mode 100644 index 00000000000..70be6c2b0f7 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/startOfDay/getpossibleinstantsfor-out-of-range-backward-offset-shift.js @@ -0,0 +1,46 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-backward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-backward-offset-shift.js new file mode 100644 index 00000000000..c923075d16f --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-backward-offset-shift.js @@ -0,0 +1,47 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-forward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-forward-offset-shift.js new file mode 100644 index 00000000000..a6e382f7b46 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-out-of-range-forward-offset-shift.js @@ -0,0 +1,42 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-loop-arbitrarily.js b/test/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-loop-arbitrarily.js index c2e2fa6d861..004ce0838b9 100644 --- a/test/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-loop-arbitrarily.js +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-loop-arbitrarily.js @@ -4,72 +4,38 @@ /*--- 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); -} +}("UTC"); -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" -); - -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, "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" }), "indefinite loop is prevented"); +assert.sameValue(calls, 3, "getPossibleInstantsFor is not called indefinitely"); + // Expected calls: + // DifferenceZonedDateTime -> NormalizedTimeDurationToDays -> + // AddDaysToZonedDateTime (3, step 12) + // AddDaysToZonedDateTime (4, step 15) + // AddDaysToZonedDateTime (5, step 18.d) diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-range-errors.js b/test/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-range-errors.js index 6002e788b66..307559877b3 100644 --- a/test/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-range-errors.js +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-range-errors.js @@ -7,12 +7,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 @@ const oneZDT = new Temporal.ZonedDateTime(1n, "UTC"); const epochInstant = new Temporal.Instant(0n); const options = { largestUnit: "days" }; -// Step 22: days < 0 and sign = 1 +// Step 23: days < 0 and sign = 1 let start = new Temporal.ZonedDateTime( 0n, // Sets DifferenceZonedDateTime _ns1_ timeZoneSubstituteValues( @@ -62,7 +62,7 @@ assert.throws(RangeError, () => ) ); -// Step 23: days > 0 and sign = -1 +// Step 24: days > 0 and sign = -1 start = new Temporal.ZonedDateTime( 1n, // Sets DifferenceZonedDateTime _ns1_ timeZoneSubstituteValues( @@ -83,7 +83,7 @@ assert.throws(RangeError, () => ) ); -// Step 25: nanoseconds > 0 and sign = -1 +// Step 26: nanoseconds > 0 and sign = -1 start = new Temporal.ZonedDateTime( 1n, // Sets DifferenceZonedDateTime _ns1_ timeZoneSubstituteValues( @@ -104,12 +104,12 @@ assert.throws(RangeError, () => ) ); -// 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_ + // Returned in step 19, making _oneDayFarther_ 2^53 ns later than _relativeResult_ [[new Temporal.Instant(2n ** 53n)]], [] ) diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-maximum-forward-offset-shift.js new file mode 100644 index 00000000000..ba691f7bd37 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-maximum-forward-offset-shift.js @@ -0,0 +1,49 @@ +// 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; +} diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js new file mode 100644 index 00000000000..0ed37f0829b --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/with/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js @@ -0,0 +1,43 @@ +// 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"); +} diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-maximum-backward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-maximum-backward-offset-shift.js new file mode 100644 index 00000000000..8f3e76d2271 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-maximum-backward-offset-shift.js @@ -0,0 +1,53 @@ +// 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; +} diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-out-of-range-backward-offset-shift.js new file mode 100644 index 00000000000..2c25a7deb17 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/with/getpossibleinstantsfor-out-of-range-backward-offset-shift.js @@ -0,0 +1,48 @@ +// 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"); +} diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-maximum-forward-offset-shift.js new file mode 100644 index 00000000000..e4f75e3775d --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-maximum-forward-offset-shift.js @@ -0,0 +1,46 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js new file mode 100644 index 00000000000..9c4f9c4922c --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js @@ -0,0 +1,41 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-maximum-backward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-maximum-backward-offset-shift.js new file mode 100644 index 00000000000..c0533154090 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-maximum-backward-offset-shift.js @@ -0,0 +1,50 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-out-of-range-backward-offset-shift.js new file mode 100644 index 00000000000..39fc1a30807 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/getpossibleinstantsfor-out-of-range-backward-offset-shift.js @@ -0,0 +1,46 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js new file mode 100644 index 00000000000..f4a3f2f0878 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-maximum-forward-offset-shift.js @@ -0,0 +1,46 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js new file mode 100644 index 00000000000..9ae6cfc12ed --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getoffsetnanosecondsfor-out-of-range-forward-offset-shift.js @@ -0,0 +1,41 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-maximum-backward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-maximum-backward-offset-shift.js new file mode 100644 index 00000000000..98879829f46 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-maximum-backward-offset-shift.js @@ -0,0 +1,50 @@ +// 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"); diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js b/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js new file mode 100644 index 00000000000..98f4d4cf680 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/getpossibleinstantsfor-out-of-range-backward-offset-shift.js @@ -0,0 +1,46 @@ +// 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");