Temporal: Limit day length calculations to safe integers

NormalizedTimeDurationToDays can no longer loop indefinitely, because at
a certain point we will hit the upper bound of MAX_SAFE_INTEGER, so rename
the test to reflect that it can loop an arbitrary but limited number of
times.

Add a test for the RangeError condition in NormalizedTimeDurationToDays
when the time zone calculates a day length that is not a safe integer
number of nanoseconds.

While editing these tests, rename them to match the current name of the AO
and make sure the step numbers are up to date. (Normally I wouldn't care
so much about that, but these tests can be pretty confusing so it's good
to be able to refer to the spec text.)
This commit is contained in:
Philip Chimento 2023-10-18 17:05:08 -07:00 committed by Philip Chimento
parent 092337c8d0
commit 01ec9938bb
16 changed files with 708 additions and 551 deletions

View File

@ -4,18 +4,19 @@
/*---
esid: sec-temporal.duration.prototype.add
description: >
NanosecondsToDays can loop infinitely.
NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer
info: |
NanosecondsToDays ( nanoseconds, relativeTo )
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
...
18. Repeat, while done is false,
a. Let oneDayFartherNs be (? AddZonedDateTime((intermediateNs), relativeTo.[[TimeZone]],
relativeTo.[[Calendar]], 0, 0, 0, sign, 0, 0, 0, 0, 0, 0)).
b. Set dayLengthNs to oneDayFartherNs - intermediateNs.
c. If (nanoseconds - dayLengthNs) × sign 0, then
i. Set nanoseconds to nanoseconds - dayLengthNs.
ii. Set intermediateNs to oneDayFartherNs.
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.
@ -48,24 +49,27 @@ function createRelativeTo(count) {
return new Temporal.ZonedDateTime(0n, timeZone);
}
let zdt = createRelativeTo(200);
let zdt = createRelativeTo(50);
calls.splice(0); // Reset calls list after ZonedDateTime construction
duration.add(duration, {
relativeTo: zdt,
});
assert.sameValue(
calls.length,
200 + 1,
50 + 1,
"Expected duration.add to call getPossibleInstantsFor correct number of times"
);
zdt = createRelativeTo(300);
zdt = createRelativeTo(100);
calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
duration.add(duration, {
relativeTo: zdt,
});
assert.sameValue(
calls.length,
300 + 1,
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");

View File

@ -3,13 +3,16 @@
/*---
esid: sec-temporal.duration.prototype.add
description: >
Called abstract operation NanosecondsToDays can throw three different RangeErrors when paired with a ZonedDateTime.
Abstract operation NormalizedTimeDurationToDays can throw four different
RangeErrors.
info: |
6.5.7 NanosecondsToDays ( nanoseconds, relativeTo )
19. If days < 0 and sign = 1, throw a RangeError exception.
20. If days > 0 and sign = -1, throw a RangeError exception.
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.
...
22. If nanoseconds > 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]
---*/
@ -36,22 +39,22 @@ function timeZoneSubstituteValues(
return tz;
}
// NanosecondsToDays.19: days < 0 and sign = 1
// 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 9
[epochInstant], // Returned for AddDuration step 10, setting _endNs_ -> DifferenceZonedDateTime _ns2_
[epochInstant], // Returned for NanosecondsToDays step 14, setting _intermediateNs_
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
// Behave normally in 3 calls made prior to NormalizedTimeDurationToDays
TemporalHelpers.SUBSTITUTE_SKIP,
TemporalHelpers.SUBSTITUTE_SKIP,
TemporalHelpers.SUBSTITUTE_SKIP,
dayNs - 1, // Returned for NanosecondsToDays step 7, setting _startDateTime_
-dayNs + 1, // Returned for NanosecondsToDays step 11, setting _endDateTime_
dayNs - 1, // Returned in step 8, setting _startDateTime_
-dayNs + 1, // Returned in step 9, setting _endDateTime_
]
)
);
@ -63,22 +66,22 @@ assert.throws(RangeError, () =>
"days < 0 and sign = 1"
);
// NanosecondsToDays.20: 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 9
[epochInstant], // Returned for AddDuration step 10, setting _endNs_ -> DifferenceZonedDateTime _ns2_
[epochInstant], // Returned for NanosecondsToDays step 14, setting _intermediateNs_
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 for NanosecondsToDays step 7, setting _startDateTime_
dayNs - 1, // Returned for NanosecondsToDays step 11, setting _endDateTime_
-dayNs + 1, // Returned in step 8, setting _startDateTime_
dayNs - 1, // Returned in step 9, setting _endDateTime_
]
)
);
@ -90,23 +93,23 @@ assert.throws(RangeError, () =>
"days > 0 and sign = -1"
);
// NanosecondsToDays.22: nanoseconds > 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 9
[new Temporal.Instant(-1n)], // Returned for AddDuration step 10, setting _endNs_ -> DifferenceZonedDateTime _ns2_
[new Temporal.Instant(-2n)], // Returned for NanosecondsToDays step 14, setting _intermediateNs_
[new Temporal.Instant(-4n)], // Returned for NanosecondsToDays step 18.a, setting _oneDayFartherNs_
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 for NanosecondsToDays step 7, setting _startDateTime_
-dayNs + 1, // Returned for NanosecondsToDays step 11, setting _endDateTime_
dayNs - 1, // Returned in step 8, setting _startDateTime_
-dayNs + 1, // Returned in step 9, setting _endDateTime_
]
)
);
@ -117,3 +120,24 @@ assert.throws(RangeError, () =>
}),
"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"
);

