summaryrefslogtreecommitdiffstats
path: root/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since')
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-iso-string.js30
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-year-zero.js3
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-backward-offset-shift.js50
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-forward-offset-shift.js45
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-string-calendar-annotation-invalid-key.js26
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/calendar-dateadd-called-with-options-undefined.js16
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/differencezoneddatetime-inconsistent-custom-calendar.js56
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-month-day-boundary.js29
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-rounding-result.js48
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/find-intermediate-instant.js84
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js86
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js51
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/order-of-operations.js15
-rw-r--r--js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/wrapping-at-end-of-month.js120
14 files changed, 554 insertions, 105 deletions
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-iso-string.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-iso-string.js
new file mode 100644
index 0000000000..6eeb874839
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-iso-string.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: An ISO 8601 string can be converted to a calendar ID in Calendar
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const timeZone = new Temporal.TimeZone("UTC");
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+
+for (const calendar of [
+ "2020-01-01",
+ "2020-01-01[u-ca=iso8601]",
+ "2020-01-01T00:00:00.000000000",
+ "2020-01-01T00:00:00.000000000[u-ca=iso8601]",
+ "01-01",
+ "01-01[u-ca=iso8601]",
+ "2020-01",
+ "2020-01[u-ca=iso8601]",
+]) {
+ const arg = { year: 1970, monthCode: "M01", day: 1, timeZone, calendar };
+ const result = instance.since(arg);
+ TemporalHelpers.assertDuration(result, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, `Calendar created from string "${calendar}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-year-zero.js
index 52a3aee0f5..b5dc42f348 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-year-zero.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-year-zero.js
@@ -17,7 +17,8 @@ const invalidStrings = [
];
const timeZone = new Temporal.TimeZone("UTC");
const instance = new Temporal.ZonedDateTime(0n, timeZone);
-invalidStrings.forEach((arg) => {
+invalidStrings.forEach((str) => {
+ const arg = { year: 1976, month: 11, day: 18, calendar: str };
assert.throws(
RangeError,
() => instance.since(arg),
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-backward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-backward-offset-shift.js
new file mode 100644
index 0000000000..c5181e85e0
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-backward-offset-shift.js
@@ -0,0 +1,50 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: >
+ UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
+features: [Temporal]
+info: |
+ GetPossibleInstantsFor:
+ 5.b.i. Let _numResults_ be _list_'s length.
+ ii. If _numResults_ > 1, then
+ 1. Let _epochNs_ be a new empty List.
+ 2. For each value _instant_ in list, do
+ a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
+ 3. Let _min_ be the least element of the List _epochNs_.
+ 4. Let _max_ be the greatest element of the List _epochNs_.
+ 5. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return 0;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const utc = new Temporal.TimeZone("UTC");
+ const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
+ return [
+ utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
+ utcInstant.add({ hours: 12 }),
+ utcInstant, // add a third value in case the implementation doesn't sort
+ ];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+assert.throws(RangeError, () => instance.since(arg), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-forward-offset-shift.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-forward-offset-shift.js
new file mode 100644
index 0000000000..d6f7970e8a
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-out-of-range-forward-offset-shift.js
@@ -0,0 +1,45 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: >
+ UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
+ in DisambiguatePossibleInstants cannot be greater than 24 hours.
+features: [Temporal]
+info: |
+ DisambiguatePossibleInstants:
+ 18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
+---*/
+
+class ShiftLonger24Hour extends Temporal.TimeZone {
+ id = 'TestTimeZone';
+ _shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
+
+ constructor() {
+ super('UTC');
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
+ return 12 * 3600e9 + 1;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
+ const { year, month, day } = plainDateTime;
+
+ if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
+ if (year === 1970 && month === 1 && day === 1) return [];
+ return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
+ }
+}
+
+const timeZone = new ShiftLonger24Hour();
+const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
+
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+assert.throws(RangeError, () => instance.since(arg), "RangeError should be thrown");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-string-calendar-annotation-invalid-key.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-string-calendar-annotation-invalid-key.js
new file mode 100644
index 0000000000..5d0053cad5
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/argument-string-calendar-annotation-invalid-key.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: Annotation keys are lowercase-only
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ ["1970-01-01T00:00[UTC][U-CA=iso8601]", "invalid capitalized key"],
+ ["1970-01-01T00:00[UTC][u-CA=iso8601]", "invalid partially-capitalized key"],
+ ["1970-01-01T00:00[UTC][FOO=bar]", "invalid capitalized unrecognized key"],
+];
+const timeZone = new Temporal.TimeZone("UTC");
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+invalidStrings.forEach(([arg, descr]) => {
+ assert.throws(
+ RangeError,
+ () => instance.since(arg),
+ `annotation keys must be lowercase: ${arg} - ${descr}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/calendar-dateadd-called-with-options-undefined.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/calendar-dateadd-called-with-options-undefined.js
index 7a5367bf29..932847c329 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/calendar-dateadd-called-with-options-undefined.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/calendar-dateadd-called-with-options-undefined.js
@@ -14,27 +14,17 @@ features: [Temporal]
const calendar = TemporalHelpers.calendarDateAddUndefinedOptions();
const timeZone = TemporalHelpers.oneShiftTimeZone(new Temporal.Instant(0n), 3600e9);
const earlier = new Temporal.ZonedDateTime(0n, timeZone, calendar);
-
-// Basic difference with largestUnit larger than days.
-// The call comes from this path:
-// ZonedDateTime.since() -> DifferenceZonedDateTime -> AddZonedDateTime -> calendar.dateAdd()
-
-const later1 = new Temporal.ZonedDateTime(1_213_200_000_000_000n, timeZone, calendar);
-later1.since(earlier, { largestUnit: "weeks" });
-assert.sameValue(calendar.dateAddCallCount, 1, "basic difference with largestUnit >days");
+const later = new Temporal.ZonedDateTime(1_213_200_000_000_000n, timeZone, calendar);
// Difference with rounding, with smallestUnit a calendar unit.
// The calls come from these paths:
// ZonedDateTime.since() ->
-// DifferenceZonedDateTime -> AddZonedDateTime -> calendar.dateAdd()
// RoundDuration ->
// MoveRelativeZonedDateTime -> AddZonedDateTime -> calendar.dateAdd()
// MoveRelativeDate -> calendar.dateAdd()
// BalanceDurationRelative -> MoveRelativeDate -> calendar.dateAdd()
-calendar.dateAddCallCount = 0;
-
-later1.since(earlier, { smallestUnit: "weeks" });
-assert.sameValue(calendar.dateAddCallCount, 4, "rounding difference with calendar smallestUnit");
+later.since(earlier, { smallestUnit: "weeks" });
+assert.sameValue(calendar.dateAddCallCount, 3, "rounding difference with calendar smallestUnit");
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/differencezoneddatetime-inconsistent-custom-calendar.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/differencezoneddatetime-inconsistent-custom-calendar.js
new file mode 100644
index 0000000000..391f978f0d
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/differencezoneddatetime-inconsistent-custom-calendar.js
@@ -0,0 +1,56 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: >
+ Throws a RangeError when custom calendar method returns inconsistent result
+info: |
+ DifferenceZonedDateTime ( ... )
+ 8. Repeat 3 times:
+ ...
+ g. If _sign_ = 0, or _timeSign_ = 0, or _sign_ = _timeSign_, then
+ ...
+ viii. Return ? CreateNormalizedDurationRecord(_dateDifference_.[[Years]],
+ _dateDifference_.[[Months]], _dateDifference_.[[Weeks]],
+ _dateDifference_.[[Days]], _norm_).
+ h. Set _dayCorrection_ to _dayCorrection_ + 1.
+ 9. NOTE: This step is only reached when custom calendar or time zone methods
+ return inconsistent values.
+ 10. Throw a *RangeError* exception.
+features: [Temporal]
+---*/
+
+// Based partly on a test case by André Bargull
+
+const fiftyDays12Hours = 50n * 86400_000_000_000n + 12n * 3600_000_000_000n;
+
+{
+ const tz = new (class extends Temporal.TimeZone {
+ getPossibleInstantsFor(dateTime) {
+ return super.getPossibleInstantsFor(dateTime.subtract({ days: 3 }));
+ }
+ })("UTC");
+
+ const zdt1 = new Temporal.ZonedDateTime(0n, tz);
+ const zdt2 = new Temporal.ZonedDateTime(fiftyDays12Hours, tz);
+
+ assert.throws(RangeError, () => zdt2.since(zdt1, { largestUnit: "weeks" }),
+ "Calendar calculation where more than 2 days correction is needed should cause RangeError");
+}
+
+{
+ const cal = new (class extends Temporal.Calendar {
+ dateUntil(one, two, options) {
+ return super.dateUntil(one, two, options).negated();
+ }
+ })("iso8601");
+
+ const zdt1 = new Temporal.ZonedDateTime(0n, "UTC", cal);
+ const zdt2 = new Temporal.ZonedDateTime(fiftyDays12Hours, "UTC", cal);
+
+ assert.throws(RangeError, () => zdt2.since(zdt1, { largestUnit: "weeks" }),
+ "Calendar calculation causing mixed-sign values should cause RangeError");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-month-day-boundary.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-month-day-boundary.js
new file mode 100644
index 0000000000..c45ebd5aa2
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-month-day-boundary.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: >
+ Difference with the endpoint being the end of a skipped hour, chooses the
+ smaller of two possible durations
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+// Based on a test case by Adam Shaw
+
+const timeZone = TemporalHelpers.springForwardFallBackTimeZone();
+
+const d1 = new Temporal.ZonedDateTime(957258000_000_000_000n /* = 2000-05-02T02:00-07:00 */, timeZone);
+const d2 = new Temporal.ZonedDateTime(954669600_000_000_000n /* = 2000-04-02T03:00-07:00 */, timeZone);
+// NOTE: nonexistent hour just before d2
+
+const result = d1.since(d2, { largestUnit: "months" });
+
+TemporalHelpers.assertDuration(
+ result, 0, 0, 0, 29, 23, 0, 0, 0, 0, 0,
+ "Result should not balance up to months, but pick the smaller of two possible durations"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-rounding-result.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-rounding-result.js
new file mode 100644
index 0000000000..c3a0f3f3a2
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/dst-rounding-result.js
@@ -0,0 +1,48 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: >
+ Rounding the resulting duration takes the time zone's UTC offset shifts
+ into account
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+// Based on a test case by Adam Shaw
+
+const timeZone = TemporalHelpers.springForwardFallBackTimeZone();
+
+{
+ // Month-only part of duration lands on skipped DST hour, should not cause
+ // disambiguation
+ const start = new Temporal.ZonedDateTime(
+ 950868000_000_000_000n /* = 2000-02-18T10Z */,
+ timeZone); /* = 2000-02-18T02-08 in local time */
+ const end = new Temporal.ZonedDateTime(
+ 954709200_000_000_000n /* = 2000-04-02T21Z */,
+ timeZone); /* = 2000-04-02T14-07 in local time */
+
+ const duration = start.since(end, { largestUnit: "months" });
+ TemporalHelpers.assertDuration(duration, 0, -1, 0, -15, -11, 0, 0, 0, 0, 0,
+ "1-month rounding window is shortened by DST");
+}
+
+
+{
+ // Month-only part of duration lands on skipped DST hour, should not cause
+ // disambiguation
+ const start = new Temporal.ZonedDateTime(
+ 951991200_000_000_000n /* = 2000-03-02T10Z */,
+ timeZone); /* = 2000-03-02T02-08 in local time */
+ const end = new Temporal.ZonedDateTime(
+ 956005200_000_000_000n /* = 2000-04-17T21Z */,
+ timeZone); /* = 2000-04-17T14-07 in local time */
+
+ const duration = start.since(end, { largestUnit: "months" });
+ TemporalHelpers.assertDuration(duration, 0, -1, 0, -15, -12, 0, 0, 0, 0, 0,
+ "1-month rounding window is not shortened by DST");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/find-intermediate-instant.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/find-intermediate-instant.js
new file mode 100644
index 0000000000..7ef187ce3b
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/find-intermediate-instant.js
@@ -0,0 +1,84 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.zoneddatetime.prototype.until
+description: >
+ Up to 3 intermediate instants may be tried when calculating ZonedDateTime
+ difference
+includes: [compareArray.js, temporalHelpers.js]
+features: [BigInt, Temporal]
+---*/
+
+const calls = [];
+
+const springFallZone = TemporalHelpers.springForwardFallBackTimeZone();
+TemporalHelpers.observeMethod(calls, springFallZone, "getPossibleInstantsFor");
+
+const dateLineZone = TemporalHelpers.crossDateLineTimeZone();
+TemporalHelpers.observeMethod(calls, dateLineZone, "getPossibleInstantsFor");
+
+const zdt2 = new Temporal.ZonedDateTime(946722600_000_000_000n /* = 2000-01-01T02:30 local */, springFallZone);
+
+// Future -> past, without wall-clock overshoot
+// Expects valid intermediate Instant WITHOUT day correction (computed once)
+{
+ const zdt1 = new Temporal.ZonedDateTime(949442400_000_000_000n /* = 2000-02-01T14:00 local */, springFallZone);
+ const result = zdt1.since(zdt2, { largestUnit: "years" });
+ TemporalHelpers.assertDuration(result, 0, 1, 0, 0, 11, 30, 0, 0, 0, 0, "no wall-clock overshoot, no DST");
+ assert.compareArray(calls, [
+ "call getPossibleInstantsFor", // first intermediate in DifferenceZonedDateTime
+ ], "one intermediate should be tried");
+}
+
+calls.splice(0); // clear
+
+// Future -> past, WITH wall-clock overshoot
+// Expects valid intermediate Instant with guaranteed 1-DAY correction (computed once)
+{
+ const zdt1 = new Temporal.ZonedDateTime(949395600_000_000_000n /* = 2000-02-01T01:00 local */, springFallZone);
+ const result = zdt1.since(zdt2, { largestUnit: "years" });
+ TemporalHelpers.assertDuration(result, 0, 0, 0, 30, 22, 30, 0, 0, 0, 0, "wall-clock overshoot, no DST");
+ assert.compareArray(calls, [
+ "call getPossibleInstantsFor", // first intermediate in DifferenceZonedDateTime
+ ], "one intermediate should be tried");
+}
+
+calls.splice(0); // clear
+
+// Future -> past, WITH wall-clock overshoot
+// Expects valid intermediate Instant with guaranteed 1-DAY correction (computed once)
+// Intermediate Instant falls within spring DST gap and gets pushed forward,
+// but since moving from future -> past, not possible to exacerbate overflow,
+// so no other day corrections.
+{
+ const end = new Temporal.ZonedDateTime(957258000_000_000_000n /* = 2000-05-02T02:00 local */, springFallZone);
+ const start = new Temporal.ZonedDateTime(954671400_000_000_000n /* = 2000-04-02T03:30-07:00 local */, springFallZone);
+ const result = end.since(start, { largestUnit: "years" });
+ TemporalHelpers.assertDuration(result, 0, 0, 0, 29, 22, 30, 0, 0, 0, 0, "wall-clock overshoot, inconsiquential DST");
+ assert.compareArray(calls, [
+ "call getPossibleInstantsFor", // first intermediate in DifferenceZonedDateTime
+ ], "one intermediate should be tried");
+}
+
+calls.splice(0); // clear
+
+// Past -> future, WITH wall-clock overshoot
+// Tries intermediate Instant with 1-DAY correction (first compute)
+// Then, ANOTHER day correction because updated intermediate Instant falls within dateline DST gap,
+// pushing it forward, causing wall-clock overshoot again
+// (Not possible when going backwards)
+// (This test is just the same as the corresponding one in until(), but negative)
+{
+ const start = new Temporal.ZonedDateTime(1325102400_000_000_000n /* = 2011-12-28T10:00 local */, dateLineZone);
+ const end = new Temporal.ZonedDateTime(1325257200_000_000_000n /* = 2011-12-31T05:00 local */, dateLineZone);
+ const result = start.since(end, { largestUnit: "days" });
+ TemporalHelpers.assertDuration(result, 0, 0, 0, -1, -19, 0, 0, 0, 0, 0, "wall-clock overshoot, consiquential DST");
+ assert.compareArray(calls, [
+ "call getPossibleInstantsFor", // first intermediate in DifferenceZonedDateTime
+ "call getPossibleInstantsFor", // second intermediate in DifferenceZonedDateTime
+ "call getPossibleInstantsFor", // DisambiguatePossibleInstants on second intermediate
+ ], "two intermediates should be tried, with disambiguation");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js
index 23f993f25f..454d5d216a 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js
@@ -5,74 +5,42 @@
/*---
esid: sec-temporal.zoneddatetime.prototype.since
description: >
- NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer
+ NormalizedTimeDurationToDays should not be able to loop arbitrarily.
info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
...
- 21. Repeat, while done is false,
- a. Let oneDayFarther be ? AddDaysToZonedDateTime(relativeResult.[[Instant]],
- relativeResult.[[DateTime]], timeZoneRec, zonedRelativeTo.[[Calendar]], sign).
- b. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(oneDayFarther.[[EpochNanoseconds]],
- relativeResult.[[EpochNanoseconds]]).
- c. Let oneDayLess be ? SubtractNormalizedTimeDuration(norm, dayLengthNs).
- c. If NormalizedTimeDurationSign(oneDayLess) × sign ≥ 0, then
- i. Set norm to oneDayLess.
- ii. Set relativeResult to oneDayFarther.
- iii. Set days to days + sign.
- d. Else,
- i. Set done to true.
-includes: [temporalHelpers.js]
+ 22. If NormalizedTimeDurationSign(_oneDayLess_) × _sign_ ≥ 0, then
+ a. Set _norm_ to _oneDayLess_.
+ b. Set _relativeResult_ to _oneDayFarther_.
+ c. Set _days_ to _days_ + _sign_.
+ d. Set _oneDayFarther_ to ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_).
+ e. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther.[[EpochNanoseconds]], relativeResult.[[EpochNanoseconds]]).
+ f. If NormalizedTimeDurationSign(? SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_)) × _sign_ ≥ 0, then
+ i. Throw a *RangeError* exception.
features: [Temporal]
---*/
-const calls = [];
const dayLengthNs = 86400000000000n;
-const other = new Temporal.ZonedDateTime(dayLengthNs, "UTC", "iso8601");
-
-function createRelativeTo(count) {
- const dayInstant = new Temporal.Instant(dayLengthNs);
- const substitutions = [];
- const timeZone = new Temporal.TimeZone("UTC");
- // Return constant value for first _count_ calls
- TemporalHelpers.substituteMethod(
- timeZone,
- "getPossibleInstantsFor",
- substitutions
- );
- substitutions.length = count;
- let i = 0;
- for (i = 0; i < substitutions.length; i++) {
- // (this value)
- substitutions[i] = [dayInstant];
+const dayInstant = new Temporal.Instant(dayLengthNs);
+let calls = 0;
+const timeZone = new class extends Temporal.TimeZone {
+ getPossibleInstantsFor() {
+ calls++;
+ return [dayInstant];
}
- // Record calls in calls[]
- TemporalHelpers.observeMethod(calls, timeZone, "getPossibleInstantsFor");
- return new Temporal.ZonedDateTime(0n, timeZone);
-}
-
-let zdt = createRelativeTo(50);
-calls.splice(0); // Reset calls list after ZonedDateTime construction
-zdt.since(other, {
- largestUnit: "day",
-});
-assert.sameValue(
- calls.length,
- 50 + 1,
- "Expected ZonedDateTime.since to call getPossibleInstantsFor correct number of times"
-);
+}("UTC");
-zdt = createRelativeTo(100);
-calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
-zdt.since(other, {
- largestUnit: "day",
-});
-assert.sameValue(
- calls.length,
- 100 + 1,
- "Expected ZonedDateTime.since to call getPossibleInstantsFor correct number of times"
-);
+const zdt = new Temporal.ZonedDateTime(0n, timeZone);
+const other = new Temporal.ZonedDateTime(dayLengthNs * 2n, "UTC", "iso8601");
-zdt = createRelativeTo(105);
-assert.throws(RangeError, () => zdt.since(other, { largestUnit: "day" }), "105 days > 2⁵³ ns");
+assert.throws(RangeError, () => zdt.since(other, { largestUnit: "day", smallestUnit: "second" }), "indefinite loop is prevented");
+assert.sameValue(calls, 4, "getPossibleInstantsFor is not called indefinitely");
+ // Expected calls:
+ // DifferenceTemporalZonedDateTime ->
+ // DifferenceZonedDateTime -> GetInstantFor (1)
+ // NormalizedTimeDurationToDays ->
+ // AddDaysToZonedDateTime (2, step 12)
+ // AddDaysToZonedDateTime (3, step 15)
+ // AddDaysToZonedDateTime (4, step 18.d)
reportCompare(0, 0);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js
index 0c0b07f5dc..a6af0a69bc 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js
@@ -8,12 +8,12 @@ description: >
RangeErrors.
info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
- 22. If days < 0 and sign = 1, throw a RangeError exception.
- 23. If days > 0 and sign = -1, throw a RangeError exception.
+ 23. If days < 0 and sign = 1, throw a RangeError exception.
+ 24. If days > 0 and sign = -1, throw a RangeError exception.
...
- 25. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception.
+ 26. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception.
...
- 28. If dayLength ≥ 2⁵³, throw a RangeError exception.
+ 29. If dayLength ≥ 2⁵³, throw a RangeError exception.
features: [Temporal, BigInt]
includes: [temporalHelpers.js]
---*/
@@ -40,13 +40,16 @@ const dayNs = 86_400_000_000_000;
const zeroZDT = new Temporal.ZonedDateTime(0n, "UTC");
const oneZDT = new Temporal.ZonedDateTime(1n, "UTC");
const epochInstant = new Temporal.Instant(0n);
-const options = { largestUnit: "days" };
+const options = { largestUnit: "days", smallestUnit: "seconds", roundingMode: "expand" };
-// Step 22: days < 0 and sign = 1
+// Step 23: days < 0 and sign = 1
let start = new Temporal.ZonedDateTime(
0n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
- [[epochInstant]], // Returned in step 16, setting _relativeResult_
+ [
+ TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally in DifferenceZonedDateTime
+ [epochInstant], // Returned in step 16, setting _relativeResult_
+ ],
[
// Behave normally in 2 calls made prior to NormalizedTimeDurationToDays
TemporalHelpers.SUBSTITUTE_SKIP,
@@ -60,14 +63,18 @@ assert.throws(RangeError, () =>
start.since(
oneZDT, // Sets DifferenceZonedDateTime _ns2_
options
- )
+ ),
+ "days < 0 and sign = 1"
);
-// Step 23: days > 0 and sign = -1
+// Step 24: days > 0 and sign = -1
start = new Temporal.ZonedDateTime(
1n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
- [[epochInstant]], // Returned in step 16, setting _relativeResult_
+ [
+ TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally in DifferenceZonedDateTime
+ [epochInstant], // Returned in step 16, setting _relativeResult_
+ ],
[
// Behave normally in 2 calls made prior to NormalizedTimeDurationToDays
TemporalHelpers.SUBSTITUTE_SKIP,
@@ -81,14 +88,18 @@ assert.throws(RangeError, () =>
start.since(
zeroZDT, // Sets DifferenceZonedDateTime _ns2_
options
- )
+ ),
+ "days > 0 and sign = -1"
);
-// Step 25: nanoseconds > 0 and sign = -1
+// Step 26: nanoseconds > 0 and sign = -1
start = new Temporal.ZonedDateTime(
1n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
- [[new Temporal.Instant(-1n)]], // Returned in step 16, setting _relativeResult_
+ [
+ TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally in DifferenceZonedDateTime
+ [new Temporal.Instant(-2_000_000_000n)], // Returned in step 16, setting _relativeResult_
+ ],
[
// Behave normally in 2 calls made prior to NormalizedTimeDurationToDays
TemporalHelpers.SUBSTITUTE_SKIP,
@@ -102,16 +113,20 @@ assert.throws(RangeError, () =>
start.since(
zeroZDT, // Sets DifferenceZonedDateTime _ns2_
options
- )
+ ),
+ "norm > 0 and sign = -1"
);
-// Step 28: day length is an unsafe integer
+// Step 29: day length is an unsafe integer
start = new Temporal.ZonedDateTime(
0n,
timeZoneSubstituteValues(
- // Not called in step 16 because _days_ = 0
- // Returned in step 21.a, making _oneDayFarther_ 2^53 ns later than _relativeResult_
- [[new Temporal.Instant(2n ** 53n)]],
+ [
+ TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally in DifferenceZonedDateTime
+ // Not called in step 16 because _days_ = 0
+ // Returned in step 19, making _oneDayFarther_ 2^53 ns later than _relativeResult_
+ [new Temporal.Instant(2n ** 53n)],
+ ],
[]
)
);
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/order-of-operations.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/order-of-operations.js
index 566a1ce192..90fc1e0475 100644
--- a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/order-of-operations.js
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/order-of-operations.js
@@ -306,10 +306,6 @@ assert.compareArray(actual, [
// DifferenceZonedDateTime
"call this.timeZone.getOffsetNanosecondsFor",
"call this.timeZone.getOffsetNanosecondsFor",
- // NanosecondsToDays
- "call this.timeZone.getOffsetNanosecondsFor",
- "call this.timeZone.getOffsetNanosecondsFor",
- // NanosecondsToDays → AddDaysToZonedDateTime
"call this.timeZone.getPossibleInstantsFor",
], "order of operations with identical wall-clock times and largestUnit a calendar unit");
actual.splice(0); // clear
@@ -328,17 +324,8 @@ const expectedOpsForCalendarDifference = [
"call this.timeZone.getOffsetNanosecondsFor",
// DifferenceZonedDateTime
"call this.timeZone.getOffsetNanosecondsFor",
- // DifferenceISODateTime
- "call this.calendar.dateUntil",
- // AddZonedDateTime
- "call this.calendar.dateAdd",
- "call this.timeZone.getPossibleInstantsFor",
- // NanosecondsToDays
- "call this.timeZone.getOffsetNanosecondsFor",
- "call this.timeZone.getOffsetNanosecondsFor",
- // NanosecondsToDays → AddDaysToZonedDateTime
- "call this.timeZone.getPossibleInstantsFor",
"call this.timeZone.getPossibleInstantsFor",
+ "call this.calendar.dateUntil",
];
const expectedOpsForCalendarRounding = [
diff --git a/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/wrapping-at-end-of-month.js b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/wrapping-at-end-of-month.js
new file mode 100644
index 0000000000..3fddf0d09b
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/since/wrapping-at-end-of-month.js
@@ -0,0 +1,120 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2024 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: Tests balancing of days to months at end of month
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+// Difference between end of longer month to end of following shorter month
+{
+ const end = new Temporal.ZonedDateTime(5011200_000_000_000n /* = 1970-02-28T00Z */, "UTC");
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(2332800_000_000_000n /* = 1970-01-28T00Z */, "UTC").since(end, { largestUnit }),
+ 0, -1, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Jan 28th to Feb 28th is one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(2419200_000_000_000n /* = 1970-01-29T00Z */, "UTC").since(end, { largestUnit }),
+ 0, 0, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Feb 28th is 30 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(2505600_000_000_000n /* = 1970-01-30T00Z */, "UTC").since(end, { largestUnit }),
+ 0, 0, 0, -29, 0, 0, 0, 0, 0, 0,
+ "Jan 30th to Feb 28th is 29 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(2592000_000_000_000n /* = 1970-01-31T00Z */, "UTC").since(end, { largestUnit }),
+ 0, 0, 0, -28, 0, 0, 0, 0, 0, 0,
+ "Jan 31st to Feb 28th is 28 days, not one month"
+ );
+ }
+}
+
+// Difference between end of leap-year January to end of leap-year February
+{
+ const end = new Temporal.ZonedDateTime(68169600_000_000_000n /* = 1972-02-29T00Z */, "UTC");
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(65491200_000_000_000n /* = 1972-01-29T00Z */, "UTC").since(end, { largestUnit }),
+ 0, -1, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Feb 29th is one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(65577600_000_000_000n /* = 1972-01-30T00Z */, "UTC").since(end, { largestUnit }),
+ 0, 0, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Jan 30th to Feb 29th is 30 days, not one month"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(65664000_000_000_000n /* = 1972-01-31T00Z */, "UTC").since(end, { largestUnit }),
+ 0, 0, 0, -29, 0, 0, 0, 0, 0, 0,
+ "Jan 31st to Feb 29th is 29 days, not one month"
+ );
+ }
+}
+
+// Difference between end of longer month to end of not-immediately-following
+// shorter month
+{
+ const end = new Temporal.ZonedDateTime(28771200_000_000_000n /* = 1970-11-30T00Z */, "UTC");
+ for (const largestUnit of ["years", "months"]) {
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(20822400_000_000_000n /* = 1970-08-30T00Z */, "UTC").since(end, { largestUnit }),
+ 0, -3, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Aug 30th to Nov 30th is 3 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(20908800_000_000_000n /* = 1970-08-31T00Z */, "UTC").since(end, { largestUnit }),
+ 0, -2, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Aug 31st to Nov 30th is 2 months 30 days, not 3 months"
+ );
+ }
+}
+
+// Difference between end of longer month in one year to shorter month in
+// later year
+{
+ const end = new Temporal.ZonedDateTime(104976000_000_000_000n /* = 1973-04-30T00Z */, "UTC");
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(31363200_000_000_000n /* = 1970-12-30T00Z */, "UTC").since(end, { largestUnit: "months" }),
+ 0, -28, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 28 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(31363200_000_000_000n /* = 1970-12-30T00Z */, "UTC").since(end, { largestUnit: "years" }),
+ -2, -4, 0, 0, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 2 years, 4 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(31449600_000_000_000n /* = 1970-12-31T00Z */, "UTC").since(end, { largestUnit: "months" }),
+ 0, -27, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 27 months, 30 days, not 28 months"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(31449600_000_000_000n /* = 1970-12-31T00Z */, "UTC").since(end, { largestUnit: "years" }),
+ -2, -3, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Dec 30th 1970 to Apr 30th 1973 is 2 years, 3 months, 30 days, not 2 years 4 months"
+ );
+}
+
+// Difference where months passes through a month that's the same length or
+// shorter than either the start or end month
+{
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(2419200_000_000_000n /* = 1970-01-29T00Z */, "UTC").since(new Temporal.ZonedDateTime(7430400_000_000_000n /* = 1970-03-28T00Z */, "UTC"), { largestUnit: "months" }),
+ 0, -1, 0, -28, 0, 0, 0, 0, 0, 0,
+ "Jan 29th to Mar 28th is 1 month 28 days, not 58 days"
+ );
+ TemporalHelpers.assertDuration(
+ new Temporal.ZonedDateTime(2592000_000_000_000n /* = 1970-01-31T00Z */, "UTC").since(new Temporal.ZonedDateTime(44409600_000_000_000n /* = 1971-05-30T00Z */, "UTC"), { largestUnit: "years" }),
+ -1, -3, 0, -30, 0, 0, 0, 0, 0, 0,
+ "Jan 31st 1970 to May 30th 1971 is 1 year, 3 months, 30 days, not 1 year, 2 months, 60 days"
+ );
+}
+
+reportCompare(0, 0);