summaryrefslogtreecommitdiffstats
path: root/js/src/tests/test262/built-ins/Temporal/Duration/prototype
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/tests/test262/built-ins/Temporal/Duration/prototype')
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/calendar-dateadd-called-with-options-undefined.js2
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/differencezoneddatetime-inconsistent-custom-calendar.js55
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/normalized-time-duration-to-days-loop-arbitrarily.js78
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/order-of-operations.js12
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-backward-offset-shift.js50
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-forward-offset-shift.js45
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js146
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/dst-rounding-result.js48
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/duration-out-of-range-added-to-relativeto.js45
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/normalized-time-duration-to-days-loop-arbitrarily.js88
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/out-of-range-when-adjusting-rounded-days.js32
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/out-of-range-when-converting-from-normalized-duration.js2
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-backward-offset-shift.js50
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-forward-offset-shift.js45
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js20
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/calendar-dateadd-called-with-options-undefined.js2
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/differencezoneddatetime-inconsistent-custom-calendar.js55
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/normalized-time-duration-to-days-loop-arbitrarily.js78
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/order-of-operations.js12
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-backward-offset-shift.js50
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-forward-offset-shift.js45
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js144
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/dst-rounding-result.js40
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/duration-out-of-range-added-to-relativeto.js45
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/normalized-time-duration-to-days-loop-arbitrarily.js86
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-3.js2
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-backward-offset-shift.js50
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-forward-offset-shift.js45
-rw-r--r--js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js20
29 files changed, 758 insertions, 634 deletions
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/calendar-dateadd-called-with-options-undefined.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/calendar-dateadd-called-with-options-undefined.js
index 4955abacdd..84079298ce 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/calendar-dateadd-called-with-options-undefined.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/calendar-dateadd-called-with-options-undefined.js
@@ -15,6 +15,6 @@ const calendar = TemporalHelpers.calendarDateAddUndefinedOptions();
const timeZone = TemporalHelpers.oneShiftTimeZone(new Temporal.Instant(0n), 3600e9);
const instance = new Temporal.Duration(1, 1, 1, 1);
instance.add(instance, { relativeTo: new Temporal.ZonedDateTime(0n, timeZone, calendar) });
-assert.sameValue(calendar.dateAddCallCount, 3);
+assert.sameValue(calendar.dateAddCallCount, 2);
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/differencezoneddatetime-inconsistent-custom-calendar.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/differencezoneddatetime-inconsistent-custom-calendar.js
new file mode 100644
index 0000000000..01bf6c756e
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/differencezoneddatetime-inconsistent-custom-calendar.js
@@ -0,0 +1,55 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.duration.prototype.add
+description: >
+ Throws a RangeError when custom calendar method returns inconsistent result
+info: |
+ DifferenceZonedDateTime ( ... )
+ 8. Repeat 3 times:
+ ...
+ g. If _sign_ = 0, or _timeSign_ = 0, or _sign_ = _timeSign_, then
+ ...
+ viii. Return ? CreateNormalizedDurationRecord(_dateDifference_.[[Years]],
+ _dateDifference_.[[Months]], _dateDifference_.[[Weeks]],
+ _dateDifference_.[[Days]], _norm_).
+ h. Set _dayCorrection_ to _dayCorrection_ + 1.
+ 9. NOTE: This step is only reached when custom calendar or time zone methods
+ return inconsistent values.
+ 10. Throw a *RangeError* exception.
+features: [Temporal]
+---*/
+
+// Based partly on a test case by André Bargull
+
+const duration1 = new Temporal.Duration(0, 0, /* weeks = */ 7, 0, /* hours = */ 12);
+const duration2 = new Temporal.Duration(0, 0, 0, /* days = */ 1);
+
+{
+ const tz = new (class extends Temporal.TimeZone {
+ getPossibleInstantsFor(dateTime) {
+ return super.getPossibleInstantsFor(dateTime.add({ days: 3 }));
+ }
+ })("UTC");
+
+ const relativeTo = new Temporal.ZonedDateTime(0n, tz);
+
+ assert.throws(RangeError, () => duration1.add(duration2, { relativeTo }),
+ "Calendar calculation where more than 2 days correction is needed should cause RangeError");
+}
+
+{
+ const cal = new (class extends Temporal.Calendar {
+ dateUntil(one, two, options) {
+ return super.dateUntil(one, two, options).negated();
+ }
+ })("iso8601");
+
+ const relativeTo = new Temporal.ZonedDateTime(0n, "UTC", cal);
+
+ assert.throws(RangeError, () => duration1.add(duration2, { relativeTo }),
+ "Calendar calculation causing mixed-sign values should cause RangeError");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/normalized-time-duration-to-days-loop-arbitrarily.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/normalized-time-duration-to-days-loop-arbitrarily.js
deleted file mode 100644
index 62833e87c6..0000000000
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/normalized-time-duration-to-days-loop-arbitrarily.js
+++ /dev/null
@@ -1,78 +0,0 @@
-// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
-// Copyright (C) 2022 Igalia, S.L. All rights reserved.
-// This code is governed by the BSD license found in the LICENSE file.
-
-/*---
-esid: sec-temporal.duration.prototype.add
-description: >
- NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer
-info: |
- NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
- ...
- 21. Repeat, while done is false,
- a. Let oneDayFarther be ? AddDaysToZonedDateTime(relativeResult.[[Instant]],
- relativeResult.[[DateTime]], timeZoneRec, zonedRelativeTo.[[Calendar]], sign).
- b. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(oneDayFarther.[[EpochNanoseconds]],
- relativeResult.[[EpochNanoseconds]]).
- c. Let oneDayLess be ? SubtractNormalizedTimeDuration(norm, dayLengthNs).
- c. If NormalizedTimeDurationSign(oneDayLess) × sign ≥ 0, then
- i. Set norm to oneDayLess.
- ii. Set relativeResult to oneDayFarther.
- iii. Set days to days + sign.
- d. Else,
- i. Set done to true.
-includes: [temporalHelpers.js]
-features: [Temporal]
----*/
-
-const calls = [];
-const duration = Temporal.Duration.from({ days: 1 });
-
-function createRelativeTo(count) {
- const dayLengthNs = 86400000000000n;
- const dayInstant = new Temporal.Instant(dayLengthNs);
- const substitutions = [];
- const timeZone = new Temporal.TimeZone("UTC");
- // Return constant value for first _count_ calls
- TemporalHelpers.substituteMethod(
- timeZone,
- "getPossibleInstantsFor",
- substitutions
- );
- substitutions.length = count;
- let i = 0;
- for (i = 0; i < substitutions.length; i++) {
- // (this value)
- substitutions[i] = [dayInstant];
- }
- // Record calls in calls[]
- TemporalHelpers.observeMethod(calls, timeZone, "getPossibleInstantsFor");
- return new Temporal.ZonedDateTime(0n, timeZone);
-}
-
-let zdt = createRelativeTo(50);
-calls.splice(0); // Reset calls list after ZonedDateTime construction
-duration.add(duration, {
- relativeTo: zdt,
-});
-assert.sameValue(
- calls.length,
- 50 + 1,
- "Expected duration.add to call getPossibleInstantsFor correct number of times"
-);
-
-zdt = createRelativeTo(100);
-calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
-duration.add(duration, {
- relativeTo: zdt,
-});
-assert.sameValue(
- calls.length,
- 100 + 1,
- "Expected duration.add to call getPossibleInstantsFor correct number of times"
-);
-
-zdt = createRelativeTo(107);
-assert.throws(RangeError, () => duration.add(duration, { relativeTo: zdt }), "107-2 days > 2⁵³ ns");
-
-reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/order-of-operations.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/order-of-operations.js
index a530ba55e9..c3a2f2c4a9 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/order-of-operations.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/order-of-operations.js
@@ -339,18 +339,8 @@ const expectedOpsForZonedRelativeTo = expected.concat([
"call options.relativeTo.timeZone.getPossibleInstantsFor",
// AddDuration → DifferenceZonedDateTime
"call options.relativeTo.timeZone.getOffsetNanosecondsFor",
- // AddDuration → DifferenceZonedDateTime → DifferenceISODateTime
- "call options.relativeTo.calendar.dateUntil",
- // AddDuration → DifferenceZonedDateTime → AddZonedDateTime
- "call options.relativeTo.calendar.dateAdd",
- "call options.relativeTo.timeZone.getPossibleInstantsFor",
- // AddDuration → DifferenceZonedDateTime → NanosecondsToDays
- "call options.relativeTo.timeZone.getOffsetNanosecondsFor",
- "call options.relativeTo.timeZone.getOffsetNanosecondsFor",
- // AddDuration → DifferenceZonedDateTime → NanosecondsToDays → AddZonedDateTime 1
- "call options.relativeTo.timeZone.getPossibleInstantsFor",
- // AddDuration → DifferenceZonedDateTime → NanosecondsToDays → AddZonedDateTime 2
"call options.relativeTo.timeZone.getPossibleInstantsFor",
+ "call options.relativeTo.calendar.dateUntil",
]);
const zonedRelativeTo = TemporalHelpers.propertyBagObserver(actual, {
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..a8506addba
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-backward-offset-shift.js
@@ -0,0 +1,50 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.add
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.Duration(1, 0, 0, 1);
+assert.throws(RangeError, () => instance.add(new Temporal.Duration(0, 0, 0, 0, -24), { relativeTo }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..705327b70e
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-out-of-range-forward-offset-shift.js
@@ -0,0 +1,45 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.add
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.Duration(1, 0, 0, 1);
+assert.throws(RangeError, () => instance.add(new Temporal.Duration(0, 0, 0, 0, -24), { relativeTo }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js
deleted file mode 100644
index 7acd5929b6..0000000000
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/add/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js
+++ /dev/null
@@ -1,146 +0,0 @@
-// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
-// Copyright (C) 2022 Igalia, S.L. All rights reserved.
-// This code is governed by the BSD license found in the LICENSE file.
-/*---
-esid: sec-temporal.duration.prototype.add
-description: >
- Abstract operation NormalizedTimeDurationToDays can throw four different
- RangeErrors.
-info: |
- NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
- 22. If days < 0 and sign = 1, throw a RangeError exception.
- 23. If days > 0 and sign = -1, throw a RangeError exception.
- ...
- 25. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception.
- ...
- 28. If dayLength ≥ 2⁵³, throw a RangeError exception.
-features: [Temporal, BigInt]
-includes: [temporalHelpers.js]
----*/
-
-const dayNs = 86_400_000_000_000;
-const dayDuration = Temporal.Duration.from({ days: 1 });
-const epochInstant = new Temporal.Instant(0n);
-
-function timeZoneSubstituteValues(
- getPossibleInstantsFor,
- getOffsetNanosecondsFor
-) {
- const tz = new Temporal.TimeZone("UTC");
- TemporalHelpers.substituteMethod(
- tz,
- "getPossibleInstantsFor",
- getPossibleInstantsFor
- );
- TemporalHelpers.substituteMethod(
- tz,
- "getOffsetNanosecondsFor",
- getOffsetNanosecondsFor
- );
- return tz;
-}
-
-// Step 22: days < 0 and sign = 1
-let zdt = new Temporal.ZonedDateTime(
- -1n, // Set DifferenceZonedDateTime _ns1_
- timeZoneSubstituteValues(
- [
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for first call, AddDuration step 15
- [epochInstant], // Returned in AddDuration step 16, setting _endNs_ -> DifferenceZonedDateTime _ns2_
- [epochInstant], // Returned in step 16, setting _relativeResult_
- ],
- [
- // Behave normally in 3 calls made prior to NormalizedTimeDurationToDays
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- dayNs - 1, // Returned in step 8, setting _startDateTime_
- -dayNs + 1, // Returned in step 9, setting _endDateTime_
- ]
- )
-);
-assert.throws(RangeError, () =>
- // Adding day to day sets largestUnit to 'day', avoids having any week/month/year components in differences
- dayDuration.add(dayDuration, {
- relativeTo: zdt,
- }),
- "days < 0 and sign = 1"
-);
-
-// Step 23: days > 0 and sign = -1
-zdt = new Temporal.ZonedDateTime(
- 1n, // Set DifferenceZonedDateTime _ns1_
- timeZoneSubstituteValues(
- [
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for first call, AddDuration step 15
- [epochInstant], // Returned in AddDuration step 16, setting _endNs_ -> DifferenceZonedDateTime _ns2_
- [epochInstant], // Returned in step 16, setting _relativeResult_
- ],
- [
- // Behave normally in 3 calls made prior to NanosecondsToDays
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- -dayNs + 1, // Returned in step 8, setting _startDateTime_
- dayNs - 1, // Returned in step 9, setting _endDateTime_
- ]
- )
-);
-assert.throws(RangeError, () =>
- // Adding day to day sets largestUnit to 'day', avoids having any week/month/year components in differences
- dayDuration.add(dayDuration, {
- relativeTo: zdt,
- }),
- "days > 0 and sign = -1"
-);
-
-// Step 25: nanoseconds > 0 and sign = -1
-zdt = new Temporal.ZonedDateTime(
- 0n, // Set DifferenceZonedDateTime _ns1_
- timeZoneSubstituteValues(
- [
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for first call, AddDuration step 15
- [new Temporal.Instant(-1n)], // Returned in AddDuration step 16, setting _endNs_ -> DifferenceZonedDateTime _ns2_
- [new Temporal.Instant(-2n)], // Returned in step 16, setting _relativeResult_
- [new Temporal.Instant(-4n)], // Returned in step 21.a, setting _oneDayFarther_
- ],
- [
- // Behave normally in 3 calls made prior to NanosecondsToDays
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- dayNs - 1, // Returned in step 8, setting _startDateTime_
- -dayNs + 1, // Returned in step 9, setting _endDateTime_
- ]
- )
-);
-assert.throws(RangeError, () =>
- // Adding day to day sets largestUnit to 'day', avoids having any week/month/year components in differences
- dayDuration.add(dayDuration, {
- relativeTo: zdt,
- }),
- "nanoseconds > 0 and sign = -1"
-);
-
-// Step 28: day length is an unsafe integer
-zdt = new Temporal.ZonedDateTime(
- 0n,
- timeZoneSubstituteValues(
- [
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for AddDuration step 15
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for AddDuration step 16
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for step 16, setting _relativeResult_
- // Returned in step 21.a, making _oneDayFarther_ 2^53 ns later than _relativeResult_
- [new Temporal.Instant(2n ** 53n + 2n * BigInt(dayNs))],
- ],
- []
- )
-);
-assert.throws(RangeError, () =>
- dayDuration.add(dayDuration, {
- relativeTo: zdt,
- }),
- "Should throw RangeError when time zone calculates an outrageous day length"
-);
-
-reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/dst-rounding-result.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/dst-rounding-result.js
new file mode 100644
index 0000000000..daff4e93f6
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/dst-rounding-result.js
@@ -0,0 +1,48 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.duration.prototype.round
+description: >
+ Rounding the resulting duration takes the time zone's UTC offset shifts
+ into account
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const timeZone = TemporalHelpers.springForwardFallBackTimeZone();
+
+// Based on a test case by Adam Shaw
+
+{
+ // Date part of duration lands on skipped DST hour, causing disambiguation
+ const duration = new Temporal.Duration(0, 1, 0, 15, 12);
+ const relativeTo = new Temporal.ZonedDateTime(
+ 950868000_000_000_000n /* = 2000-02-18T10Z */,
+ timeZone); /* = 2000-02-18T02-08 in local time */
+
+ TemporalHelpers.assertDuration(duration.round({ smallestUnit: "months", relativeTo }),
+ 0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
+ "1 month 15 days 12 hours should be exactly 1.5 months, which rounds up to 2 months");
+ TemporalHelpers.assertDuration(duration.round({ smallestUnit: "months", roundingMode: 'halfTrunc', relativeTo }),
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ "1 month 15 days 12 hours should be exactly 1.5 months, which rounds down to 1 month");
+}
+
+{
+ // Month-only part of duration lands on skipped DST hour, should not cause
+ // disambiguation
+ const duration = new Temporal.Duration(0, 1, 0, 15);
+ const relativeTo = new Temporal.ZonedDateTime(
+ 951991200_000_000_000n /* = 2000-03-02T10Z */,
+ timeZone); /* = 2000-03-02T02-08 in local time */
+
+ TemporalHelpers.assertDuration(duration.round({ smallestUnit: "months", relativeTo }),
+ 0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
+ "1 month 15 days should be exactly 1.5 months, which rounds up to 2 months");
+ TemporalHelpers.assertDuration(duration.round({ smallestUnit: "months", roundingMode: 'halfTrunc', relativeTo }),
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ "1 month 15 days should be exactly 1.5 months, which rounds down to 1 month");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/duration-out-of-range-added-to-relativeto.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/duration-out-of-range-added-to-relativeto.js
index b564b27eb4..f63eb3b789 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/duration-out-of-range-added-to-relativeto.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/duration-out-of-range-added-to-relativeto.js
@@ -8,21 +8,44 @@ description: RangeError thrown when calendar part of duration added to relativeT
features: [Temporal]
info: |
RoundDuration:
- 8.k. Let _isoResult_ be ! AddISODate(_plainRelativeTo_.[[ISOYear]]. _plainRelativeTo_.[[ISOMonth]], _plainRelativeTo_.[[ISODay]], 0, 0, 0, truncate(_fractionalDays_), *"constrain"*).
- l. Let _wholeDaysLater_ be ? CreateTemporalDate(_isoResult_.[[Year]], _isoResult_.[[Month]], _isoResult_.[[Day]], _calendar_).
+ 10.h. Let _isoResult_ be ! AddISODate(_plainRelativeTo_.[[ISOYear]]. _plainRelativeTo_.[[ISOMonth]], _plainRelativeTo_.[[ISODay]], 0, 0, 0, truncate(_fractionalDays_), *"constrain"*).
+ i. Let _wholeDaysLater_ be ? CreateTemporalDate(_isoResult_.[[Year]], _isoResult_.[[Month]], _isoResult_.[[Day]], _calendar_).
+ ...
+ 11.h. Let _isoResult_ be ! AddISODate(_plainRelativeTo_.[[ISOYear]]. _plainRelativeTo_.[[ISOMonth]], _plainRelativeTo_.[[ISODay]], 0, 0, 0, truncate(_fractionalDays_), *"constrain"*).
+ i. Let _wholeDaysLater_ be ? CreateTemporalDate(_isoResult_.[[Year]], _isoResult_.[[Month]], _isoResult_.[[Day]], _calendar_).
+ ...
+ 12.a. Let _isoResult_ be ! AddISODate(_plainRelativeTo_.[[ISOYear]]. _plainRelativeTo_.[[ISOMonth]], _plainRelativeTo_.[[ISODay]], 0, 0, 0, truncate(_fractionalDays_), *"constrain"*).
+ b. Let _wholeDaysLater_ be ? CreateTemporalDate(_isoResult_.[[Year]], _isoResult_.[[Month]], _isoResult_.[[Day]], _calendar_).
+
+ UnbalanceDateDurationRelative:
+ 11. Let _yearsMonthsWeeksDuration_ be ! CreateTemporalDuration(_years_, _months_, _weeks_, 0, 0, 0, 0, 0, 0, 0).
+ 12. Let _later_ be ? CalendarDateAdd(_calendaRec_, _plainRelativeTo_, _yearsMonthsWeeksDuration_).
+ 13. Let _yearsMonthsWeeksInDays_ be DaysUntil(_plainRelativeTo_, _later_).
+ 14. Return ? CreateDateDurationRecord(0, 0, 0, _days_ + _yearsMonthsWeeksInDays_).
---*/
// Based on a test case by André Bargull <andre.bargull@gmail.com>
-const instance = new Temporal.Duration(0, 0, 0, /* days = */ 500_000_000);
const relativeTo = new Temporal.PlainDate(2000, 1, 1);
-assert.throws(RangeError, () => instance.round({relativeTo, smallestUnit: "years"}));
-assert.throws(RangeError, () => instance.round({relativeTo, smallestUnit: "months"}));
-assert.throws(RangeError, () => instance.round({relativeTo, smallestUnit: "weeks"}));
-
-const negInstance = new Temporal.Duration(0, 0, 0, /* days = */ -500_000_000);
-assert.throws(RangeError, () => negInstance.round({relativeTo, smallestUnit: "years"}));
-assert.throws(RangeError, () => negInstance.round({relativeTo, smallestUnit: "months"}));
-assert.throws(RangeError, () => negInstance.round({relativeTo, smallestUnit: "weeks"}));
+
+{
+ const instance = new Temporal.Duration(0, 0, 0, /* days = */ 500_000_000);
+ assert.throws(RangeError, () => instance.round({relativeTo, smallestUnit: "years"}), "days out of range, positive, smallestUnit years");
+ assert.throws(RangeError, () => instance.round({relativeTo, smallestUnit: "months"}), "days out of range, positive, smallestUnit months");
+ assert.throws(RangeError, () => instance.round({relativeTo, smallestUnit: "weeks"}), "days out of range, positive, smallestUnit weeks");
+
+ const negInstance = new Temporal.Duration(0, 0, 0, /* days = */ -500_000_000);
+ assert.throws(RangeError, () => negInstance.round({relativeTo, smallestUnit: "years"}), "days out of range, negative, smallestUnit years");
+ assert.throws(RangeError, () => negInstance.round({relativeTo, smallestUnit: "months"}), "days out of range, negative, smallestUnit months");
+ assert.throws(RangeError, () => negInstance.round({relativeTo, smallestUnit: "weeks"}), "days out of range, negative, smallestUnit weeks");
+}
+
+{
+ const instance = new Temporal.Duration(0, 0, /* weeks = */ 1, /* days = */ Math.trunc((2 ** 53) / 86_400));
+ assert.throws(RangeError, () => instance.round({relativeTo, largestUnit: "days"}), "weeks + days out of range, positive");
+
+ const negInstance = new Temporal.Duration(0, 0, /* weeks = */ -1, /* days = */ -Math.trunc((2 ** 53) / 86_400));
+ assert.throws(RangeError, () => instance.round({relativeTo, largestUnit: "days"}), "weeks + days out of range, negative");
+}
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/normalized-time-duration-to-days-loop-arbitrarily.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/normalized-time-duration-to-days-loop-arbitrarily.js
index 923d788246..ed63026c0a 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/normalized-time-duration-to-days-loop-arbitrarily.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/normalized-time-duration-to-days-loop-arbitrarily.js
@@ -4,76 +4,44 @@
/*---
esid: sec-temporal.duration.prototype.round
description: >
- NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer
+ NormalizedTimeDurationToDays should not be able to loop arbitrarily.
info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
...
- 21. Repeat, while done is false,
- a. Let oneDayFarther be ? AddDaysToZonedDateTime(relativeResult.[[Instant]],
- relativeResult.[[DateTime]], timeZoneRec, zonedRelativeTo.[[Calendar]], sign).
- b. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(oneDayFarther.[[EpochNanoseconds]],
- relativeResult.[[EpochNanoseconds]]).
- c. Let oneDayLess be ? SubtractNormalizedTimeDuration(norm, dayLengthNs).
- c. If NormalizedTimeDurationSign(oneDayLess) × sign ≥ 0, then
- i. Set norm to oneDayLess.
- ii. Set relativeResult to oneDayFarther.
- iii. Set days to days + sign.
- d. Else,
- i. Set done to true.
-includes: [temporalHelpers.js]
+ 22. If NormalizedTimeDurationSign(_oneDayLess_) × _sign_ ≥ 0, then
+ a. Set _norm_ to _oneDayLess_.
+ b. Set _relativeResult_ to _oneDayFarther_.
+ c. Set _days_ to _days_ + _sign_.
+ d. Set _oneDayFarther_ to ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_).
+ e. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther.[[EpochNanoseconds]], relativeResult.[[EpochNanoseconds]]).
+ f. If NormalizedTimeDurationSign(? SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_)) × _sign_ ≥ 0, then
+ i. Throw a *RangeError* exception.
features: [Temporal]
---*/
-const calls = [];
const duration = Temporal.Duration.from({ days: 1 });
-function createRelativeTo(count) {
- const dayLengthNs = 86400000000000n;
- const dayInstant = new Temporal.Instant(dayLengthNs);
- const substitutions = [];
- const timeZone = new Temporal.TimeZone("UTC");
- // Return constant value for first _count_ calls
- TemporalHelpers.substituteMethod(
- timeZone,
- "getPossibleInstantsFor",
- substitutions
- );
- substitutions.length = count;
- let i = 0;
- for (i = 0; i < substitutions.length; i++) {
- // (this value)
- substitutions[i] = [dayInstant];
+const dayLengthNs = 86400000000000n;
+const dayInstant = new Temporal.Instant(dayLengthNs);
+let calls = 0;
+const timeZone = new class extends Temporal.TimeZone {
+ getPossibleInstantsFor() {
+ calls++;
+ return [dayInstant];
}
- // Record calls in calls[]
- TemporalHelpers.observeMethod(calls, timeZone, "getPossibleInstantsFor");
- return new Temporal.ZonedDateTime(0n, timeZone);
-}
+}("UTC");
-let zdt = createRelativeTo(50);
-calls.splice(0); // Reset calls list after ZonedDateTime construction
-duration.round({
- smallestUnit: "days",
- relativeTo: zdt,
-});
-assert.sameValue(
- calls.length,
- 50 + 1,
- "Expected duration.round to call getPossibleInstantsFor correct number of times"
-);
+const relativeTo = new Temporal.ZonedDateTime(0n, timeZone);
-zdt = createRelativeTo(100);
-calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
-duration.round({
- smallestUnit: "days",
- relativeTo: zdt,
-});
-assert.sameValue(
- calls.length,
- 100 + 1,
- "Expected duration.round to call getPossibleInstantsFor correct number of times"
-);
-
-zdt = createRelativeTo(107);
-assert.throws(RangeError, () => duration.round({ smallestUnit: "days", relativeTo: zdt }), "107-2 days > 2⁵³ ns");
+assert.throws(RangeError, () => duration.round({ smallestUnit: "days", relativeTo }), "indefinite loop is prevented");
+assert.sameValue(calls, 5, "getPossibleInstantsFor is not called indefinitely");
+ // Expected calls:
+ // RoundDuration -> MoveRelativeZonedDateTime -> AddZonedDateTime (1)
+ // BalanceTimeDurationRelative ->
+ // AddZonedDateTime (2)
+ // NormalizedTimeDurationToDays ->
+ // AddDaysToZonedDateTime (3, step 12)
+ // AddDaysToZonedDateTime (4, step 15)
+ // AddDaysToZonedDateTime (5, step 18.d)
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/out-of-range-when-adjusting-rounded-days.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/out-of-range-when-adjusting-rounded-days.js
new file mode 100644
index 0000000000..cd6c307e5b
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/out-of-range-when-adjusting-rounded-days.js
@@ -0,0 +1,32 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.round
+description: >
+ When adjusting the rounded days after rounding relative to a ZonedDateTime,
+ the duration may go out of range
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+// Based on a test case by André Bargull
+
+const d = new Temporal.Duration(0, 0, 0, /* days = */ 1, 0, 0, /* s = */ Number.MAX_SAFE_INTEGER - 86400, 0, 0, /* ns = */ 999_999_999);
+
+const timeZone = new Temporal.TimeZone("UTC");
+TemporalHelpers.substituteMethod(timeZone, 'getPossibleInstantsFor', [
+ TemporalHelpers.SUBSTITUTE_SKIP,
+ [new Temporal.Instant(1n)],
+]);
+
+const relativeTo = new Temporal.ZonedDateTime(0n, timeZone);
+
+assert.throws(RangeError, () => d.round({
+ largestUnit: 'nanoseconds',
+ roundingIncrement: 2,
+ relativeTo
+}));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/out-of-range-when-converting-from-normalized-duration.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/out-of-range-when-converting-from-normalized-duration.js
index 4fde761351..d4f973d639 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/out-of-range-when-converting-from-normalized-duration.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/out-of-range-when-converting-from-normalized-duration.js
@@ -14,6 +14,6 @@ const d = new Temporal.Duration(0, 0, 0, 0, 0, 0, /* s = */ Number.MAX_SAFE_INTE
assert.throws(RangeError, () => d.round({
largestUnit: "nanoseconds",
roundingIncrement: 1,
-}), "nanoseconds component is an unsafe integer after balancing");
+}), "nanoseconds component after balancing as a float64-representable integer is out of range");
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..e147558077
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-backward-offset-shift.js
@@ -0,0 +1,50 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.round
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.Duration(1, 0, 0, 0, 24);
+assert.throws(RangeError, () => instance.round({ largestUnit: "years", relativeTo }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..f3b8d604ee
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-out-of-range-forward-offset-shift.js
@@ -0,0 +1,45 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.round
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.Duration(1, 0, 0, 0, 24);
+assert.throws(RangeError, () => instance.round({ largestUnit: "years", relativeTo }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js
index 150f9f419e..9c04b2991e 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/round/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js
@@ -8,12 +8,12 @@ description: >
RangeErrors.
info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
- 22. If days < 0 and sign = 1, throw a RangeError exception.
- 23. If days > 0 and sign = -1, throw a RangeError exception.
+ 23. If days < 0 and sign = 1, throw a RangeError exception.
+ 24. If days > 0 and sign = -1, throw a RangeError exception.
...
- 25. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception.
+ 26. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception.
...
- 28. If dayLength ≥ 2⁵³, throw a RangeError exception.
+ 29. If dayLength ≥ 2⁵³, throw a RangeError exception.
features: [Temporal, BigInt]
includes: [temporalHelpers.js]
---*/
@@ -41,7 +41,7 @@ function timeZoneSubstituteValues(
return tz;
}
-// Step 22: days < 0 and sign = 1
+// Step 23: days < 0 and sign = 1
let zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0
timeZoneSubstituteValues(
@@ -62,7 +62,7 @@ assert.throws(RangeError, () =>
"RangeError when days < 0 and sign = 1"
);
-// Step 23: days > 0 and sign = -1
+// Step 24: days > 0 and sign = -1
zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0
timeZoneSubstituteValues(
@@ -83,13 +83,13 @@ assert.throws(RangeError, () =>
"RangeError when days > 0 and sign = -1"
);
-// Step 25: nanoseconds > 0 and sign = -1
+// Step 26: nanoseconds > 0 and sign = -1
zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0
timeZoneSubstituteValues(
[
[new Temporal.Instant(-2n)], // Returned in step 16, setting _relativeResult_
- [new Temporal.Instant(-4n)], // Returned in step 21.a, setting _oneDayFarther_
+ [new Temporal.Instant(-4n)], // Returned in step 19, setting _oneDayFarther_
],
[
TemporalHelpers.SUBSTITUTE_SKIP, // Pre-conversion in Duration.p.round
@@ -107,12 +107,12 @@ assert.throws(RangeError, () =>
"RangeError when nanoseconds > 0 and sign = -1"
);
-// Step 28: day length is an unsafe integer
+// Step 29: day length is an unsafe integer
zdt = new Temporal.ZonedDateTime(
0n,
timeZoneSubstituteValues(
// Not called in step 16 because _days_ = 0
- // Returned in step 21.a, making _oneDayFarther_ 2^53 ns later than _relativeResult_
+ // Returned in step 19, making _oneDayFarther_ 2^53 ns later than _relativeResult_
[[new Temporal.Instant(2n ** 53n)]],
[]
)
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/calendar-dateadd-called-with-options-undefined.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/calendar-dateadd-called-with-options-undefined.js
index 5f5155e292..e0c22a689e 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/calendar-dateadd-called-with-options-undefined.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/calendar-dateadd-called-with-options-undefined.js
@@ -15,6 +15,6 @@ const calendar = TemporalHelpers.calendarDateAddUndefinedOptions();
const timeZone = TemporalHelpers.oneShiftTimeZone(new Temporal.Instant(0n), 3600e9);
const instance = new Temporal.Duration(1, 1, 1, 1);
instance.subtract(new Temporal.Duration(-1, -1, -1, -1), { relativeTo: new Temporal.ZonedDateTime(0n, timeZone, calendar) });
-assert.sameValue(calendar.dateAddCallCount, 3);
+assert.sameValue(calendar.dateAddCallCount, 2);
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/differencezoneddatetime-inconsistent-custom-calendar.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/differencezoneddatetime-inconsistent-custom-calendar.js
new file mode 100644
index 0000000000..e2c543c7d2
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/differencezoneddatetime-inconsistent-custom-calendar.js
@@ -0,0 +1,55 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.duration.prototype.subtract
+description: >
+ Throws a RangeError when custom calendar method returns inconsistent result
+info: |
+ DifferenceZonedDateTime ( ... )
+ 8. Repeat 3 times:
+ ...
+ g. If _sign_ = 0, or _timeSign_ = 0, or _sign_ = _timeSign_, then
+ ...
+ viii. Return ? CreateNormalizedDurationRecord(_dateDifference_.[[Years]],
+ _dateDifference_.[[Months]], _dateDifference_.[[Weeks]],
+ _dateDifference_.[[Days]], _norm_).
+ h. Set _dayCorrection_ to _dayCorrection_ + 1.
+ 9. NOTE: This step is only reached when custom calendar or time zone methods
+ return inconsistent values.
+ 10. Throw a *RangeError* exception.
+features: [Temporal]
+---*/
+
+// Based partly on a test case by André Bargull
+
+const duration1 = new Temporal.Duration(0, 0, /* weeks = */ 7, 0, /* hours = */ 12);
+const duration2 = new Temporal.Duration(0, 0, 0, /* days = */ -1);
+
+{
+ const tz = new (class extends Temporal.TimeZone {
+ getPossibleInstantsFor(dateTime) {
+ return super.getPossibleInstantsFor(dateTime.add({ days: 3 }));
+ }
+ })("UTC");
+
+ const relativeTo = new Temporal.ZonedDateTime(0n, tz);
+
+ assert.throws(RangeError, () => duration1.subtract(duration2, { relativeTo }),
+ "Calendar calculation where more than 2 days correction is needed should cause RangeError");
+}
+
+{
+ const cal = new (class extends Temporal.Calendar {
+ dateUntil(one, two, options) {
+ return super.dateUntil(one, two, options).negated();
+ }
+ })("iso8601");
+
+ const relativeTo = new Temporal.ZonedDateTime(0n, "UTC", cal);
+
+ assert.throws(RangeError, () => duration1.subtract(duration2, { relativeTo }),
+ "Calendar calculation causing mixed-sign values should cause RangeError");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/normalized-time-duration-to-days-loop-arbitrarily.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/normalized-time-duration-to-days-loop-arbitrarily.js
deleted file mode 100644
index 8873380037..0000000000
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/normalized-time-duration-to-days-loop-arbitrarily.js
+++ /dev/null
@@ -1,78 +0,0 @@
-// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
-// Copyright (C) 2022 Igalia, S.L. All rights reserved.
-// This code is governed by the BSD license found in the LICENSE file.
-
-/*---
-esid: sec-temporal.duration.prototype.subtract
-description: >
- NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer
-info: |
- NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
- ...
- 21. Repeat, while done is false,
- a. Let oneDayFarther be ? AddDaysToZonedDateTime(relativeResult.[[Instant]],
- relativeResult.[[DateTime]], timeZoneRec, zonedRelativeTo.[[Calendar]], sign).
- b. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(oneDayFarther.[[EpochNanoseconds]],
- relativeResult.[[EpochNanoseconds]]).
- c. Let oneDayLess be ? SubtractNormalizedTimeDuration(norm, dayLengthNs).
- c. If NormalizedTimeDurationSign(oneDayLess) × sign ≥ 0, then
- i. Set norm to oneDayLess.
- ii. Set relativeResult to oneDayFarther.
- iii. Set days to days + sign.
- d. Else,
- i. Set done to true.
-includes: [temporalHelpers.js]
-features: [Temporal]
----*/
-
-const calls = [];
-const duration = Temporal.Duration.from({ days: 1 });
-
-function createRelativeTo(count) {
- const dayLengthNs = 86400000000000n;
- const dayInstant = new Temporal.Instant(dayLengthNs);
- const substitutions = [];
- const timeZone = new Temporal.TimeZone("UTC");
- // Return constant value for first _count_ calls
- TemporalHelpers.substituteMethod(
- timeZone,
- "getPossibleInstantsFor",
- substitutions
- );
- substitutions.length = count;
- let i = 0;
- for (i = 0; i < substitutions.length; i++) {
- // (this value)
- substitutions[i] = [dayInstant];
- }
- // Record calls in calls[]
- TemporalHelpers.observeMethod(calls, timeZone, "getPossibleInstantsFor");
- return new Temporal.ZonedDateTime(0n, timeZone);
-}
-
-let zdt = createRelativeTo(50);
-calls.splice(0); // Reset calls list after ZonedDateTime construction
-duration.subtract(duration, {
- relativeTo: zdt,
-});
-assert.sameValue(
- calls.length,
- 50 + 1,
- "Expected duration.subtract to call getPossibleInstantsFor correct number of times"
-);
-
-zdt = createRelativeTo(100);
-calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
-duration.subtract(duration, {
- relativeTo: zdt,
-});
-assert.sameValue(
- calls.length,
- 100 + 1,
- "Expected duration.subtract to call getPossibleInstantsFor correct number of times"
-);
-
-zdt = createRelativeTo(107);
-assert.throws(RangeError, () => duration.subtract(duration, { relativeTo: zdt }), "107-2 days > 2⁵³ ns");
-
-reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/order-of-operations.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/order-of-operations.js
index 988835838f..55438a2b97 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/order-of-operations.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/order-of-operations.js
@@ -339,18 +339,8 @@ const expectedOpsForZonedRelativeTo = expected.concat([
"call options.relativeTo.timeZone.getPossibleInstantsFor",
// AddDuration → DifferenceZonedDateTime
"call options.relativeTo.timeZone.getOffsetNanosecondsFor",
- // AddDuration → DifferenceZonedDateTime → DifferenceISODateTime
- "call options.relativeTo.calendar.dateUntil",
- // AddDuration → DifferenceZonedDateTime → AddZonedDateTime
- "call options.relativeTo.calendar.dateAdd",
- "call options.relativeTo.timeZone.getPossibleInstantsFor",
- // AddDuration → DifferenceZonedDateTime → NanosecondsToDays
- "call options.relativeTo.timeZone.getOffsetNanosecondsFor",
- "call options.relativeTo.timeZone.getOffsetNanosecondsFor",
- // AddDuration → DifferenceZonedDateTime → NanosecondsToDays → AddZonedDateTime 1
- "call options.relativeTo.timeZone.getPossibleInstantsFor",
- // AddDuration → DifferenceZonedDateTime → NanosecondsToDays → AddZonedDateTime 2
"call options.relativeTo.timeZone.getPossibleInstantsFor",
+ "call options.relativeTo.calendar.dateUntil",
]);
const zonedRelativeTo = TemporalHelpers.propertyBagObserver(actual, {
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..193c09e806
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-backward-offset-shift.js
@@ -0,0 +1,50 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.subtract
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.Duration(1, 0, 0, 1);
+assert.throws(RangeError, () => instance.subtract(new Temporal.Duration(0, 0, 0, 0, 24), { relativeTo }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..3d1d6262f0
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-out-of-range-forward-offset-shift.js
@@ -0,0 +1,45 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.subtract
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.Duration(1, 0, 0, 1);
+assert.throws(RangeError, () => instance.subtract(new Temporal.Duration(0, 0, 0, 0, 24), { relativeTo }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js
deleted file mode 100644
index 46307bcaf9..0000000000
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/subtract/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js
+++ /dev/null
@@ -1,144 +0,0 @@
-// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
-// Copyright (C) 2022 Igalia, S.L. All rights reserved.
-// This code is governed by the BSD license found in the LICENSE file.
-/*---
-esid: sec-temporal.duration.prototype.subtract
-description: >
- Abstract operation NormalizedTimeDurationToDays can throw four different
- RangeErrors.
-info: |
- NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
- 22. If days < 0 and sign = 1, throw a RangeError exception.
- 23. If days > 0 and sign = -1, throw a RangeError exception.
- ...
- 25. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception.
- ...
- 28. If dayLength ≥ 2⁵³, throw a RangeError exception.
-features: [Temporal, BigInt]
-includes: [temporalHelpers.js]
----*/
-
-const dayNs = 86_400_000_000_000;
-const dayDuration = Temporal.Duration.from({ days: 1 });
-const epochInstant = new Temporal.Instant(0n);
-
-function timeZoneSubstituteValues(
- getPossibleInstantsFor,
- getOffsetNanosecondsFor
-) {
- const tz = new Temporal.TimeZone("UTC");
- TemporalHelpers.substituteMethod(
- tz,
- "getPossibleInstantsFor",
- getPossibleInstantsFor
- );
- TemporalHelpers.substituteMethod(
- tz,
- "getOffsetNanosecondsFor",
- getOffsetNanosecondsFor
- );
- return tz;
-}
-
-// Step 22: days < 0 and sign = 1
-let zdt = new Temporal.ZonedDateTime(
- -1n, // Set DifferenceZonedDateTime _ns1_
- timeZoneSubstituteValues(
- [
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for first call, AddDuration step 15
- [epochInstant], // Returned in AddDuration step 16, setting _endNs_ -> DifferenceZonedDateTime _ns2_
- [epochInstant], // Returned in step 16, setting _relativeResult_
- ],
- [
- // Behave normally in 3 calls made prior to NormalizedTimeDurationToDays
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- dayNs - 1, // Returned in step 8, setting _startDateTime_
- -dayNs + 1, // Returned in step 9, setting _endDateTime_
- ]
- )
-);
-assert.throws(RangeError, () =>
- // Subtracting day from day sets largestUnit to 'day', avoids having any week/month/year components in difference
- dayDuration.subtract(dayDuration, {
- relativeTo: zdt,
- })
-);
-
-// Step 23: days > 0 and sign = -1
-zdt = new Temporal.ZonedDateTime(
- 1n, // Set DifferenceZonedDateTime _ns1_
- timeZoneSubstituteValues(
- [
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for first call, AddDuration step 15
- [epochInstant], // Returned in AddDuration step 16, setting _endNs_ -> DifferenceZonedDateTime _ns2_
- [epochInstant], // Returned in step 16, setting _relativeResult_
- ],
- [
- // Behave normally in 3 calls made prior to NanosecondsToDays
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- -dayNs + 1, // Returned in step 8, setting _startDateTime_
- dayNs - 1, // Returned in step 9, setting _endDateTime_
- ]
- )
-);
-assert.throws(RangeError, () =>
- // Subtracting day from day sets largestUnit to 'day', avoids having any week/month/year components in difference
- dayDuration.subtract(dayDuration, {
- relativeTo: zdt,
- })
-);
-
-// Step 25: nanoseconds > 0 and sign = -1
-zdt = new Temporal.ZonedDateTime(
- 0n, // Set DifferenceZonedDateTime _ns1_
- timeZoneSubstituteValues(
- [
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for first call, AddDuration step 15
- [new Temporal.Instant(-1n)], // Returned in AddDuration step 16, setting _endNs_ -> DifferenceZonedDateTime _ns2_
- [new Temporal.Instant(-2n)], // Returned in step 16, setting _relativeResult_
- [new Temporal.Instant(-4n)], // Returned in step 21.a, setting _oneDayFarther_
- ],
- [
- // Behave normally in 3 calls made prior to NanosecondsToDays
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- TemporalHelpers.SUBSTITUTE_SKIP,
- dayNs - 1, // Returned in step 8, setting _startDateTime_
- -dayNs + 1, // Returned in step 9, setting _endDateTime_
- ]
- )
-);
-assert.throws(RangeError, () =>
- // Subtracting day from day sets largestUnit to 'day', avoids having any week/month/year components in difference
- dayDuration.subtract(dayDuration, {
- relativeTo: zdt,
- })
-);
-
-// Step 28: day length is an unsafe integer
-zdt = new Temporal.ZonedDateTime(
- 0n,
- timeZoneSubstituteValues(
- [
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for AddDuration step 15
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for AddDuration step 16
- TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for step 16, setting _relativeResult_
- // Returned in step 21.a, making _oneDayFarther_ 2^53 ns later than _relativeResult_
- [new Temporal.Instant(2n ** 53n - 3n * BigInt(dayNs))],
- ],
- []
- )
-);
-const twoDaysDuration = new Temporal.Duration(0, 0, 0, 2);
-assert.throws(RangeError, () =>
- dayDuration.subtract(twoDaysDuration, {
- relativeTo: zdt,
- }),
- "Should throw RangeError when time zone calculates an outrageous day length"
-);
-
-reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/dst-rounding-result.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/dst-rounding-result.js
new file mode 100644
index 0000000000..b2b4daf2de
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/dst-rounding-result.js
@@ -0,0 +1,40 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.duration.prototype.total
+description: >
+ Rounding the resulting duration takes the time zone's UTC offset shifts
+ into account
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const timeZone = TemporalHelpers.springForwardFallBackTimeZone();
+
+// Based on a test case by Adam Shaw
+
+{
+ // Date part of duration lands on skipped DST hour, causing disambiguation
+ const duration = new Temporal.Duration(0, 1, 0, 15, 12);
+ const relativeTo = new Temporal.ZonedDateTime(
+ 950868000_000_000_000n /* = 2000-02-18T10Z */,
+ timeZone); /* = 2000-02-18T02-08 in local time */
+
+ assert.sameValue(duration.total({ unit: "months", relativeTo }), 1.5,
+ "1 month 15 days 12 hours should be exactly 1.5 months");
+}
+
+{
+ // Month-only part of duration lands on skipped DST hour, should not cause
+ // disambiguation
+ const duration = new Temporal.Duration(0, 1, 0, 15);
+ const relativeTo = new Temporal.ZonedDateTime(
+ 951991200_000_000_000n /* = 2000-03-02T10Z */,
+ timeZone); /* = 2000-03-02T02-08 in local time */
+
+ assert.sameValue(duration.total({ unit: "months", relativeTo }), 1.5,
+ "1 month 15 days should be exactly 1.5 months");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/duration-out-of-range-added-to-relativeto.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/duration-out-of-range-added-to-relativeto.js
index 2abd469541..cf33d39b0e 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/duration-out-of-range-added-to-relativeto.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/duration-out-of-range-added-to-relativeto.js
@@ -8,21 +8,44 @@ description: RangeError thrown when calendar part of duration added to relativeT
features: [Temporal]
info: |
RoundDuration:
- 8.k. Let _isoResult_ be ! AddISODate(_plainRelativeTo_.[[ISOYear]]. _plainRelativeTo_.[[ISOMonth]], _plainRelativeTo_.[[ISODay]], 0, 0, 0, truncate(_fractionalDays_), *"constrain"*).
- l. Let _wholeDaysLater_ be ? CreateTemporalDate(_isoResult_.[[Year]], _isoResult_.[[Month]], _isoResult_.[[Day]], _calendar_).
+ 10.h. Let _isoResult_ be ! AddISODate(_plainRelativeTo_.[[ISOYear]]. _plainRelativeTo_.[[ISOMonth]], _plainRelativeTo_.[[ISODay]], 0, 0, 0, truncate(_fractionalDays_), *"constrain"*).
+ i. Let _wholeDaysLater_ be ? CreateTemporalDate(_isoResult_.[[Year]], _isoResult_.[[Month]], _isoResult_.[[Day]], _calendar_).
+ ...
+ 11.h. Let _isoResult_ be ! AddISODate(_plainRelativeTo_.[[ISOYear]]. _plainRelativeTo_.[[ISOMonth]], _plainRelativeTo_.[[ISODay]], 0, 0, 0, truncate(_fractionalDays_), *"constrain"*).
+ i. Let _wholeDaysLater_ be ? CreateTemporalDate(_isoResult_.[[Year]], _isoResult_.[[Month]], _isoResult_.[[Day]], _calendar_).
+ ...
+ 12.a. Let _isoResult_ be ! AddISODate(_plainRelativeTo_.[[ISOYear]]. _plainRelativeTo_.[[ISOMonth]], _plainRelativeTo_.[[ISODay]], 0, 0, 0, truncate(_fractionalDays_), *"constrain"*).
+ b. Let _wholeDaysLater_ be ? CreateTemporalDate(_isoResult_.[[Year]], _isoResult_.[[Month]], _isoResult_.[[Day]], _calendar_).
+
+ UnbalanceDateDurationRelative:
+ 11. Let _yearsMonthsWeeksDuration_ be ! CreateTemporalDuration(_years_, _months_, _weeks_, 0, 0, 0, 0, 0, 0, 0).
+ 12. Let _later_ be ? CalendarDateAdd(_calendaRec_, _plainRelativeTo_, _yearsMonthsWeeksDuration_).
+ 13. Let _yearsMonthsWeeksInDays_ be DaysUntil(_plainRelativeTo_, _later_).
+ 14. Return ? CreateDateDurationRecord(0, 0, 0, _days_ + _yearsMonthsWeeksInDays_).
---*/
// Based on a test case by André Bargull <andre.bargull@gmail.com>
-const instance = new Temporal.Duration(0, 0, 0, /* days = */ 500_000_000);
const relativeTo = new Temporal.PlainDate(2000, 1, 1);
-assert.throws(RangeError, () => instance.total({relativeTo, unit: "years"}));
-assert.throws(RangeError, () => instance.total({relativeTo, unit: "months"}));
-assert.throws(RangeError, () => instance.total({relativeTo, unit: "weeks"}));
-
-const negInstance = new Temporal.Duration(0, 0, 0, /* days = */ -500_000_000);
-assert.throws(RangeError, () => negInstance.total({relativeTo, unit: "years"}));
-assert.throws(RangeError, () => negInstance.total({relativeTo, unit: "months"}));
-assert.throws(RangeError, () => negInstance.total({relativeTo, unit: "weeks"}));
+
+{
+ const instance = new Temporal.Duration(0, 0, 0, /* days = */ 500_000_000);
+ assert.throws(RangeError, () => instance.total({relativeTo, unit: "years"}), "days out of range, positive, unit years");
+ assert.throws(RangeError, () => instance.total({relativeTo, unit: "months"}), "days out of range, positive, unit months");
+ assert.throws(RangeError, () => instance.total({relativeTo, unit: "weeks"}), "days out of range, positive, unit weeks");
+
+ const negInstance = new Temporal.Duration(0, 0, 0, /* days = */ -500_000_000);
+ assert.throws(RangeError, () => negInstance.total({relativeTo, unit: "years"}), "days out of range, negative, unit years");
+ assert.throws(RangeError, () => negInstance.total({relativeTo, unit: "months"}), "days out of range, negative, unit months");
+ assert.throws(RangeError, () => negInstance.total({relativeTo, unit: "weeks"}), "days out of range, negative, unit weeks");
+}
+
+{
+ const instance = new Temporal.Duration(0, 0, /* weeks = */ 1, /* days = */ Math.trunc((2 ** 53) / 86_400));
+ assert.throws(RangeError, () => instance.total({relativeTo, unit: "days"}), "weeks + days out of range, positive");
+
+ const negInstance = new Temporal.Duration(0, 0, /* weeks = */ -1, /* days = */ -Math.trunc((2 ** 53) / 86_400));
+ assert.throws(RangeError, () => instance.total({relativeTo, unit: "days"}), "weeks + days out of range, negative");
+}
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/normalized-time-duration-to-days-loop-arbitrarily.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/normalized-time-duration-to-days-loop-arbitrarily.js
index 72260abd68..c4d685113d 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/normalized-time-duration-to-days-loop-arbitrarily.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/normalized-time-duration-to-days-loop-arbitrarily.js
@@ -5,76 +5,42 @@
/*---
esid: sec-temporal.duration.prototype.total
description: >
- NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer
+ NormalizedTimeDurationToDays should not be able to loop arbitrarily.
info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
...
- 21. Repeat, while done is false,
- a. Let oneDayFarther be ? AddDaysToZonedDateTime(relativeResult.[[Instant]],
- relativeResult.[[DateTime]], timeZoneRec, zonedRelativeTo.[[Calendar]], sign).
- b. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(oneDayFarther.[[EpochNanoseconds]],
- relativeResult.[[EpochNanoseconds]]).
- c. Let oneDayLess be ? SubtractNormalizedTimeDuration(norm, dayLengthNs).
- c. If NormalizedTimeDurationSign(oneDayLess) × sign ≥ 0, then
- i. Set norm to oneDayLess.
- ii. Set relativeResult to oneDayFarther.
- iii. Set days to days + sign.
- d. Else,
- i. Set done to true.
-includes: [temporalHelpers.js]
+ 22. If NormalizedTimeDurationSign(_oneDayLess_) × _sign_ ≥ 0, then
+ a. Set _norm_ to _oneDayLess_.
+ b. Set _relativeResult_ to _oneDayFarther_.
+ c. Set _days_ to _days_ + _sign_.
+ d. Set _oneDayFarther_ to ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_).
+ e. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther.[[EpochNanoseconds]], relativeResult.[[EpochNanoseconds]]).
+ f. If NormalizedTimeDurationSign(? SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_)) × _sign_ ≥ 0, then
+ i. Throw a *RangeError* exception.
features: [Temporal]
---*/
-const calls = [];
const duration = Temporal.Duration.from({ days: 1 });
-function createRelativeTo(count) {
- const dayLengthNs = 86400000000000n;
- const dayInstant = new Temporal.Instant(dayLengthNs);
- const substitutions = [];
- const timeZone = new Temporal.TimeZone("UTC");
- // Return constant value for first _count_ calls
- TemporalHelpers.substituteMethod(
- timeZone,
- "getPossibleInstantsFor",
- substitutions
- );
- substitutions.length = count;
- let i = 0;
- for (i = 0; i < substitutions.length; i++) {
- // (this value)
- substitutions[i] = [dayInstant];
+const dayLengthNs = 86400000000000n;
+const dayInstant = new Temporal.Instant(dayLengthNs);
+let calls = 0;
+const timeZone = new class extends Temporal.TimeZone {
+ getPossibleInstantsFor() {
+ calls++;
+ return [dayInstant];
}
- // Record calls in calls[]
- TemporalHelpers.observeMethod(calls, timeZone, "getPossibleInstantsFor");
- return new Temporal.ZonedDateTime(0n, timeZone);
-}
+}("UTC");
-let zdt = createRelativeTo(50);
-calls.splice(0); // Reset calls list after ZonedDateTime construction
-duration.total({
- unit: "day",
- relativeTo: zdt,
-});
-assert.sameValue(
- calls.length,
- 50 + 2,
- "Expected duration.total to call getPossibleInstantsFor correct number of times"
-);
+const relativeTo = new Temporal.ZonedDateTime(0n, timeZone);
-zdt = createRelativeTo(100);
-calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
-duration.total({
- unit: "day",
- relativeTo: zdt,
-});
-assert.sameValue(
- calls.length,
- 100 + 2,
- "Expected duration.total to call getPossibleInstantsFor correct number of times"
-);
-
-zdt = createRelativeTo(106);
-assert.throws(RangeError, () => duration.total({ unit: "day", relativeTo: zdt }), "106-1 days > 2⁵³ ns");
+assert.throws(RangeError, () => duration.total({ unit: "days", relativeTo }), "indefinite loop is prevented");
+assert.sameValue(calls, 4, "getPossibleInstantsFor is not called indefinitely");
+ // Expected calls:
+ // AddZonedDateTime (1)
+ // NormalizedTimeDurationToDays ->
+ // AddDaysToZonedDateTime (2, step 12)
+ // AddDaysToZonedDateTime (3, step 15)
+ // AddDaysToZonedDateTime (4, step 18.d)
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-3.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-3.js
index ab3fd772cc..9f00233078 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-3.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-3.js
@@ -71,7 +71,7 @@ function f64Repr(f) {
const tz = new (class extends Temporal.TimeZone {
getPossibleInstantsFor() {
- // Called in NormalizedTimeDurationToDays 21.a from RoundDuration 7.b.
+ // Called in NormalizedTimeDurationToDays 19 from RoundDuration 7.b.
// Sets _result_.[[DayLength]] to 2⁵³ - 1 ns, its largest possible value
return [new Temporal.Instant(-86400_0000_0000_000_000_000n + 2n ** 53n - 1n)];
}
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..83878fb1b2
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-backward-offset-shift.js
@@ -0,0 +1,50 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.total
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.Duration(1, 0, 0, 0, 24);
+assert.throws(RangeError, () => instance.total({ unit: "days", relativeTo }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..0012871d85
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-out-of-range-forward-offset-shift.js
@@ -0,0 +1,45 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.total
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.Duration(1, 0, 0, 0, 24);
+assert.throws(RangeError, () => instance.total({ unit: "days", relativeTo }), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js
index d415117f89..3150c81852 100644
--- a/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js
+++ b/js/src/tests/test262/built-ins/Temporal/Duration/prototype/total/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js
@@ -8,12 +8,12 @@ description: >
RangeErrors.
info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
- 22. If days < 0 and sign = 1, throw a RangeError exception.
- 23. If days > 0 and sign = -1, throw a RangeError exception.
+ 23. If days < 0 and sign = 1, throw a RangeError exception.
+ 24. If days > 0 and sign = -1, throw a RangeError exception.
...
- 25. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception.
+ 26. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception.
...
- 28. If dayLength ≥ 2⁵³, throw a RangeError exception.
+ 29. If dayLength ≥ 2⁵³, throw a RangeError exception.
features: [Temporal, BigInt]
includes: [temporalHelpers.js]
---*/
@@ -41,7 +41,7 @@ function timeZoneSubstituteValues(
return tz;
}
-// Step 22: days < 0 and sign = 1
+// Step 23: days < 0 and sign = 1
let zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0
timeZoneSubstituteValues(
@@ -62,7 +62,7 @@ assert.throws(RangeError, () =>
"RangeError when days < 0 and sign = 1"
);
-// Step 23: days > 0 and sign = -1
+// Step 24: days > 0 and sign = -1
zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0
timeZoneSubstituteValues(
@@ -83,13 +83,13 @@ assert.throws(RangeError, () =>
"RangeError when days > 0 and sign = -1"
);
-// Step 25: nanoseconds > 0 and sign = -1
+// Step 26: nanoseconds > 0 and sign = -1
zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0
timeZoneSubstituteValues(
[
[new Temporal.Instant(-2n)], // Returned in step 16, setting _relativeResult_
- [new Temporal.Instant(-4n)], // Returned in step 21.a, setting _oneDayFarther_
+ [new Temporal.Instant(-4n)], // Returned in step 19, setting _oneDayFarther_
],
[
TemporalHelpers.SUBSTITUTE_SKIP, // pre-conversion in Duration.p.total
@@ -107,12 +107,12 @@ assert.throws(RangeError, () =>
"RangeError when nanoseconds > 0 and sign = -1"
);
-// Step 28: day length is an unsafe integer
+// Step 29: day length is an unsafe integer
zdt = new Temporal.ZonedDateTime(
0n,
timeZoneSubstituteValues(
// Not called in step 16 because _days_ = 0
- // Returned in step 21.a, making _oneDayFarther_ 2^53 ns later than _relativeResult_
+ // Returned in step 19, making _oneDayFarther_ 2^53 ns later than _relativeResult_
[[new Temporal.Instant(2n ** 53n)]],
[]
)