View File

@ -3,18 +3,19 @@
/*---
esid: sec-temporal.duration.prototype.round
description: >
NanosecondsToDays can loop infinitely.
NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer
info: |
NanosecondsToDays ( nanoseconds, relativeTo )
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
...
18. Repeat, while done is false,
a. Let oneDayFartherNs be (? AddZonedDateTime((intermediateNs), relativeTo.[[TimeZone]],
relativeTo.[[Calendar]], 0, 0, 0, sign, 0, 0, 0, 0, 0, 0)).
b. Set dayLengthNs to oneDayFartherNs - intermediateNs.
c. If (nanoseconds - dayLengthNs) × sign 0, then
i. Set nanoseconds to nanoseconds - dayLengthNs.
ii. Set intermediateNs to oneDayFartherNs.
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.
@ -47,7 +48,7 @@ function createRelativeTo(count) {
return new Temporal.ZonedDateTime(0n, timeZone);
}
let zdt = createRelativeTo(200);
let zdt = createRelativeTo(50);
calls.splice(0); // Reset calls list after ZonedDateTime construction
duration.round({
smallestUnit: "days",
@ -55,11 +56,11 @@ duration.round({
});
assert.sameValue(
calls.length,
200 + 1,
50 + 1,
"Expected duration.round to call getPossibleInstantsFor correct number of times"
);
zdt = createRelativeTo(300);
zdt = createRelativeTo(100);
calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
duration.round({
smallestUnit: "days",
@ -67,6 +68,9 @@ duration.round({
});
assert.sameValue(
calls.length,
300 + 1,
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");

View File

@ -3,13 +3,16 @@
/*---
esid: sec-temporal.duration.prototype.round
description: >
Called abstract operation NanosecondsToDays can throw three different RangeErrors when paired with a ZonedDateTime.
Abstract operation NormalizedTimeDurationToDays can throw four different
RangeErrors.
info: |
6.5.7 NanosecondsToDays ( nanoseconds, relativeTo )
19. If days < 0 and sign = 1, throw a RangeError exception.
20. If days > 0 and sign = -1, throw a RangeError exception.
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.
...
22. If nanoseconds > 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]
---*/
@ -37,15 +40,15 @@ function timeZoneSubstituteValues(
return tz;
}
// NanosecondsToDays.19: days < 0 and sign = 1
// Step 22: days < 0 and sign = 1
let zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0
timeZoneSubstituteValues(
[[epochInstant]], // Returned for NanosecondsToDays step 14, setting _intermediateNs_
[[epochInstant]], // Returned in step 16, setting _relativeResult_
[
TemporalHelpers.SUBSTITUTE_SKIP, // Pre-conversion in Duration.p.round
dayNs - 1, // Returned for NanosecondsToDays step 7, setting _startDateTime_
-dayNs + 1, // Returned for NanosecondsToDays step 11, setting _endDateTime_
dayNs - 1, // Returned in step 8, setting _startDateTime_
-dayNs + 1, // Returned in step 9, setting _endDateTime_
]
)
);
@ -58,15 +61,15 @@ assert.throws(RangeError, () =>
"RangeError when days < 0 and sign = 1"
);
// NanosecondsToDays.20: days > 0 and sign = -1
// Step 23: days > 0 and sign = -1
zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0
timeZoneSubstituteValues(
[[epochInstant]], // Returned for NanosecondsToDays step 14, setting _intermediateNs_
[[epochInstant]], // Returned in step 16, setting _relativeResult_
[
TemporalHelpers.SUBSTITUTE_SKIP, // Pre-conversion in Duration.p.round
-dayNs + 1, // Returned for NanosecondsToDays step 7, setting _startDateTime_
dayNs - 1, // Returned for NanosecondsToDays step 11, setting _endDateTime_
-dayNs + 1, // Returned in step 8, setting _startDateTime_
dayNs - 1, // Returned in step 9, setting _endDateTime_
]
)
);
@ -79,18 +82,18 @@ assert.throws(RangeError, () =>
"RangeError when days > 0 and sign = -1"
);
// NanosecondsToDays.22: nanoseconds > 0 and sign = -1
// Step 25: nanoseconds > 0 and sign = -1
zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0
timeZoneSubstituteValues(
[
[new Temporal.Instant(-2n)], // Returned for NanosecondsToDays step 14, setting _intermediateNs_
[new Temporal.Instant(-4n)], // Returned for NanosecondsToDays step 18.a, setting _oneDayFartherNs_
[new Temporal.Instant(-2n)], // Returned in step 16, setting _relativeResult_
[new Temporal.Instant(-4n)], // Returned in step 21.a, setting _oneDayFarther_
],
[
TemporalHelpers.SUBSTITUTE_SKIP, // Pre-conversion in Duration.p.round
dayNs - 1, // Returned for NanosecondsToDays step 7, setting _startDateTime_
-dayNs + 1, // Returned for NanosecondsToDays step 11, setting _endDateTime_
dayNs - 1, // Returned in step 8, setting _startDateTime_
-dayNs + 1, // Returned in step 9, setting _endDateTime_
]
)
);
@ -102,3 +105,21 @@ assert.throws(RangeError, () =>
}),
"RangeError when nanoseconds > 0 and sign = -1"
);
// Step 28: 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_
[[new Temporal.Instant(2n ** 53n)]],
[]
)
);
assert.throws(RangeError, () =>
oneNsDuration.round({
relativeTo: zdt,
smallestUnit: "days",
}),
"Should throw RangeError when time zone calculates an outrageous day length"
);

