From a3f54a8afa1d73272d11ec0c577dac903fc58415 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Fri, 19 Jan 2024 14:28:16 -0800 Subject: [PATCH] Temporal: Tests for correct intermediate value in ZonedDateTime difference/rounding These test cases ensure that DST disambiguation does not take place on intermediate values that are not the start or the end of the calculation. Note that NormalizedTimeDurationToDays is no longer called inside Temporal.Duration.prototype.add/subtract, so a few tests can be deleted. Other tests need to be adjusted because NormalizedTimeDurationToDays is no longer called inside Temporal.ZonedDateTime.prototype.since/until via DifferenceZonedDateTime, although it is still called as part of rounding. In addition, new tests for the now-fixed edge case are added. See https://github.com/tc39/proposal-temporal/pull/2760 --- ...-time-duration-to-days-loop-arbitrarily.js | 46 ------ .../prototype/add/order-of-operations.js | 7 - ...ized-time-duration-to-days-range-errors.js | 143 ------------------ .../prototype/round/dst-rounding-result.js | 45 ++++++ ...-time-duration-to-days-loop-arbitrarily.js | 46 ------ .../prototype/subtract/order-of-operations.js | 7 - ...ized-time-duration-to-days-range-errors.js | 141 ----------------- .../prototype/total/dst-rounding-result.js | 37 +++++ .../prototype/since/dst-rounding-result.js | 45 ++++++ ...-time-duration-to-days-loop-arbitrarily.js | 16 +- ...ized-time-duration-to-days-range-errors.js | 4 +- .../prototype/since/order-of-operations.js | 11 -- .../prototype/until/dst-rounding-result.js | 45 ++++++ ...-time-duration-to-days-loop-arbitrarily.js | 16 +- ...ized-time-duration-to-days-range-errors.js | 4 +- .../prototype/until/order-of-operations.js | 11 -- 16 files changed, 194 insertions(+), 430 deletions(-) delete mode 100644 test/built-ins/Temporal/Duration/prototype/add/normalized-time-duration-to-days-loop-arbitrarily.js delete mode 100644 test/built-ins/Temporal/Duration/prototype/add/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js create mode 100644 test/built-ins/Temporal/Duration/prototype/round/dst-rounding-result.js delete mode 100644 test/built-ins/Temporal/Duration/prototype/subtract/normalized-time-duration-to-days-loop-arbitrarily.js delete mode 100644 test/built-ins/Temporal/Duration/prototype/subtract/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js create mode 100644 test/built-ins/Temporal/Duration/prototype/total/dst-rounding-result.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/since/dst-rounding-result.js create mode 100644 test/built-ins/Temporal/ZonedDateTime/prototype/until/dst-rounding-result.js diff --git a/test/built-ins/Temporal/Duration/prototype/add/normalized-time-duration-to-days-loop-arbitrarily.js b/test/built-ins/Temporal/Duration/prototype/add/normalized-time-duration-to-days-loop-arbitrarily.js deleted file mode 100644 index b661510e8f8..00000000000 --- a/test/built-ins/Temporal/Duration/prototype/add/normalized-time-duration-to-days-loop-arbitrarily.js +++ /dev/null @@ -1,46 +0,0 @@ -// 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 should not be able to loop arbitrarily. -info: | - NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] ) - ... - 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 duration = Temporal.Duration.from({ days: 1 }); - -const dayLengthNs = 86400000000000n; -const dayInstant = new Temporal.Instant(dayLengthNs); -let calls = 0; -const timeZone = new class extends Temporal.TimeZone { - getPossibleInstantsFor() { - calls++; - return [dayInstant]; - } -}("UTC"); - -const relativeTo = new Temporal.ZonedDateTime(0n, timeZone); - -assert.throws(RangeError, () => duration.add(duration, { relativeTo }), "arbitrarily long loop is prevented"); -assert.sameValue(calls, 5, "getPossibleInstantsFor is not called in an arbitrarily long loop"); - // Expected calls: - // AddDuration -> - // AddZonedDateTime (1) - // AddZonedDateTime (2) - // DifferenceZonedDateTime -> - // NormalizedTimeDurationToDays -> - // AddDaysToZonedDateTime (3, step 12) - // AddDaysToZonedDateTime (4, step 15) - // AddDaysToZonedDateTime (5, step 18.d) diff --git a/test/built-ins/Temporal/Duration/prototype/add/order-of-operations.js b/test/built-ins/Temporal/Duration/prototype/add/order-of-operations.js index d59a7cf0b83..b72d82d9c1d 100644 --- a/test/built-ins/Temporal/Duration/prototype/add/order-of-operations.js +++ b/test/built-ins/Temporal/Duration/prototype/add/order-of-operations.js @@ -343,13 +343,6 @@ const expectedOpsForZonedRelativeTo = expected.concat([ // 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", ]); const zonedRelativeTo = TemporalHelpers.propertyBagObserver(actual, { diff --git a/test/built-ins/Temporal/Duration/prototype/add/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js b/test/built-ins/Temporal/Duration/prototype/add/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js deleted file mode 100644 index 85901d2f0e4..00000000000 --- a/test/built-ins/Temporal/Duration/prototype/add/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js +++ /dev/null @@ -1,143 +0,0 @@ -// 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 ] ) - 23. If days < 0 and sign = 1, throw a RangeError exception. - 24. If days > 0 and sign = -1, throw a RangeError exception. - ... - 26. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception. - ... - 29. 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 23: 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 24: 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 26: 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 19, 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 29: 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 19, 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" -); diff --git a/test/built-ins/Temporal/Duration/prototype/round/dst-rounding-result.js b/test/built-ins/Temporal/Duration/prototype/round/dst-rounding-result.js new file mode 100644 index 00000000000..a2744257e29 --- /dev/null +++ b/test/built-ins/Temporal/Duration/prototype/round/dst-rounding-result.js @@ -0,0 +1,45 @@ +// Copyright (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"); +} diff --git a/test/built-ins/Temporal/Duration/prototype/subtract/normalized-time-duration-to-days-loop-arbitrarily.js b/test/built-ins/Temporal/Duration/prototype/subtract/normalized-time-duration-to-days-loop-arbitrarily.js deleted file mode 100644 index 4f207894d62..00000000000 --- a/test/built-ins/Temporal/Duration/prototype/subtract/normalized-time-duration-to-days-loop-arbitrarily.js +++ /dev/null @@ -1,46 +0,0 @@ -// 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 should not be able to loop arbitrarily. -info: | - NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] ) - ... - 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 duration = Temporal.Duration.from({ days: 1 }); - -const dayLengthNs = 86400000000000n; -const dayInstant = new Temporal.Instant(dayLengthNs); -let calls = 0; -const timeZone = new class extends Temporal.TimeZone { - getPossibleInstantsFor() { - calls++; - return [dayInstant]; - } -}("UTC"); - -const relativeTo = new Temporal.ZonedDateTime(0n, timeZone); - -assert.throws(RangeError, () => duration.subtract(duration, { relativeTo }), "indefinite loop is prevented"); -assert.sameValue(calls, 5, "getPossibleInstantsFor is not called indefinitely"); - // Expected calls: - // AddDuration -> - // AddZonedDateTime (1) - // AddZonedDateTime (2) - // DifferenceZonedDateTime -> - // NormalizedTimeDurationToDays -> - // AddDaysToZonedDateTime (3, step 12) - // AddDaysToZonedDateTime (4, step 15) - // AddDaysToZonedDateTime (5, step 18.d) diff --git a/test/built-ins/Temporal/Duration/prototype/subtract/order-of-operations.js b/test/built-ins/Temporal/Duration/prototype/subtract/order-of-operations.js index 107a1a7bae4..fcac4d98764 100644 --- a/test/built-ins/Temporal/Duration/prototype/subtract/order-of-operations.js +++ b/test/built-ins/Temporal/Duration/prototype/subtract/order-of-operations.js @@ -343,13 +343,6 @@ const expectedOpsForZonedRelativeTo = expected.concat([ // 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", ]); const zonedRelativeTo = TemporalHelpers.propertyBagObserver(actual, { diff --git a/test/built-ins/Temporal/Duration/prototype/subtract/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js b/test/built-ins/Temporal/Duration/prototype/subtract/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js deleted file mode 100644 index 4ba27ff5f23..00000000000 --- a/test/built-ins/Temporal/Duration/prototype/subtract/relativeto-zoneddatetime-normalized-time-duration-to-days-range-errors.js +++ /dev/null @@ -1,141 +0,0 @@ -// 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 ] ) - 23. If days < 0 and sign = 1, throw a RangeError exception. - 24. If days > 0 and sign = -1, throw a RangeError exception. - ... - 26. If NormalizedTimeDurationSign(_norm_) = 1 and sign = -1, throw a RangeError exception. - ... - 29. 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 23: 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 24: 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 26: 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 19, 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 29: 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 19, 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" -); diff --git a/test/built-ins/Temporal/Duration/prototype/total/dst-rounding-result.js b/test/built-ins/Temporal/Duration/prototype/total/dst-rounding-result.js new file mode 100644 index 00000000000..47e1d3de751 --- /dev/null +++ b/test/built-ins/Temporal/Duration/prototype/total/dst-rounding-result.js @@ -0,0 +1,37 @@ +// Copyright (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"); +} diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/since/dst-rounding-result.js b/test/built-ins/Temporal/ZonedDateTime/prototype/since/dst-rounding-result.js new file mode 100644 index 00000000000..b47be48140f --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/since/dst-rounding-result.js @@ -0,0 +1,45 @@ +// Copyright (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"); +} diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js b/test/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js index 5328a887e85..b52723873e4 100644 --- a/test/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-loop-arbitrarily.js @@ -30,12 +30,14 @@ const timeZone = new class extends Temporal.TimeZone { }("UTC"); const zdt = new Temporal.ZonedDateTime(0n, timeZone); -const other = new Temporal.ZonedDateTime(dayLengthNs, "UTC", "iso8601"); +const other = new Temporal.ZonedDateTime(dayLengthNs * 2n, "UTC", "iso8601"); -assert.throws(RangeError, () => zdt.since(other, { largestUnit: "day" }), "indefinite loop is prevented"); -assert.sameValue(calls, 3, "getPossibleInstantsFor is not called indefinitely"); +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: - // DifferenceZonedDateTime -> NormalizedTimeDurationToDays -> - // AddDaysToZonedDateTime (3, step 12) - // AddDaysToZonedDateTime (4, step 15) - // AddDaysToZonedDateTime (5, step 18.d) + // DifferenceTemporalZonedDateTime -> + // DifferenceZonedDateTime -> AddZonedDateTime (1) + // NormalizedTimeDurationToDays -> + // AddDaysToZonedDateTime (2, step 12) + // AddDaysToZonedDateTime (3, step 15) + // AddDaysToZonedDateTime (4, step 18.d) diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js b/test/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js index 99025931b31..fb79b4cd0b3 100644 --- a/test/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/since/normalized-time-duration-to-days-range-errors.js @@ -39,7 +39,7 @@ 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 23: days < 0 and sign = 1 let start = new Temporal.ZonedDateTime( @@ -87,7 +87,7 @@ assert.throws(RangeError, () => start = new Temporal.ZonedDateTime( 1n, // Sets DifferenceZonedDateTime _ns1_ timeZoneSubstituteValues( - [[new Temporal.Instant(-1n)]], // Returned in step 16, setting _relativeResult_ + [[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, diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/since/order-of-operations.js b/test/built-ins/Temporal/ZonedDateTime/prototype/since/order-of-operations.js index 1478687e2ae..6a23c01e0ec 100644 --- a/test/built-ins/Temporal/ZonedDateTime/prototype/since/order-of-operations.js +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/since/order-of-operations.js @@ -305,11 +305,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 @@ -332,12 +327,6 @@ const expectedOpsForCalendarDifference = [ // 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", ]; const expectedOpsForCalendarRounding = [ diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/until/dst-rounding-result.js b/test/built-ins/Temporal/ZonedDateTime/prototype/until/dst-rounding-result.js new file mode 100644 index 00000000000..86323f27f10 --- /dev/null +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/until/dst-rounding-result.js @@ -0,0 +1,45 @@ +// Copyright (C) 2024 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-temporal.zoneddatetime.prototype.until +description: > + Rounding the resulting duration takes the time zone's UTC offset shifts + into account +includes: [temporalHelpers.js] +features: [Temporal] +---*/ + +// Based on a test case by Adam Shaw + +const timeZone = TemporalHelpers.springForwardFallBackTimeZone(); + +{ + // Month-only part of duration lands on skipped DST hour, should not cause + // disambiguation + const start = new Temporal.ZonedDateTime( + 950868000_000_000_000n /* = 2000-02-18T10Z */, + timeZone); /* = 2000-02-18T02-08 in local time */ + const end = new Temporal.ZonedDateTime( + 954709200_000_000_000n /* = 2000-04-02T21Z */, + timeZone); /* = 2000-04-02T14-07 in local time */ + + const duration = start.until(end, { largestUnit: "months" }); + TemporalHelpers.assertDuration(duration, 0, 1, 0, 15, 11, 0, 0, 0, 0, 0, + "1-month rounding window is shortened by DST"); +} + + +{ + // Month-only part of duration lands on skipped DST hour, should not cause + // disambiguation + const start = new Temporal.ZonedDateTime( + 951991200_000_000_000n /* = 2000-03-02T10Z */, + timeZone); /* = 2000-03-02T02-08 in local time */ + const end = new Temporal.ZonedDateTime( + 956005200_000_000_000n /* = 2000-04-17T21Z */, + timeZone); /* = 2000-04-17T14-07 in local time */ + + const duration = start.until(end, { largestUnit: "months" }); + TemporalHelpers.assertDuration(duration, 0, 1, 0, 15, 12, 0, 0, 0, 0, 0, + "1-month rounding window is not shortened by DST"); +} diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-loop-arbitrarily.js b/test/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-loop-arbitrarily.js index 004ce0838b9..c02d291922c 100644 --- a/test/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-loop-arbitrarily.js +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-loop-arbitrarily.js @@ -30,12 +30,14 @@ const timeZone = new class extends Temporal.TimeZone { }("UTC"); const zdt = new Temporal.ZonedDateTime(0n, timeZone); -const other = new Temporal.ZonedDateTime(dayLengthNs, "UTC", "iso8601"); +const other = new Temporal.ZonedDateTime(dayLengthNs * 2n, "UTC", "iso8601"); -assert.throws(RangeError, () => zdt.until(other, { largestUnit: "day" }), "indefinite loop is prevented"); -assert.sameValue(calls, 3, "getPossibleInstantsFor is not called indefinitely"); +assert.throws(RangeError, () => zdt.until(other, { largestUnit: "day", smallestUnit: "second" }), "indefinite loop is prevented"); +assert.sameValue(calls, 4, "getPossibleInstantsFor is not called indefinitely"); // Expected calls: - // DifferenceZonedDateTime -> NormalizedTimeDurationToDays -> - // AddDaysToZonedDateTime (3, step 12) - // AddDaysToZonedDateTime (4, step 15) - // AddDaysToZonedDateTime (5, step 18.d) + // DifferenceTemporalZonedDateTime -> + // DifferenceZonedDateTime -> AddZonedDateTime (1) + // NormalizedTimeDurationToDays -> + // AddDaysToZonedDateTime (2, step 12) + // AddDaysToZonedDateTime (3, step 15) + // AddDaysToZonedDateTime (4, step 18.d) diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-range-errors.js b/test/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-range-errors.js index 307559877b3..1d1ba6ec069 100644 --- a/test/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-range-errors.js +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/until/normalized-time-duration-to-days-range-errors.js @@ -39,7 +39,7 @@ 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 23: days < 0 and sign = 1 let start = new Temporal.ZonedDateTime( @@ -87,7 +87,7 @@ assert.throws(RangeError, () => start = new Temporal.ZonedDateTime( 1n, // Sets DifferenceZonedDateTime _ns1_ timeZoneSubstituteValues( - [[new Temporal.Instant(-1n)]], // Returned in step 16, setting _relativeResult_ + [[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, diff --git a/test/built-ins/Temporal/ZonedDateTime/prototype/until/order-of-operations.js b/test/built-ins/Temporal/ZonedDateTime/prototype/until/order-of-operations.js index 23a8e3fe371..a2e1ef3fdb8 100644 --- a/test/built-ins/Temporal/ZonedDateTime/prototype/until/order-of-operations.js +++ b/test/built-ins/Temporal/ZonedDateTime/prototype/until/order-of-operations.js @@ -305,11 +305,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 @@ -332,12 +327,6 @@ const expectedOpsForCalendarDifference = [ // 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", ]; const expectedOpsForCalendarRounding = [