View File

@ -4,18 +4,19 @@
/*---
esid: sec-temporal.duration.prototype.subtract
description: >
NanosecondsToDays can loop infinitely.
NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer
info: |
NanosecondsToDays ( nanoseconds, relativeTo )
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
...
18. Repeat, while done is false,
a. Let oneDayFartherNs be (? AddZonedDateTime((intermediateNs), relativeTo.[[TimeZone]],
relativeTo.[[Calendar]], 0, 0, 0, sign, 0, 0, 0, 0, 0, 0)).
b. Set dayLengthNs to oneDayFartherNs - intermediateNs.
c. If (nanoseconds - dayLengthNs) × sign 0, then
i. Set nanoseconds to nanoseconds - dayLengthNs.
ii. Set intermediateNs to oneDayFartherNs.
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.
@ -48,24 +49,27 @@ function createRelativeTo(count) {
return new Temporal.ZonedDateTime(0n, timeZone);
}
let zdt = createRelativeTo(200);
let zdt = createRelativeTo(50);
calls.splice(0); // Reset calls list after ZonedDateTime construction
duration.subtract(duration, {
relativeTo: zdt,
});
assert.sameValue(
calls.length,
200 + 1,
50 + 1,
"Expected duration.subtract to call getPossibleInstantsFor correct number of times"
);
zdt = createRelativeTo(300);
zdt = createRelativeTo(100);
calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
duration.subtract(duration, {
relativeTo: zdt,
});
assert.sameValue(
calls.length,
300 + 1,
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");

View File

@ -1,116 +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: >
Called abstract operation NanosecondsToDays can throw three different RangeErrors when paired with a ZonedDateTime.
info: |
6.5.7 NanosecondsToDays ( nanoseconds, relativeTo )
19. If days < 0 and sign = 1, throw a RangeError exception.
20. If days > 0 and sign = -1, throw a RangeError exception.
...
22. If nanoseconds > 0 and sign = -1, 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;
}
// NanosecondsToDays.19: 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 9
[epochInstant], // Returned for AddDuration step 10, setting _endNs_ -> DifferenceZonedDateTime _ns2_
[epochInstant], // Returned for NanosecondsToDays step 14, setting _intermediateNs_
],
[
// Behave normally in 3 calls made prior to NanosecondsToDays
TemporalHelpers.SUBSTITUTE_SKIP,
TemporalHelpers.SUBSTITUTE_SKIP,
TemporalHelpers.SUBSTITUTE_SKIP,
dayNs - 1, // Returned for NanosecondsToDays step 7, setting _startDateTime_
-dayNs + 1, // Returned for NanosecondsToDays step 11, 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,
})
);
// NanosecondsToDays.20: days > 0 and sign = -1
zdt = new Temporal.ZonedDateTime(
1n, // Set DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
[
TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for first call, AddDuration step 9
[epochInstant], // Returned for AddDuration step 10, setting _endNs_ -> DifferenceZonedDateTime _ns2_
[epochInstant], // Returned for NanosecondsToDays step 14, setting _intermediateNs_
],
[
// Behave normally in 3 calls made prior to NanosecondsToDays
TemporalHelpers.SUBSTITUTE_SKIP,
TemporalHelpers.SUBSTITUTE_SKIP,
TemporalHelpers.SUBSTITUTE_SKIP,
-dayNs + 1, // Returned for NanosecondsToDays step 7, setting _startDateTime_
dayNs - 1, // Returned for NanosecondsToDays step 11, 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,
})
);
// NanosecondsToDays.22: nanoseconds > 0 and sign = -1
zdt = new Temporal.ZonedDateTime(
0n, // Set DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
[
TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for first call, AddDuration step 9
[new Temporal.Instant(-1n)], // Returned for AddDuration step 10, setting _endNs_ -> DifferenceZonedDateTime _ns2_
[new Temporal.Instant(-2n)], // Returned for NanosecondsToDays step 14, setting _intermediateNs_
[new Temporal.Instant(-4n)], // Returned for NanosecondsToDays step 18.a, setting _oneDayFartherNs_
],
[
// Behave normally in 3 calls made prior to NanosecondsToDays
TemporalHelpers.SUBSTITUTE_SKIP,
TemporalHelpers.SUBSTITUTE_SKIP,
TemporalHelpers.SUBSTITUTE_SKIP,
dayNs - 1, // Returned for NanosecondsToDays step 7, setting _startDateTime_
-dayNs + 1, // Returned for NanosecondsToDays step 11, 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,
})
);

View File

@ -0,0 +1,141 @@
// 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"
);

View File

@ -4,18 +4,19 @@
/*---
esid: sec-temporal.duration.prototype.total
description: >
NanosecondsToDays can loop infinitely.
NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer
info: |
NanosecondsToDays ( nanoseconds, relativeTo )
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
...
18. Repeat, while done is false,
a. Let oneDayFartherNs be (? AddZonedDateTime((intermediateNs), relativeTo.[[TimeZone]],
relativeTo.[[Calendar]], 0, 0, 0, sign, 0, 0, 0, 0, 0, 0)).
b. Set dayLengthNs to oneDayFartherNs - intermediateNs.
c. If (nanoseconds - dayLengthNs) × sign 0, then
i. Set nanoseconds to nanoseconds - dayLengthNs.
ii. Set intermediateNs to oneDayFartherNs.
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.
@ -48,7 +49,7 @@ function createRelativeTo(count) {
return new Temporal.ZonedDateTime(0n, timeZone);
}
let zdt = createRelativeTo(200);
let zdt = createRelativeTo(50);
calls.splice(0); // Reset calls list after ZonedDateTime construction
duration.total({
unit: "day",
@ -56,11 +57,11 @@ duration.total({
});
assert.sameValue(
calls.length,
200 + 2,
50 + 2,
"Expected duration.total to call getPossibleInstantsFor correct number of times"
);
zdt = createRelativeTo(300);
zdt = createRelativeTo(100);
calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
duration.total({
unit: "day",
@ -68,6 +69,9 @@ duration.total({
});
assert.sameValue(
calls.length,
300 + 2,
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");

View File

@ -1,104 +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.total
description: >
Called abstract operation NanosecondsToDays can throw three different RangeErrors when paired with a ZonedDateTime.
info: |
6.5.7 NanosecondsToDays ( nanoseconds, relativeTo )
19. If days < 0 and sign = 1, throw a RangeError exception.
20. If days > 0 and sign = -1, throw a RangeError exception.
...
22. If nanoseconds > 0 and sign = -1, throw a RangeError exception.
features: [Temporal, BigInt]
includes: [temporalHelpers.js]
---*/
const oneNsDuration = Temporal.Duration.from({ nanoseconds: 1 });
const negOneNsDuration = Temporal.Duration.from({ nanoseconds: -1 });
const dayNs = 86_400_000_000_000;
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;
}
// NanosecondsToDays.19: days < 0 and sign = 1
let zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0
timeZoneSubstituteValues(
[[epochInstant]], // Returned for NanosecondsToDays step 14, setting _intermediateNs_
[
TemporalHelpers.SUBSTITUTE_SKIP, // pre-conversion in Duration.p.total
dayNs - 1, // Returned for NanosecondsToDays step 7, setting _startDateTime_
-dayNs + 1, // Returned for NanosecondsToDays step 11, setting _endDateTime_
]
)
);
assert.throws(RangeError, () =>
// Using 1ns duration _nanoseconds_ to 1 and _sign_ to 1
oneNsDuration.total({
relativeTo: zdt,
unit: "day",
}),
"RangeError when days < 0 and sign = 1"
);
// NanosecondsToDays.20: days > 0 and sign = -1
zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0
timeZoneSubstituteValues(
[[epochInstant]], // Returned for NanosecondsToDays step 14, setting _intermediateNs_
[
TemporalHelpers.SUBSTITUTE_SKIP, // pre-conversion in Duration.p.total
-dayNs + 1, // Returned for NanosecondsToDays step 7, setting _startDateTime_
dayNs - 1, // Returned for NanosecondsToDays step 11, setting _endDateTime_
]
)
);
assert.throws(RangeError, () =>
// Using -1ns duration sets _nanoseconds_ to -1 and _sign_ to -1
negOneNsDuration.total({
relativeTo: zdt,
unit: "day",
}),
"RangeError when days > 0 and sign = -1"
);
// NanosecondsToDays.22: nanoseconds > 0 and sign = -1
zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0
timeZoneSubstituteValues(
[
[new Temporal.Instant(-2n)], // Returned for NanosecondsToDays step 14, setting _intermediateNs_
[new Temporal.Instant(-4n)], // Returned for NanosecondsToDays step 18.a, setting _oneDayFartherNs_
],
[
TemporalHelpers.SUBSTITUTE_SKIP, // pre-conversion in Duration.p.total
dayNs - 1, // Returned for NanosecondsToDays step 7, setting _startDateTime_
-dayNs + 1, // Returned for NanosecondsToDays step 11, setting _endDateTime_
]
)
);
assert.throws(RangeError, () =>
// Using -1ns duration sets _nanoseconds_ to -1 and _sign_ to -1
negOneNsDuration.total({
relativeTo: zdt,
unit: "day",
}),
"RangeError when nanoseconds > 0 and sign = -1"
);

View File

@ -0,0 +1,125 @@
// 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.total
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 oneNsDuration = Temporal.Duration.from({ nanoseconds: 1 });
const negOneNsDuration = Temporal.Duration.from({ nanoseconds: -1 });
const dayNs = 86_400_000_000_000;
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(
0n, // Sets _startNs_ to 0
timeZoneSubstituteValues(
[[epochInstant]], // Returned in step 16, setting _relativeResult_
[
TemporalHelpers.SUBSTITUTE_SKIP, // Pre-conversion in Duration.p.total
dayNs - 1, // Returned in step 8, setting _startDateTime_
-dayNs + 1, // Returned in step 9, setting _endDateTime_
]
)
);
assert.throws(RangeError, () =>
// Using 1ns duration _nanoseconds_ to 1 and _sign_ to 1
oneNsDuration.total({
relativeTo: zdt,
unit: "day",
}),
"RangeError when days < 0 and sign = 1"
);
// Step 23: days > 0 and sign = -1
zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0
timeZoneSubstituteValues(
[[epochInstant]], // Returned in step 16, setting _relativeResult_
[
TemporalHelpers.SUBSTITUTE_SKIP, // Pre-conversion in Duration.p.total
-dayNs + 1, // Returned in step 8, setting _startDateTime_
dayNs - 1, // Returned in step 9, setting _endDateTime_
]
)
);
assert.throws(RangeError, () =>
// Using -1ns duration sets _nanoseconds_ to -1 and _sign_ to -1
negOneNsDuration.total({
relativeTo: zdt,
unit: "day",
}),
"RangeError when days > 0 and sign = -1"
);
// Step 25: 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_
],
[
TemporalHelpers.SUBSTITUTE_SKIP, // pre-conversion in Duration.p.total
dayNs - 1, // Returned in step 8, setting _startDateTime_
-dayNs + 1, // Returned in step 9, setting _endDateTime_
]
)
);
assert.throws(RangeError, () =>
// Using -1ns duration sets _nanoseconds_ to -1 and _sign_ to -1
negOneNsDuration.total({
relativeTo: zdt,
unit: "day",
}),
"RangeError when nanoseconds > 0 and sign = -1"
);
// Step 28: 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_
[[new Temporal.Instant(2n ** 53n)]],
[]
)
);
assert.throws(RangeError, () =>
oneNsDuration.total({
relativeTo: zdt,
unit: "days",
}),
"Should throw RangeError when time zone calculates an outrageous day length"
);

View File

@ -1,102 +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.zoneddatetime.prototype.since
description: >
Called abstract operation NanosecondsToDays can throw three different RangeErrors when paired with a ZonedDateTime.
info: |
6.5.7 NanosecondsToDays ( nanoseconds, relativeTo )
19. If days < 0 and sign = 1, throw a RangeError exception.
20. If days > 0 and sign = -1, throw a RangeError exception.
...
22. If nanoseconds > 0 and sign = -1, throw a RangeError exception.
features: [Temporal, BigInt]
includes: [temporalHelpers.js]
---*/
function timeZoneSubstituteValues(
getPossibleInstantsFor,
getOffsetNanosecondsFor
) {
const tz = new Temporal.TimeZone("UTC");
TemporalHelpers.substituteMethod(
tz,
"getPossibleInstantsFor",
getPossibleInstantsFor
);
TemporalHelpers.substituteMethod(
tz,
"getOffsetNanosecondsFor",
getOffsetNanosecondsFor
);
return tz;
}
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" };
// NanosecondsToDays.19: days < 0 and sign = 1
let start = new Temporal.ZonedDateTime(
0n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
[[epochInstant]], // Returned for NanosecondsToDays step 14, setting _intermediateNs_
[
// Behave normally in 2 calls made prior to NanosecondsToDays
TemporalHelpers.SUBSTITUTE_SKIP,
TemporalHelpers.SUBSTITUTE_SKIP,
dayNs - 1, // Returned for NanosecondsToDays step 7, setting _startDateTime_
-dayNs + 1, // Returned for NanosecondsToDays step 11, setting _endDateTime_
]
)
);
assert.throws(RangeError, () =>
start.since(
oneZDT, // Sets DifferenceZonedDateTime _ns2_
options
)
);
// NanosecondsToDays.20: days > 0 and sign = -1
start = new Temporal.ZonedDateTime(
1n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
[[epochInstant]], // Returned for NanosecondsToDays step 14, setting _intermediateNs_
[
// Behave normally in 2 calls made prior to NanosecondsToDays
TemporalHelpers.SUBSTITUTE_SKIP,
TemporalHelpers.SUBSTITUTE_SKIP,
-dayNs + 1, // Returned for NanosecondsToDays step 7, setting _startDateTime_
dayNs - 1, // Returned for NanosecondsToDays step 11, setting _endDateTime_
]
)
);
assert.throws(RangeError, () =>
start.since(
zeroZDT, // Sets DifferenceZonedDateTime _ns2_
options
)
);
// NanosecondsToDays.22: nanoseconds > 0 and sign = -1
start = new Temporal.ZonedDateTime(
1n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
[[new Temporal.Instant(-1n)]], // Returned for NanosecondsToDays step 14, setting _intermediateNs_
[
// Behave normally in 2 calls made prior to NanosecondsToDays
TemporalHelpers.SUBSTITUTE_SKIP,
TemporalHelpers.SUBSTITUTE_SKIP,
dayNs - 1, // Returned for NanosecondsToDays step 7, setting _startDateTime_
-dayNs + 1, // Returned for NanosecondsToDays step 11, setting _endDateTime_
]
)
);
assert.throws(RangeError, () =>
start.since(
zeroZDT, // Sets DifferenceZonedDateTime _ns2_
options
)
);

View File

@ -4,18 +4,19 @@
/*---
esid: sec-temporal.zoneddatetime.prototype.since
description: >
NanosecondsToDays can loop infinitely.
NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer
info: |
NanosecondsToDays ( nanoseconds, relativeTo )
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
...
18. Repeat, while done is false,
a. Let oneDayFartherNs be (? AddZonedDateTime((intermediateNs), relativeTo.[[TimeZone]],
relativeTo.[[Calendar]], 0, 0, 0, sign, 0, 0, 0, 0, 0, 0)).
b. Set dayLengthNs to oneDayFartherNs - intermediateNs.
c. If (nanoseconds - dayLengthNs) × sign 0, then
i. Set nanoseconds to nanoseconds - dayLengthNs.
ii. Set intermediateNs to oneDayFartherNs.
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.
@ -48,24 +49,27 @@ function createRelativeTo(count) {
return new Temporal.ZonedDateTime(0n, timeZone);
}
let zdt = createRelativeTo(200);
let zdt = createRelativeTo(50);
calls.splice(0); // Reset calls list after ZonedDateTime construction
zdt.since(other, {
largestUnit: "day",
});
assert.sameValue(
calls.length,
200 + 1,
50 + 1,
"Expected ZonedDateTime.since to call getPossibleInstantsFor correct number of times"
);
zdt = createRelativeTo(300);
zdt = createRelativeTo(100);
calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
zdt.since(other, {
largestUnit: "day",
});
assert.sameValue(
calls.length,
300 + 1,
100 + 1,
"Expected ZonedDateTime.since to call getPossibleInstantsFor correct number of times"
);
zdt = createRelativeTo(105);
assert.throws(RangeError, () => zdt.since(other, { largestUnit: "day" }), "105 days > 2⁵³ ns");

View File

@ -0,0 +1,123 @@
// 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.zoneddatetime.prototype.since
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]
---*/
function timeZoneSubstituteValues(
getPossibleInstantsFor,
getOffsetNanosecondsFor
) {
const tz = new Temporal.TimeZone("UTC");
TemporalHelpers.substituteMethod(
tz,
"getPossibleInstantsFor",
getPossibleInstantsFor
);
TemporalHelpers.substituteMethod(
tz,
"getOffsetNanosecondsFor",
getOffsetNanosecondsFor
);
return tz;
}
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" };
// Step 22: days < 0 and sign = 1
let start = new Temporal.ZonedDateTime(
0n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
[[epochInstant]], // Returned in step 16, setting _relativeResult_
[
// Behave normally in 2 calls made prior to NormalizedTimeDurationToDays
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, () =>
start.since(
oneZDT, // Sets DifferenceZonedDateTime _ns2_
options
)
);
// Step 23: days > 0 and sign = -1
start = new Temporal.ZonedDateTime(
1n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
[[epochInstant]], // Returned in step 16, setting _relativeResult_
[
// Behave normally in 2 calls made prior to NormalizedTimeDurationToDays
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, () =>
start.since(
zeroZDT, // Sets DifferenceZonedDateTime _ns2_
options
)
);
// Step 25: nanoseconds > 0 and sign = -1
start = new Temporal.ZonedDateTime(
1n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
[[new Temporal.Instant(-1n)]], // Returned in step 16, setting _relativeResult_
[
// Behave normally in 2 calls made prior to NormalizedTimeDurationToDays
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, () =>
start.since(
zeroZDT, // Sets DifferenceZonedDateTime _ns2_
options
)
);
// Step 28: 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)]],
[]
)
);
assert.throws(RangeError, () =>
start.since(
oneZDT,
options
),
"Should throw RangeError when time zone calculates an outrageous day length"
);

View File

@ -1,102 +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.zoneddatetime.prototype.until
description: >
Called abstract operation NanosecondsToDays can throw three different RangeErrors when paired with a ZonedDateTime.
info: |
6.5.7 NanosecondsToDays ( nanoseconds, relativeTo )
19. If days < 0 and sign = 1, throw a RangeError exception.
20. If days > 0 and sign = -1, throw a RangeError exception.
...
22. If nanoseconds > 0 and sign = -1, throw a RangeError exception.
features: [Temporal, BigInt]
includes: [temporalHelpers.js]
---*/
function timeZoneSubstituteValues(
getPossibleInstantsFor,
getOffsetNanosecondsFor
) {
const tz = new Temporal.TimeZone("UTC");
TemporalHelpers.substituteMethod(
tz,
"getPossibleInstantsFor",
getPossibleInstantsFor
);
TemporalHelpers.substituteMethod(
tz,
"getOffsetNanosecondsFor",
getOffsetNanosecondsFor
);
return tz;
}
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" };
// NanosecondsToDays.19: days < 0 and sign = 1
let start = new Temporal.ZonedDateTime(
0n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
[[epochInstant]], // Returned for NanosecondsToDays step 14, setting _intermediateNs_
[
// Behave normally in 2 calls made prior to NanosecondsToDays
TemporalHelpers.SUBSTITUTE_SKIP,
TemporalHelpers.SUBSTITUTE_SKIP,
dayNs - 1, // Returned for NanosecondsToDays step 7, setting _startDateTime_
-dayNs + 1, // Returned for NanosecondsToDays step 11, setting _endDateTime_
]
)
);
assert.throws(RangeError, () =>
start.until(
oneZDT, // Sets DifferenceZonedDateTime _ns2_
options
)
);
// NanosecondsToDays.20: days > 0 and sign = -1
start = new Temporal.ZonedDateTime(
1n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
[[epochInstant]], // Returned for NanosecondsToDays step 14, setting _intermediateNs_
[
// Behave normally in 2 calls made prior to NanosecondsToDays
TemporalHelpers.SUBSTITUTE_SKIP,
TemporalHelpers.SUBSTITUTE_SKIP,
-dayNs + 1, // Returned for NanosecondsToDays step 7, setting _startDateTime_
dayNs - 1, // Returned for NanosecondsToDays step 11, setting _endDateTime_
]
)
);
assert.throws(RangeError, () =>
start.until(
zeroZDT, // Sets DifferenceZonedDateTime _ns2_
options
)
);
// NanosecondsToDays.22: nanoseconds > 0 and sign = -1
start = new Temporal.ZonedDateTime(
1n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
[[new Temporal.Instant(-1n)]], // Returned for NanosecondsToDays step 14, setting _intermediateNs_
[
// Behave normally in 2 calls made prior to NanosecondsToDays
TemporalHelpers.SUBSTITUTE_SKIP,
TemporalHelpers.SUBSTITUTE_SKIP,
dayNs - 1, // Returned for NanosecondsToDays step 7, setting _startDateTime_
-dayNs + 1, // Returned for NanosecondsToDays step 11, setting _endDateTime_
]
)
);
assert.throws(RangeError, () =>
start.until(
zeroZDT, // Sets DifferenceZonedDateTime _ns2_
options
)
);

View File

@ -4,18 +4,19 @@
/*---
esid: sec-temporal.zoneddatetime.prototype.until
description: >
NanosecondsToDays can loop infinitely.
NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer
info: |
NanosecondsToDays ( nanoseconds, relativeTo )
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
...
18. Repeat, while done is false,
a. Let oneDayFartherNs be (? AddZonedDateTime((intermediateNs), relativeTo.[[TimeZone]],
relativeTo.[[Calendar]], 0, 0, 0, sign, 0, 0, 0, 0, 0, 0)).
b. Set dayLengthNs to oneDayFartherNs - intermediateNs.
c. If (nanoseconds - dayLengthNs) × sign 0, then
i. Set nanoseconds to nanoseconds - dayLengthNs.
ii. Set intermediateNs to oneDayFartherNs.
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.
@ -48,24 +49,27 @@ function createRelativeTo(count) {
return new Temporal.ZonedDateTime(0n, timeZone);
}
let zdt = createRelativeTo(200);
let zdt = createRelativeTo(50);
calls.splice(0); // Reset calls list after ZonedDateTime construction
zdt.until(other, {
largestUnit: "day",
});
assert.sameValue(
calls.length,
200 + 1,
50 + 1,
"Expected ZonedDateTime.until to call getPossibleInstantsFor correct number of times"
);
zdt = createRelativeTo(300);
zdt = createRelativeTo(100);
calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
zdt.until(other, {
largestUnit: "day",
});
assert.sameValue(
calls.length,
300 + 1,
100 + 1,
"Expected ZonedDateTime.until to call getPossibleInstantsFor correct number of times"
);
zdt = createRelativeTo(105);
assert.throws(RangeError, () => zdt.until(other, { largestUnit: "day" }), "105 days > 2⁵³ ns");

View File

@ -0,0 +1,123 @@
// 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.zoneddatetime.prototype.until
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]
---*/
function timeZoneSubstituteValues(
getPossibleInstantsFor,
getOffsetNanosecondsFor
) {
const tz = new Temporal.TimeZone("UTC");
TemporalHelpers.substituteMethod(
tz,
"getPossibleInstantsFor",
getPossibleInstantsFor
);
TemporalHelpers.substituteMethod(
tz,
"getOffsetNanosecondsFor",
getOffsetNanosecondsFor
);
return tz;
}
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" };
// Step 22: days < 0 and sign = 1
let start = new Temporal.ZonedDateTime(
0n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
[[epochInstant]], // Returned in step 16, setting _relativeResult_
[
// Behave normally in 2 calls made prior to NormalizedTimeDurationToDays
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, () =>
start.until(
oneZDT, // Sets DifferenceZonedDateTime _ns2_
options
)
);
// Step 23: days > 0 and sign = -1
start = new Temporal.ZonedDateTime(
1n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
[[epochInstant]], // Returned in step 16, setting _relativeResult_
[
// Behave normally in 2 calls made prior to NormalizedTimeDurationToDays
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, () =>
start.until(
zeroZDT, // Sets DifferenceZonedDateTime _ns2_
options
)
);
// Step 25: nanoseconds > 0 and sign = -1
start = new Temporal.ZonedDateTime(
1n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues(
[[new Temporal.Instant(-1n)]], // Returned in step 16, setting _relativeResult_
[
// Behave normally in 2 calls made prior to NormalizedTimeDurationToDays
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, () =>
start.until(
zeroZDT, // Sets DifferenceZonedDateTime _ns2_
options
)
);
// Step 28: 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)]],
[]
)
);
assert.throws(RangeError, () =>
start.until(
oneZDT,
options
),
"Should throw RangeError when time zone calculates an outrageous day length"
);