Temporal: Prevent arbitrary loops in NormalizedTimeDurationToDays

Adapts the tests that checked arbitrarily long loops, to now check that an
exception is thrown if the loop would happen.

Adds tests that exercise the newly added checks on return values of
getPossibleInstantsFor and getOffsetNanosecondsFor that limit UTC offset
shifts to 24 hours or less.

Also updates some step numbers in related tests.
This commit is contained in:
Philip Chimento 2023-06-20 13:11:20 +02:00 committed by Philip Chimento
parent 584048ed08
commit 3e7938c1f5
73 changed files with 2962 additions and 415 deletions

View File

@ -0,0 +1,48 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.duration.prototype.compare
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
utcInstant.add({ hours: 12 }),
utcInstant, // add a third value in case the implementation doesn't sort
];
}
}
const timeZone = new ShiftLonger24Hour();
const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
const duration1 = new Temporal.Duration(1);
const duration2 = new Temporal.Duration(2);
assert.throws(RangeError, () => Temporal.Duration.compare(duration1, duration2, {relativeTo: relativeTo}), "RangeError should be thrown");

View File

@ -0,0 +1,43 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.duration.prototype.compare
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants cannot be greater than 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9 + 1;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
}
}
const timeZone = new ShiftLonger24Hour();
const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
const duration1 = new Temporal.Duration(1);
const duration2 = new Temporal.Duration(2);
assert.throws(RangeError, () => Temporal.Duration.compare(duration1, duration2, {relativeTo: relativeTo}), "RangeError should be thrown");

View File

@ -4,72 +4,43 @@
/*--- /*---
esid: sec-temporal.duration.prototype.add esid: sec-temporal.duration.prototype.add
description: > description: >
NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer NormalizedTimeDurationToDays should not be able to loop arbitrarily.
info: | info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] ) NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
... ...
21. Repeat, while done is false, 22. If NormalizedTimeDurationSign(_oneDayLess_) × _sign_ 0, then
a. Let oneDayFarther be ? AddDaysToZonedDateTime(relativeResult.[[Instant]], a. Set _norm_ to _oneDayLess_.
relativeResult.[[DateTime]], timeZoneRec, zonedRelativeTo.[[Calendar]], sign). b. Set _relativeResult_ to _oneDayFarther_.
b. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(oneDayFarther.[[EpochNanoseconds]], c. Set _days_ to _days_ + _sign_.
relativeResult.[[EpochNanoseconds]]). d. Set _oneDayFarther_ to ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_).
c. Let oneDayLess be ? SubtractNormalizedTimeDuration(norm, dayLengthNs). e. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther.[[EpochNanoseconds]], relativeResult.[[EpochNanoseconds]]).
c. If NormalizedTimeDurationSign(oneDayLess) × sign 0, then f. If NormalizedTimeDurationSign(? SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_)) × _sign_ 0, then
i. Set norm to oneDayLess. i. Throw a *RangeError* exception.
ii. Set relativeResult to oneDayFarther.
iii. Set days to days + sign.
d. Else,
i. Set done to true.
includes: [temporalHelpers.js]
features: [Temporal] features: [Temporal]
---*/ ---*/
const calls = [];
const duration = Temporal.Duration.from({ days: 1 }); const duration = Temporal.Duration.from({ days: 1 });
function createRelativeTo(count) {
const dayLengthNs = 86400000000000n; const dayLengthNs = 86400000000000n;
const dayInstant = new Temporal.Instant(dayLengthNs); const dayInstant = new Temporal.Instant(dayLengthNs);
const substitutions = []; let calls = 0;
const timeZone = new Temporal.TimeZone("UTC"); const timeZone = new class extends Temporal.TimeZone {
// Return constant value for first _count_ calls getPossibleInstantsFor() {
TemporalHelpers.substituteMethod( calls++;
timeZone, return [dayInstant];
"getPossibleInstantsFor",
substitutions
);
substitutions.length = count;
let i = 0;
for (i = 0; i < substitutions.length; i++) {
// (this value)
substitutions[i] = [dayInstant];
}
// Record calls in calls[]
TemporalHelpers.observeMethod(calls, timeZone, "getPossibleInstantsFor");
return new Temporal.ZonedDateTime(0n, timeZone);
} }
}("UTC");
let zdt = createRelativeTo(50); const relativeTo = new Temporal.ZonedDateTime(0n, timeZone);
calls.splice(0); // Reset calls list after ZonedDateTime construction
duration.add(duration, {
relativeTo: zdt,
});
assert.sameValue(
calls.length,
50 + 1,
"Expected duration.add to call getPossibleInstantsFor correct number of times"
);
zdt = createRelativeTo(100); assert.throws(RangeError, () => duration.add(duration, { relativeTo }), "arbitrarily long loop is prevented");
calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction assert.sameValue(calls, 5, "getPossibleInstantsFor is not called in an arbitrarily long loop");
duration.add(duration, { // Expected calls:
relativeTo: zdt, // AddDuration ->
}); // AddZonedDateTime (1)
assert.sameValue( // AddZonedDateTime (2)
calls.length, // DifferenceZonedDateTime ->
100 + 1, // NormalizedTimeDurationToDays ->
"Expected duration.add to call getPossibleInstantsFor correct number of times" // AddDaysToZonedDateTime (3, step 12)
); // AddDaysToZonedDateTime (4, step 15)
// AddDaysToZonedDateTime (5, step 18.d)
zdt = createRelativeTo(107);
assert.throws(RangeError, () => duration.add(duration, { relativeTo: zdt }), "107-2 days > 2⁵³ ns");

View File

@ -0,0 +1,47 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.duration.prototype.add
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
utcInstant.add({ hours: 12 }),
utcInstant, // add a third value in case the implementation doesn't sort
];
}
}
const timeZone = new ShiftLonger24Hour();
const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
const instance = new Temporal.Duration(1, 0, 0, 1);
assert.throws(RangeError, () => instance.add(new Temporal.Duration(0, 0, 0, 0, -24), { relativeTo }), "RangeError should be thrown");

View File

@ -0,0 +1,42 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.duration.prototype.add
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants cannot be greater than 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9 + 1;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
}
}
const timeZone = new ShiftLonger24Hour();
const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
const instance = new Temporal.Duration(1, 0, 0, 1);
assert.throws(RangeError, () => instance.add(new Temporal.Duration(0, 0, 0, 0, -24), { relativeTo }), "RangeError should be thrown");

View File

@ -7,12 +7,12 @@ description: >
RangeErrors. RangeErrors.
info: | info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] ) 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] features: [Temporal, BigInt]
includes: [temporalHelpers.js] includes: [temporalHelpers.js]
---*/ ---*/
@ -39,7 +39,7 @@ function timeZoneSubstituteValues(
return tz; return tz;
} }
// Step 22: days < 0 and sign = 1 // Step 23: days < 0 and sign = 1
let zdt = new Temporal.ZonedDateTime( let zdt = new Temporal.ZonedDateTime(
-1n, // Set DifferenceZonedDateTime _ns1_ -1n, // Set DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues( timeZoneSubstituteValues(
@ -66,7 +66,7 @@ assert.throws(RangeError, () =>
"days < 0 and sign = 1" "days < 0 and sign = 1"
); );
// Step 23: days > 0 and sign = -1 // Step 24: days > 0 and sign = -1
zdt = new Temporal.ZonedDateTime( zdt = new Temporal.ZonedDateTime(
1n, // Set DifferenceZonedDateTime _ns1_ 1n, // Set DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues( timeZoneSubstituteValues(
@ -93,7 +93,7 @@ assert.throws(RangeError, () =>
"days > 0 and sign = -1" "days > 0 and sign = -1"
); );
// Step 25: nanoseconds > 0 and sign = -1 // Step 26: nanoseconds > 0 and sign = -1
zdt = new Temporal.ZonedDateTime( zdt = new Temporal.ZonedDateTime(
0n, // Set DifferenceZonedDateTime _ns1_ 0n, // Set DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues( timeZoneSubstituteValues(
@ -101,7 +101,7 @@ zdt = new Temporal.ZonedDateTime(
TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for first call, AddDuration step 15 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(-1n)], // Returned in AddDuration step 16, setting _endNs_ -> DifferenceZonedDateTime _ns2_
[new Temporal.Instant(-2n)], // Returned in step 16, setting _relativeResult_ [new Temporal.Instant(-2n)], // Returned in step 16, setting _relativeResult_
[new Temporal.Instant(-4n)], // Returned in step 21.a, setting _oneDayFarther_ [new Temporal.Instant(-4n)], // Returned in step 19, setting _oneDayFarther_
], ],
[ [
// Behave normally in 3 calls made prior to NanosecondsToDays // Behave normally in 3 calls made prior to NanosecondsToDays
@ -121,7 +121,7 @@ assert.throws(RangeError, () =>
"nanoseconds > 0 and sign = -1" "nanoseconds > 0 and sign = -1"
); );
// Step 28: day length is an unsafe integer // Step 29: day length is an unsafe integer
zdt = new Temporal.ZonedDateTime( zdt = new Temporal.ZonedDateTime(
0n, 0n,
timeZoneSubstituteValues( timeZoneSubstituteValues(
@ -129,7 +129,7 @@ zdt = new Temporal.ZonedDateTime(
TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for AddDuration step 15 TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for AddDuration step 15
TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for AddDuration step 16 TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for AddDuration step 16
TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for step 16, setting _relativeResult_ TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for step 16, setting _relativeResult_
// Returned in step 21.a, making _oneDayFarther_ 2^53 ns later than _relativeResult_ // Returned in step 19, making _oneDayFarther_ 2^53 ns later than _relativeResult_
[new Temporal.Instant(2n ** 53n + 2n * BigInt(dayNs))], [new Temporal.Instant(2n ** 53n + 2n * BigInt(dayNs))],
], ],
[] []

View File

@ -3,74 +3,42 @@
/*--- /*---
esid: sec-temporal.duration.prototype.round esid: sec-temporal.duration.prototype.round
description: > description: >
NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer NormalizedTimeDurationToDays should not be able to loop arbitrarily.
info: | info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] ) NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
... ...
21. Repeat, while done is false, 22. If NormalizedTimeDurationSign(_oneDayLess_) × _sign_ 0, then
a. Let oneDayFarther be ? AddDaysToZonedDateTime(relativeResult.[[Instant]], a. Set _norm_ to _oneDayLess_.
relativeResult.[[DateTime]], timeZoneRec, zonedRelativeTo.[[Calendar]], sign). b. Set _relativeResult_ to _oneDayFarther_.
b. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(oneDayFarther.[[EpochNanoseconds]], c. Set _days_ to _days_ + _sign_.
relativeResult.[[EpochNanoseconds]]). d. Set _oneDayFarther_ to ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_).
c. Let oneDayLess be ? SubtractNormalizedTimeDuration(norm, dayLengthNs). e. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther.[[EpochNanoseconds]], relativeResult.[[EpochNanoseconds]]).
c. If NormalizedTimeDurationSign(oneDayLess) × sign 0, then f. If NormalizedTimeDurationSign(? SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_)) × _sign_ 0, then
i. Set norm to oneDayLess. i. Throw a *RangeError* exception.
ii. Set relativeResult to oneDayFarther.
iii. Set days to days + sign.
d. Else,
i. Set done to true.
includes: [temporalHelpers.js]
features: [Temporal] features: [Temporal]
---*/ ---*/
const calls = [];
const duration = Temporal.Duration.from({ days: 1 }); const duration = Temporal.Duration.from({ days: 1 });
function createRelativeTo(count) {
const dayLengthNs = 86400000000000n; const dayLengthNs = 86400000000000n;
const dayInstant = new Temporal.Instant(dayLengthNs); const dayInstant = new Temporal.Instant(dayLengthNs);
const substitutions = []; let calls = 0;
const timeZone = new Temporal.TimeZone("UTC"); const timeZone = new class extends Temporal.TimeZone {
// Return constant value for first _count_ calls getPossibleInstantsFor() {
TemporalHelpers.substituteMethod( calls++;
timeZone, return [dayInstant];
"getPossibleInstantsFor",
substitutions
);
substitutions.length = count;
let i = 0;
for (i = 0; i < substitutions.length; i++) {
// (this value)
substitutions[i] = [dayInstant];
}
// Record calls in calls[]
TemporalHelpers.observeMethod(calls, timeZone, "getPossibleInstantsFor");
return new Temporal.ZonedDateTime(0n, timeZone);
} }
}("UTC");
let zdt = createRelativeTo(50); const relativeTo = new Temporal.ZonedDateTime(0n, timeZone);
calls.splice(0); // Reset calls list after ZonedDateTime construction
duration.round({
smallestUnit: "days",
relativeTo: zdt,
});
assert.sameValue(
calls.length,
50 + 1,
"Expected duration.round to call getPossibleInstantsFor correct number of times"
);
zdt = createRelativeTo(100); assert.throws(RangeError, () => duration.round({ smallestUnit: "days", relativeTo }), "indefinite loop is prevented");
calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction assert.sameValue(calls, 5, "getPossibleInstantsFor is not called indefinitely");
duration.round({ // Expected calls:
smallestUnit: "days", // RoundDuration -> MoveRelativeZonedDateTime -> AddZonedDateTime (1)
relativeTo: zdt, // BalanceTimeDurationRelative ->
}); // AddZonedDateTime (2)
assert.sameValue( // NormalizedTimeDurationToDays ->
calls.length, // AddDaysToZonedDateTime (3, step 12)
100 + 1, // AddDaysToZonedDateTime (4, step 15)
"Expected duration.round to call getPossibleInstantsFor correct number of times" // AddDaysToZonedDateTime (5, step 18.d)
);
zdt = createRelativeTo(107);
assert.throws(RangeError, () => duration.round({ smallestUnit: "days", relativeTo: zdt }), "107-2 days > 2⁵³ ns");

View File

@ -0,0 +1,47 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.duration.prototype.round
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
utcInstant.add({ hours: 12 }),
utcInstant, // add a third value in case the implementation doesn't sort
];
}
}
const timeZone = new ShiftLonger24Hour();
const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
const instance = new Temporal.Duration(1, 0, 0, 0, 24);
assert.throws(RangeError, () => instance.round({ largestUnit: "years", relativeTo }), "RangeError should be thrown");

View File

@ -0,0 +1,42 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.duration.prototype.round
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants cannot be greater than 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9 + 1;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
}
}
const timeZone = new ShiftLonger24Hour();
const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
const instance = new Temporal.Duration(1, 0, 0, 0, 24);
assert.throws(RangeError, () => instance.round({ largestUnit: "years", relativeTo }), "RangeError should be thrown");

View File

@ -7,12 +7,12 @@ description: >
RangeErrors. RangeErrors.
info: | info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] ) 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] features: [Temporal, BigInt]
includes: [temporalHelpers.js] includes: [temporalHelpers.js]
---*/ ---*/
@ -40,7 +40,7 @@ function timeZoneSubstituteValues(
return tz; return tz;
} }
// Step 22: days < 0 and sign = 1 // Step 23: days < 0 and sign = 1
let zdt = new Temporal.ZonedDateTime( let zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0 0n, // Sets _startNs_ to 0
timeZoneSubstituteValues( timeZoneSubstituteValues(
@ -61,7 +61,7 @@ assert.throws(RangeError, () =>
"RangeError when days < 0 and sign = 1" "RangeError when days < 0 and sign = 1"
); );
// Step 23: days > 0 and sign = -1 // Step 24: days > 0 and sign = -1
zdt = new Temporal.ZonedDateTime( zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0 0n, // Sets _startNs_ to 0
timeZoneSubstituteValues( timeZoneSubstituteValues(
@ -82,13 +82,13 @@ assert.throws(RangeError, () =>
"RangeError when days > 0 and sign = -1" "RangeError when days > 0 and sign = -1"
); );
// Step 25: nanoseconds > 0 and sign = -1 // Step 26: nanoseconds > 0 and sign = -1
zdt = new Temporal.ZonedDateTime( zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0 0n, // Sets _startNs_ to 0
timeZoneSubstituteValues( timeZoneSubstituteValues(
[ [
[new Temporal.Instant(-2n)], // Returned in step 16, setting _relativeResult_ [new Temporal.Instant(-2n)], // Returned in step 16, setting _relativeResult_
[new Temporal.Instant(-4n)], // Returned in step 21.a, setting _oneDayFarther_ [new Temporal.Instant(-4n)], // Returned in step 19, setting _oneDayFarther_
], ],
[ [
TemporalHelpers.SUBSTITUTE_SKIP, // Pre-conversion in Duration.p.round TemporalHelpers.SUBSTITUTE_SKIP, // Pre-conversion in Duration.p.round
@ -106,12 +106,12 @@ assert.throws(RangeError, () =>
"RangeError when nanoseconds > 0 and sign = -1" "RangeError when nanoseconds > 0 and sign = -1"
); );
// Step 28: day length is an unsafe integer // Step 29: day length is an unsafe integer
zdt = new Temporal.ZonedDateTime( zdt = new Temporal.ZonedDateTime(
0n, 0n,
timeZoneSubstituteValues( timeZoneSubstituteValues(
// Not called in step 16 because _days_ = 0 // Not called in step 16 because _days_ = 0
// Returned in step 21.a, making _oneDayFarther_ 2^53 ns later than _relativeResult_ // Returned in step 19, making _oneDayFarther_ 2^53 ns later than _relativeResult_
[[new Temporal.Instant(2n ** 53n)]], [[new Temporal.Instant(2n ** 53n)]],
[] []
) )

View File

@ -4,72 +4,43 @@
/*--- /*---
esid: sec-temporal.duration.prototype.subtract esid: sec-temporal.duration.prototype.subtract
description: > description: >
NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer NormalizedTimeDurationToDays should not be able to loop arbitrarily.
info: | info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] ) NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
... ...
21. Repeat, while done is false, 22. If NormalizedTimeDurationSign(_oneDayLess_) × _sign_ 0, then
a. Let oneDayFarther be ? AddDaysToZonedDateTime(relativeResult.[[Instant]], a. Set _norm_ to _oneDayLess_.
relativeResult.[[DateTime]], timeZoneRec, zonedRelativeTo.[[Calendar]], sign). b. Set _relativeResult_ to _oneDayFarther_.
b. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(oneDayFarther.[[EpochNanoseconds]], c. Set _days_ to _days_ + _sign_.
relativeResult.[[EpochNanoseconds]]). d. Set _oneDayFarther_ to ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_).
c. Let oneDayLess be ? SubtractNormalizedTimeDuration(norm, dayLengthNs). e. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther.[[EpochNanoseconds]], relativeResult.[[EpochNanoseconds]]).
c. If NormalizedTimeDurationSign(oneDayLess) × sign 0, then f. If NormalizedTimeDurationSign(? SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_)) × _sign_ 0, then
i. Set norm to oneDayLess. i. Throw a *RangeError* exception.
ii. Set relativeResult to oneDayFarther.
iii. Set days to days + sign.
d. Else,
i. Set done to true.
includes: [temporalHelpers.js]
features: [Temporal] features: [Temporal]
---*/ ---*/
const calls = [];
const duration = Temporal.Duration.from({ days: 1 }); const duration = Temporal.Duration.from({ days: 1 });
function createRelativeTo(count) {
const dayLengthNs = 86400000000000n; const dayLengthNs = 86400000000000n;
const dayInstant = new Temporal.Instant(dayLengthNs); const dayInstant = new Temporal.Instant(dayLengthNs);
const substitutions = []; let calls = 0;
const timeZone = new Temporal.TimeZone("UTC"); const timeZone = new class extends Temporal.TimeZone {
// Return constant value for first _count_ calls getPossibleInstantsFor() {
TemporalHelpers.substituteMethod( calls++;
timeZone, return [dayInstant];
"getPossibleInstantsFor",
substitutions
);
substitutions.length = count;
let i = 0;
for (i = 0; i < substitutions.length; i++) {
// (this value)
substitutions[i] = [dayInstant];
}
// Record calls in calls[]
TemporalHelpers.observeMethod(calls, timeZone, "getPossibleInstantsFor");
return new Temporal.ZonedDateTime(0n, timeZone);
} }
}("UTC");
let zdt = createRelativeTo(50); const relativeTo = new Temporal.ZonedDateTime(0n, timeZone);
calls.splice(0); // Reset calls list after ZonedDateTime construction
duration.subtract(duration, {
relativeTo: zdt,
});
assert.sameValue(
calls.length,
50 + 1,
"Expected duration.subtract to call getPossibleInstantsFor correct number of times"
);
zdt = createRelativeTo(100); assert.throws(RangeError, () => duration.subtract(duration, { relativeTo }), "indefinite loop is prevented");
calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction assert.sameValue(calls, 5, "getPossibleInstantsFor is not called indefinitely");
duration.subtract(duration, { // Expected calls:
relativeTo: zdt, // AddDuration ->
}); // AddZonedDateTime (1)
assert.sameValue( // AddZonedDateTime (2)
calls.length, // DifferenceZonedDateTime ->
100 + 1, // NormalizedTimeDurationToDays ->
"Expected duration.subtract to call getPossibleInstantsFor correct number of times" // AddDaysToZonedDateTime (3, step 12)
); // AddDaysToZonedDateTime (4, step 15)
// AddDaysToZonedDateTime (5, step 18.d)
zdt = createRelativeTo(107);
assert.throws(RangeError, () => duration.subtract(duration, { relativeTo: zdt }), "107-2 days > 2⁵³ ns");

View File

@ -0,0 +1,47 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.duration.prototype.subtract
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
utcInstant.add({ hours: 12 }),
utcInstant, // add a third value in case the implementation doesn't sort
];
}
}
const timeZone = new ShiftLonger24Hour();
const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
const instance = new Temporal.Duration(1, 0, 0, 1);
assert.throws(RangeError, () => instance.subtract(new Temporal.Duration(0, 0, 0, 0, 24), { relativeTo }), "RangeError should be thrown");

View File

@ -0,0 +1,42 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.duration.prototype.subtract
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants cannot be greater than 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9 + 1;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
}
}
const timeZone = new ShiftLonger24Hour();
const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
const instance = new Temporal.Duration(1, 0, 0, 1);
assert.throws(RangeError, () => instance.subtract(new Temporal.Duration(0, 0, 0, 0, 24), { relativeTo }), "RangeError should be thrown");

View File

@ -7,12 +7,12 @@ description: >
RangeErrors. RangeErrors.
info: | info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] ) 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] features: [Temporal, BigInt]
includes: [temporalHelpers.js] includes: [temporalHelpers.js]
---*/ ---*/
@ -39,7 +39,7 @@ function timeZoneSubstituteValues(
return tz; return tz;
} }
// Step 22: days < 0 and sign = 1 // Step 23: days < 0 and sign = 1
let zdt = new Temporal.ZonedDateTime( let zdt = new Temporal.ZonedDateTime(
-1n, // Set DifferenceZonedDateTime _ns1_ -1n, // Set DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues( timeZoneSubstituteValues(
@ -65,7 +65,7 @@ assert.throws(RangeError, () =>
}) })
); );
// Step 23: days > 0 and sign = -1 // Step 24: days > 0 and sign = -1
zdt = new Temporal.ZonedDateTime( zdt = new Temporal.ZonedDateTime(
1n, // Set DifferenceZonedDateTime _ns1_ 1n, // Set DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues( timeZoneSubstituteValues(
@ -91,7 +91,7 @@ assert.throws(RangeError, () =>
}) })
); );
// Step 25: nanoseconds > 0 and sign = -1 // Step 26: nanoseconds > 0 and sign = -1
zdt = new Temporal.ZonedDateTime( zdt = new Temporal.ZonedDateTime(
0n, // Set DifferenceZonedDateTime _ns1_ 0n, // Set DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues( timeZoneSubstituteValues(
@ -99,7 +99,7 @@ zdt = new Temporal.ZonedDateTime(
TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for first call, AddDuration step 15 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(-1n)], // Returned in AddDuration step 16, setting _endNs_ -> DifferenceZonedDateTime _ns2_
[new Temporal.Instant(-2n)], // Returned in step 16, setting _relativeResult_ [new Temporal.Instant(-2n)], // Returned in step 16, setting _relativeResult_
[new Temporal.Instant(-4n)], // Returned in step 21.a, setting _oneDayFarther_ [new Temporal.Instant(-4n)], // Returned in step 19, setting _oneDayFarther_
], ],
[ [
// Behave normally in 3 calls made prior to NanosecondsToDays // Behave normally in 3 calls made prior to NanosecondsToDays
@ -118,7 +118,7 @@ assert.throws(RangeError, () =>
}) })
); );
// Step 28: day length is an unsafe integer // Step 29: day length is an unsafe integer
zdt = new Temporal.ZonedDateTime( zdt = new Temporal.ZonedDateTime(
0n, 0n,
timeZoneSubstituteValues( timeZoneSubstituteValues(
@ -126,7 +126,7 @@ zdt = new Temporal.ZonedDateTime(
TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for AddDuration step 15 TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for AddDuration step 15
TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for AddDuration step 16 TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for AddDuration step 16
TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for step 16, setting _relativeResult_ TemporalHelpers.SUBSTITUTE_SKIP, // Behave normally for step 16, setting _relativeResult_
// Returned in step 21.a, making _oneDayFarther_ 2^53 ns later than _relativeResult_ // Returned in step 19, making _oneDayFarther_ 2^53 ns later than _relativeResult_
[new Temporal.Instant(2n ** 53n - 3n * BigInt(dayNs))], [new Temporal.Instant(2n ** 53n - 3n * BigInt(dayNs))],
], ],
[] []

View File

@ -4,74 +4,40 @@
/*--- /*---
esid: sec-temporal.duration.prototype.total esid: sec-temporal.duration.prototype.total
description: > description: >
NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer NormalizedTimeDurationToDays should not be able to loop arbitrarily.
info: | info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] ) NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
... ...
21. Repeat, while done is false, 22. If NormalizedTimeDurationSign(_oneDayLess_) × _sign_ 0, then
a. Let oneDayFarther be ? AddDaysToZonedDateTime(relativeResult.[[Instant]], a. Set _norm_ to _oneDayLess_.
relativeResult.[[DateTime]], timeZoneRec, zonedRelativeTo.[[Calendar]], sign). b. Set _relativeResult_ to _oneDayFarther_.
b. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(oneDayFarther.[[EpochNanoseconds]], c. Set _days_ to _days_ + _sign_.
relativeResult.[[EpochNanoseconds]]). d. Set _oneDayFarther_ to ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_).
c. Let oneDayLess be ? SubtractNormalizedTimeDuration(norm, dayLengthNs). e. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther.[[EpochNanoseconds]], relativeResult.[[EpochNanoseconds]]).
c. If NormalizedTimeDurationSign(oneDayLess) × sign 0, then f. If NormalizedTimeDurationSign(? SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_)) × _sign_ 0, then
i. Set norm to oneDayLess. i. Throw a *RangeError* exception.
ii. Set relativeResult to oneDayFarther.
iii. Set days to days + sign.
d. Else,
i. Set done to true.
includes: [temporalHelpers.js]
features: [Temporal] features: [Temporal]
---*/ ---*/
const calls = [];
const duration = Temporal.Duration.from({ days: 1 }); const duration = Temporal.Duration.from({ days: 1 });
function createRelativeTo(count) {
const dayLengthNs = 86400000000000n; const dayLengthNs = 86400000000000n;
const dayInstant = new Temporal.Instant(dayLengthNs); const dayInstant = new Temporal.Instant(dayLengthNs);
const substitutions = []; let calls = 0;
const timeZone = new Temporal.TimeZone("UTC"); const timeZone = new class extends Temporal.TimeZone {
// Return constant value for first _count_ calls getPossibleInstantsFor() {
TemporalHelpers.substituteMethod( calls++;
timeZone, return [dayInstant];
"getPossibleInstantsFor",
substitutions
);
substitutions.length = count;
let i = 0;
for (i = 0; i < substitutions.length; i++) {
// (this value)
substitutions[i] = [dayInstant];
}
// Record calls in calls[]
TemporalHelpers.observeMethod(calls, timeZone, "getPossibleInstantsFor");
return new Temporal.ZonedDateTime(0n, timeZone);
} }
}("UTC");
let zdt = createRelativeTo(50); const relativeTo = new Temporal.ZonedDateTime(0n, timeZone);
calls.splice(0); // Reset calls list after ZonedDateTime construction
duration.total({
unit: "day",
relativeTo: zdt,
});
assert.sameValue(
calls.length,
50 + 2,
"Expected duration.total to call getPossibleInstantsFor correct number of times"
);
zdt = createRelativeTo(100); assert.throws(RangeError, () => duration.total({ unit: "days", relativeTo }), "indefinite loop is prevented");
calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction assert.sameValue(calls, 4, "getPossibleInstantsFor is not called indefinitely");
duration.total({ // Expected calls:
unit: "day", // AddZonedDateTime (1)
relativeTo: zdt, // NormalizedTimeDurationToDays ->
}); // AddDaysToZonedDateTime (2, step 12)
assert.sameValue( // AddDaysToZonedDateTime (3, step 15)
calls.length, // AddDaysToZonedDateTime (4, step 18.d)
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

@ -70,7 +70,7 @@ function f64Repr(f) {
const tz = new (class extends Temporal.TimeZone { const tz = new (class extends Temporal.TimeZone {
getPossibleInstantsFor() { getPossibleInstantsFor() {
// Called in NormalizedTimeDurationToDays 21.a from RoundDuration 7.b. // Called in NormalizedTimeDurationToDays 19 from RoundDuration 7.b.
// Sets _result_.[[DayLength]] to 2⁵³ - 1 ns, its largest possible value // Sets _result_.[[DayLength]] to 2⁵³ - 1 ns, its largest possible value
return [new Temporal.Instant(-86400_0000_0000_000_000_000n + 2n ** 53n - 1n)]; return [new Temporal.Instant(-86400_0000_0000_000_000_000n + 2n ** 53n - 1n)];
} }

View File

@ -0,0 +1,47 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.duration.prototype.total
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
utcInstant.add({ hours: 12 }),
utcInstant, // add a third value in case the implementation doesn't sort
];
}
}
const timeZone = new ShiftLonger24Hour();
const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
const instance = new Temporal.Duration(1, 0, 0, 0, 24);
assert.throws(RangeError, () => instance.total({ unit: "days", relativeTo }), "RangeError should be thrown");

View File

@ -0,0 +1,42 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.duration.prototype.total
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants cannot be greater than 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9 + 1;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
}
}
const timeZone = new ShiftLonger24Hour();
const relativeTo = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
const instance = new Temporal.Duration(1, 0, 0, 0, 24);
assert.throws(RangeError, () => instance.total({ unit: "days", relativeTo }), "RangeError should be thrown");

View File

@ -7,12 +7,12 @@ description: >
RangeErrors. RangeErrors.
info: | info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] ) 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] features: [Temporal, BigInt]
includes: [temporalHelpers.js] includes: [temporalHelpers.js]
---*/ ---*/
@ -40,7 +40,7 @@ function timeZoneSubstituteValues(
return tz; return tz;
} }
// Step 22: days < 0 and sign = 1 // Step 23: days < 0 and sign = 1
let zdt = new Temporal.ZonedDateTime( let zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0 0n, // Sets _startNs_ to 0
timeZoneSubstituteValues( timeZoneSubstituteValues(
@ -61,7 +61,7 @@ assert.throws(RangeError, () =>
"RangeError when days < 0 and sign = 1" "RangeError when days < 0 and sign = 1"
); );
// Step 23: days > 0 and sign = -1 // Step 24: days > 0 and sign = -1
zdt = new Temporal.ZonedDateTime( zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0 0n, // Sets _startNs_ to 0
timeZoneSubstituteValues( timeZoneSubstituteValues(
@ -82,13 +82,13 @@ assert.throws(RangeError, () =>
"RangeError when days > 0 and sign = -1" "RangeError when days > 0 and sign = -1"
); );
// Step 25: nanoseconds > 0 and sign = -1 // Step 26: nanoseconds > 0 and sign = -1
zdt = new Temporal.ZonedDateTime( zdt = new Temporal.ZonedDateTime(
0n, // Sets _startNs_ to 0 0n, // Sets _startNs_ to 0
timeZoneSubstituteValues( timeZoneSubstituteValues(
[ [
[new Temporal.Instant(-2n)], // Returned in step 16, setting _relativeResult_ [new Temporal.Instant(-2n)], // Returned in step 16, setting _relativeResult_
[new Temporal.Instant(-4n)], // Returned in step 21.a, setting _oneDayFarther_ [new Temporal.Instant(-4n)], // Returned in step 19, setting _oneDayFarther_
], ],
[ [
TemporalHelpers.SUBSTITUTE_SKIP, // pre-conversion in Duration.p.total TemporalHelpers.SUBSTITUTE_SKIP, // pre-conversion in Duration.p.total
@ -106,12 +106,12 @@ assert.throws(RangeError, () =>
"RangeError when nanoseconds > 0 and sign = -1" "RangeError when nanoseconds > 0 and sign = -1"
); );
// Step 28: day length is an unsafe integer // Step 29: day length is an unsafe integer
zdt = new Temporal.ZonedDateTime( zdt = new Temporal.ZonedDateTime(
0n, 0n,
timeZoneSubstituteValues( timeZoneSubstituteValues(
// Not called in step 16 because _days_ = 0 // Not called in step 16 because _days_ = 0
// Returned in step 21.a, making _oneDayFarther_ 2^53 ns later than _relativeResult_ // Returned in step 19, making _oneDayFarther_ 2^53 ns later than _relativeResult_
[[new Temporal.Instant(2n ** 53n)]], [[new Temporal.Instant(2n ** 53n)]],
[] []
) )

View File

@ -0,0 +1,46 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.plaindate.prototype.tozoneddatetime
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants can be at most 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
let calls = 0;
class Shift24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
calls++;
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12 })];
}
}
const timeZone = new Shift24Hour();
const instance = new Temporal.PlainDate(1970, 1, 1);
instance.toZonedDateTime({ timeZone, plainTime: new Temporal.PlainTime(12) });
assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice");

View File

@ -0,0 +1,41 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.plaindate.prototype.tozoneddatetime
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants cannot be greater than 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9 + 1;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
}
}
const timeZone = new ShiftLonger24Hour();
const instance = new Temporal.PlainDate(1970, 1, 1);
assert.throws(RangeError, () => instance.toZonedDateTime({ timeZone, plainTime: new Temporal.PlainTime(12) }), "RangeError should be thrown");

View File

@ -0,0 +1,50 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.plaindate.prototype.tozoneddatetime
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
let calls = 0;
class Shift24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
calls++;
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12 }),
utcInstant.add({ hours: 12 })
];
}
}
const timeZone = new Shift24Hour();
const instance = new Temporal.PlainDate(1970, 1, 1);
instance.toZonedDateTime({ timeZone, plainTime: new Temporal.PlainTime(12) });
assert(calls >= 1, "getPossibleInstantsFor should be called at least once");

View File

@ -0,0 +1,46 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.plaindate.prototype.tozoneddatetime
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
utcInstant.add({ hours: 12 }),
utcInstant, // add a third value in case the implementation doesn't sort
];
}
}
const timeZone = new ShiftLonger24Hour();
const instance = new Temporal.PlainDate(1970, 1, 1);
assert.throws(RangeError, () => instance.toZonedDateTime({ timeZone, plainTime: new Temporal.PlainTime(12) }), "RangeError should be thrown");

View File

@ -0,0 +1,46 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.plaindatetime.prototype.tozoneddatetime
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants can be at most 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
let calls = 0;
class Shift24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
calls++;
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12 })];
}
}
const timeZone = new Shift24Hour();
const instance = new Temporal.PlainDateTime(1970, 1, 1, 12);
instance.toZonedDateTime(timeZone);
assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice");

View File

@ -0,0 +1,41 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.plaindatetime.prototype.tozoneddatetime
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants cannot be greater than 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9 + 1;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
}
}
const timeZone = new ShiftLonger24Hour();
const instance = new Temporal.PlainDateTime(1970, 1, 1, 12);
assert.throws(RangeError, () => instance.toZonedDateTime(timeZone), "RangeError should be thrown");

View File

@ -0,0 +1,50 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.plaindatetime.prototype.tozoneddatetime
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
let calls = 0;
class Shift24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
calls++;
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12 }),
utcInstant.add({ hours: 12 })
];
}
}
const timeZone = new Shift24Hour();
const instance = new Temporal.PlainDateTime(1970, 1, 1, 12);
instance.toZonedDateTime(timeZone);
assert(calls >= 1, "getPossibleInstantsFor should be called at least once");

View File

@ -0,0 +1,46 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.plaindatetime.prototype.tozoneddatetime
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
utcInstant.add({ hours: 12 }),
utcInstant, // add a third value in case the implementation doesn't sort
];
}
}
const timeZone = new ShiftLonger24Hour();
const instance = new Temporal.PlainDateTime(1970, 1, 1, 12);
assert.throws(RangeError, () => instance.toZonedDateTime(timeZone), "RangeError should be thrown");

View File

@ -0,0 +1,46 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.plaintime.prototype.tozoneddatetime
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants can be at most 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
let calls = 0;
class Shift24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
calls++;
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12 })];
}
}
const timeZone = new Shift24Hour();
const instance = new Temporal.PlainTime(12);
instance.toZonedDateTime({ timeZone, plainDate: new Temporal.PlainDate(1970, 1, 1) });
assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice");

View File

@ -0,0 +1,41 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.plaintime.prototype.tozoneddatetime
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants cannot be greater than 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9 + 1;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
}
}
const timeZone = new ShiftLonger24Hour();
const instance = new Temporal.PlainTime(12);
assert.throws(RangeError, () => instance.toZonedDateTime({ timeZone, plainDate: new Temporal.PlainDate(1970, 1, 1) }), "RangeError should be thrown");

View File

@ -0,0 +1,50 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.plaintime.prototype.tozoneddatetime
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
let calls = 0;
class Shift24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
calls++;
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12 }),
utcInstant.add({ hours: 12 })
];
}
}
const timeZone = new Shift24Hour();
const instance = new Temporal.PlainTime(12);
instance.toZonedDateTime({ timeZone, plainDate: new Temporal.PlainDate(1970, 1, 1) });
assert(calls >= 1, "getPossibleInstantsFor should be called at least once");

View File

@ -0,0 +1,46 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.plaintime.prototype.tozoneddatetime
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
utcInstant.add({ hours: 12 }),
utcInstant, // add a third value in case the implementation doesn't sort
];
}
}
const timeZone = new ShiftLonger24Hour();
const instance = new Temporal.PlainTime(12);
assert.throws(RangeError, () => instance.toZonedDateTime({ timeZone, plainDate: new Temporal.PlainDate(1970, 1, 1) }), "RangeError should be thrown");

View File

@ -0,0 +1,48 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.timezone.prototype.getinstantfor
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants can be at most 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
let calls = 0;
class Shift24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
calls++;
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12 })];
}
}
const timeZone = new Shift24Hour();
for (const disambiguation of ["earlier", "later", "compatible"]) {
timeZone.getInstantFor(new Temporal.PlainDateTime(1970, 1, 1, 12), { disambiguation });
assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice");
calls = 0;
}

View File

@ -0,0 +1,43 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.timezone.prototype.getinstantfor
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants cannot be greater than 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9 + 1;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
}
}
const timeZone = new ShiftLonger24Hour();
for (const disambiguation of ["earlier", "later", "compatible"]) {
assert.throws(RangeError, () => timeZone.getInstantFor(new Temporal.PlainDateTime(1970, 1, 1, 12), { disambiguation }), "RangeError should be thrown");
}

View File

@ -0,0 +1,52 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.timezone.prototype.getinstantfor
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
let calls = 0;
class Shift24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
calls++;
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12 }),
utcInstant.add({ hours: 12 })
];
}
}
const timeZone = new Shift24Hour();
for (const disambiguation of ["earlier", "later", "compatible"]) {
timeZone.getInstantFor(new Temporal.PlainDateTime(1970, 1, 1, 12), { disambiguation });
assert(calls >= 1, "getPossibleInstantsFor should be called at least once");
calls = 0;
}

View File

@ -0,0 +1,47 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.timezone.prototype.getinstantfor
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
utcInstant.add({ hours: 12 }),
utcInstant, // add a third value in case the implementation doesn't sort
];
}
}
const timeZone = new ShiftLonger24Hour();
for (const disambiguation of ["earlier", "later", "compatible"]) {
assert.throws(RangeError, () => timeZone.getInstantFor(new Temporal.PlainDateTime(1970, 1, 1, 12), { disambiguation }), "RangeError should be thrown");
}

View File

@ -0,0 +1,48 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.compare
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
utcInstant.add({ hours: 12 }),
utcInstant, // add a third value in case the implementation doesn't sort
];
}
}
const timeZone = new ShiftLonger24Hour();
const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
const datetime = new Temporal.ZonedDateTime(0n, timeZone);
assert.throws(RangeError, () => Temporal.ZonedDateTime.compare(arg, datetime), "RangeError should be thrown (first argument)");
assert.throws(RangeError, () => Temporal.ZonedDateTime.compare(datetime, arg), "RangeError should be thrown (second argument)");

View File

@ -0,0 +1,43 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.compare
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants cannot be greater than 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9 + 1;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
}
}
const timeZone = new ShiftLonger24Hour();
const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
const datetime = new Temporal.ZonedDateTime(0n, timeZone);
assert.throws(RangeError, () => Temporal.ZonedDateTime.compare(arg, datetime), "RangeError should be thrown (first argument)");
assert.throws(RangeError, () => Temporal.ZonedDateTime.compare(datetime, arg), "RangeError should be thrown (second argument)");

View File

@ -0,0 +1,46 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.from
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
utcInstant.add({ hours: 12 }),
utcInstant, // add a third value in case the implementation doesn't sort
];
}
}
const timeZone = new ShiftLonger24Hour();
const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
assert.throws(RangeError, () => Temporal.ZonedDateTime.from(arg), "RangeError should be thrown");

View File

@ -0,0 +1,41 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.from
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants cannot be greater than 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9 + 1;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
}
}
const timeZone = new ShiftLonger24Hour();
const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
assert.throws(RangeError, () => Temporal.ZonedDateTime.from(arg), "RangeError should be thrown");

View File

@ -0,0 +1,47 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.equals
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
utcInstant.add({ hours: 12 }),
utcInstant, // add a third value in case the implementation doesn't sort
];
}
}
const timeZone = new ShiftLonger24Hour();
const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
const instance = new Temporal.ZonedDateTime(0n, timeZone);
assert.throws(RangeError, () => instance.equals(arg), "RangeError should be thrown");

View File

@ -0,0 +1,42 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.equals
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants cannot be greater than 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9 + 1;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
}
}
const timeZone = new ShiftLonger24Hour();
const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
const instance = new Temporal.ZonedDateTime(0n, timeZone);
assert.throws(RangeError, () => instance.equals(arg), "RangeError should be thrown");

View File

@ -0,0 +1,46 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.hoursinday
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants can be at most 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
let calls = 0;
class Shift24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
calls++;
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12 })];
}
}
const timeZone = new Shift24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
instance.hoursInDay;
assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice");

View File

@ -0,0 +1,41 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.hoursinday
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants cannot be greater than 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9 + 1;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
}
}
const timeZone = new ShiftLonger24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
assert.throws(RangeError, () => instance.hoursInDay, "RangeError should be thrown");

View File

@ -0,0 +1,50 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.hoursinday
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
let calls = 0;
class Shift24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
calls++;
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12 }),
utcInstant.add({ hours: 12 })
];
}
}
const timeZone = new Shift24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
instance.hoursInDay;
assert(calls >= 1, "getPossibleInstantsFor should be called at least once");

View File

@ -0,0 +1,46 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.hoursinday
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
utcInstant.add({ hours: 12 }),
utcInstant, // add a third value in case the implementation doesn't sort
];
}
}
const timeZone = new ShiftLonger24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
assert.throws(RangeError, () => instance.hoursInDay, "RangeError should be thrown");

View File

@ -0,0 +1,46 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.round
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants can be at most 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
let calls = 0;
class Shift24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
calls++;
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12 })];
}
}
const timeZone = new Shift24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
instance.round({ smallestUnit: "hours" });
assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice");

View File

@ -0,0 +1,41 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.round
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants cannot be greater than 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9 + 1;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
}
}
const timeZone = new ShiftLonger24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
assert.throws(RangeError, () => instance.round({ smallestUnit: "hours" }), "RangeError should be thrown");

View File

@ -0,0 +1,50 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.round
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
let calls = 0;
class Shift24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
calls++;
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12 }),
utcInstant.add({ hours: 12 })
];
}
}
const timeZone = new Shift24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
instance.round({ smallestUnit: "hours" });
assert(calls >= 1, "getPossibleInstantsFor should be called at least once");

View File

@ -0,0 +1,46 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.round
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
utcInstant.add({ hours: 12 }),
utcInstant, // add a third value in case the implementation doesn't sort
];
}
}
const timeZone = new ShiftLonger24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
assert.throws(RangeError, () => instance.round({ smallestUnit: "hours" }), "RangeError should be thrown");

View File

@ -0,0 +1,47 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.since
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
utcInstant.add({ hours: 12 }),
utcInstant, // add a third value in case the implementation doesn't sort
];
}
}
const timeZone = new ShiftLonger24Hour();
const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
const instance = new Temporal.ZonedDateTime(0n, timeZone);
assert.throws(RangeError, () => instance.since(arg), "RangeError should be thrown");

View File

@ -0,0 +1,42 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.since
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants cannot be greater than 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9 + 1;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
}
}
const timeZone = new ShiftLonger24Hour();
const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
const instance = new Temporal.ZonedDateTime(0n, timeZone);
assert.throws(RangeError, () => instance.since(arg), "RangeError should be thrown");

View File

@ -4,72 +4,38 @@
/*--- /*---
esid: sec-temporal.zoneddatetime.prototype.since esid: sec-temporal.zoneddatetime.prototype.since
description: > description: >
NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer NormalizedTimeDurationToDays should not be able to loop arbitrarily.
info: | info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] ) NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
... ...
21. Repeat, while done is false, 22. If NormalizedTimeDurationSign(_oneDayLess_) × _sign_ 0, then
a. Let oneDayFarther be ? AddDaysToZonedDateTime(relativeResult.[[Instant]], a. Set _norm_ to _oneDayLess_.
relativeResult.[[DateTime]], timeZoneRec, zonedRelativeTo.[[Calendar]], sign). b. Set _relativeResult_ to _oneDayFarther_.
b. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(oneDayFarther.[[EpochNanoseconds]], c. Set _days_ to _days_ + _sign_.
relativeResult.[[EpochNanoseconds]]). d. Set _oneDayFarther_ to ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_).
c. Let oneDayLess be ? SubtractNormalizedTimeDuration(norm, dayLengthNs). e. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther.[[EpochNanoseconds]], relativeResult.[[EpochNanoseconds]]).
c. If NormalizedTimeDurationSign(oneDayLess) × sign 0, then f. If NormalizedTimeDurationSign(? SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_)) × _sign_ 0, then
i. Set norm to oneDayLess. i. Throw a *RangeError* exception.
ii. Set relativeResult to oneDayFarther.
iii. Set days to days + sign.
d. Else,
i. Set done to true.
includes: [temporalHelpers.js]
features: [Temporal] features: [Temporal]
---*/ ---*/
const calls = [];
const dayLengthNs = 86400000000000n; 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 zdt = new Temporal.ZonedDateTime(0n, timeZone);
const other = new Temporal.ZonedDateTime(dayLengthNs, "UTC", "iso8601"); const other = new Temporal.ZonedDateTime(dayLengthNs, "UTC", "iso8601");
function createRelativeTo(count) { assert.throws(RangeError, () => zdt.since(other, { largestUnit: "day" }), "indefinite loop is prevented");
const dayInstant = new Temporal.Instant(dayLengthNs); assert.sameValue(calls, 3, "getPossibleInstantsFor is not called indefinitely");
const substitutions = []; // Expected calls:
const timeZone = new Temporal.TimeZone("UTC"); // DifferenceZonedDateTime -> NormalizedTimeDurationToDays ->
// Return constant value for first _count_ calls // AddDaysToZonedDateTime (3, step 12)
TemporalHelpers.substituteMethod( // AddDaysToZonedDateTime (4, step 15)
timeZone, // AddDaysToZonedDateTime (5, step 18.d)
"getPossibleInstantsFor",
substitutions
);
substitutions.length = count;
let i = 0;
for (i = 0; i < substitutions.length; i++) {
// (this value)
substitutions[i] = [dayInstant];
}
// Record calls in calls[]
TemporalHelpers.observeMethod(calls, timeZone, "getPossibleInstantsFor");
return new Temporal.ZonedDateTime(0n, timeZone);
}
let zdt = createRelativeTo(50);
calls.splice(0); // Reset calls list after ZonedDateTime construction
zdt.since(other, {
largestUnit: "day",
});
assert.sameValue(
calls.length,
50 + 1,
"Expected ZonedDateTime.since to call getPossibleInstantsFor correct number of times"
);
zdt = createRelativeTo(100);
calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
zdt.since(other, {
largestUnit: "day",
});
assert.sameValue(
calls.length,
100 + 1,
"Expected ZonedDateTime.since to call getPossibleInstantsFor correct number of times"
);
zdt = createRelativeTo(105);
assert.throws(RangeError, () => zdt.since(other, { largestUnit: "day" }), "105 days > 2⁵³ ns");

View File

@ -7,12 +7,12 @@ description: >
RangeErrors. RangeErrors.
info: | info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] ) 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] features: [Temporal, BigInt]
includes: [temporalHelpers.js] includes: [temporalHelpers.js]
---*/ ---*/
@ -41,7 +41,7 @@ const oneZDT = new Temporal.ZonedDateTime(1n, "UTC");
const epochInstant = new Temporal.Instant(0n); const epochInstant = new Temporal.Instant(0n);
const options = { largestUnit: "days" }; const options = { largestUnit: "days" };
// Step 22: days < 0 and sign = 1 // Step 23: days < 0 and sign = 1
let start = new Temporal.ZonedDateTime( let start = new Temporal.ZonedDateTime(
0n, // Sets DifferenceZonedDateTime _ns1_ 0n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues( timeZoneSubstituteValues(
@ -62,7 +62,7 @@ assert.throws(RangeError, () =>
) )
); );
// Step 23: days > 0 and sign = -1 // Step 24: days > 0 and sign = -1
start = new Temporal.ZonedDateTime( start = new Temporal.ZonedDateTime(
1n, // Sets DifferenceZonedDateTime _ns1_ 1n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues( timeZoneSubstituteValues(
@ -83,7 +83,7 @@ assert.throws(RangeError, () =>
) )
); );
// Step 25: nanoseconds > 0 and sign = -1 // Step 26: nanoseconds > 0 and sign = -1
start = new Temporal.ZonedDateTime( start = new Temporal.ZonedDateTime(
1n, // Sets DifferenceZonedDateTime _ns1_ 1n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues( timeZoneSubstituteValues(
@ -104,12 +104,12 @@ assert.throws(RangeError, () =>
) )
); );
// Step 28: day length is an unsafe integer // Step 29: day length is an unsafe integer
start = new Temporal.ZonedDateTime( start = new Temporal.ZonedDateTime(
0n, 0n,
timeZoneSubstituteValues( timeZoneSubstituteValues(
// Not called in step 16 because _days_ = 0 // Not called in step 16 because _days_ = 0
// Returned in step 21.a, making _oneDayFarther_ 2^53 ns later than _relativeResult_ // Returned in step 19, making _oneDayFarther_ 2^53 ns later than _relativeResult_
[[new Temporal.Instant(2n ** 53n)]], [[new Temporal.Instant(2n ** 53n)]],
[] []
) )

View File

@ -0,0 +1,46 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.startofday
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants can be at most 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
let calls = 0;
class Shift24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 0n
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
calls++;
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12 })];
}
}
const timeZone = new Shift24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
instance.startOfDay();
assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice");

View File

@ -0,0 +1,41 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.startofday
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants cannot be greater than 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 0n
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9 + 1;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
}
}
const timeZone = new ShiftLonger24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
assert.throws(RangeError, () => instance.startOfDay(), "RangeError should be thrown");

View File

@ -0,0 +1,50 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.startofday
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
let calls = 0;
class Shift24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
calls++;
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12 }),
utcInstant.add({ hours: 12 })
];
}
}
const timeZone = new Shift24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
instance.startOfDay();
assert(calls >= 1, "getPossibleInstantsFor should be called at least once");

View File

@ -0,0 +1,46 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.startofday
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
utcInstant.add({ hours: 12 }),
utcInstant, // add a third value in case the implementation doesn't sort
];
}
}
const timeZone = new ShiftLonger24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
assert.throws(RangeError, () => instance.startOfDay(), "RangeError should be thrown");

View File

@ -0,0 +1,47 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.until
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
utcInstant.add({ hours: 12 }),
utcInstant, // add a third value in case the implementation doesn't sort
];
}
}
const timeZone = new ShiftLonger24Hour();
const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
const instance = new Temporal.ZonedDateTime(0n, timeZone);
assert.throws(RangeError, () => instance.until(arg), "RangeError should be thrown");

View File

@ -0,0 +1,42 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.until
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants cannot be greater than 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9 + 1;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
}
}
const timeZone = new ShiftLonger24Hour();
const arg = { year: 1970, month: 1, day: 1, hour: 12, timeZone };
const instance = new Temporal.ZonedDateTime(0n, timeZone);
assert.throws(RangeError, () => instance.until(arg), "RangeError should be thrown");

View File

@ -4,72 +4,38 @@
/*--- /*---
esid: sec-temporal.zoneddatetime.prototype.until esid: sec-temporal.zoneddatetime.prototype.until
description: > description: >
NormalizedTimeDurationToDays can loop arbitrarily up to max safe integer NormalizedTimeDurationToDays should not be able to loop arbitrarily.
info: | info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] ) NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDatetime ] )
... ...
21. Repeat, while done is false, 22. If NormalizedTimeDurationSign(_oneDayLess_) × _sign_ 0, then
a. Let oneDayFarther be ? AddDaysToZonedDateTime(relativeResult.[[Instant]], a. Set _norm_ to _oneDayLess_.
relativeResult.[[DateTime]], timeZoneRec, zonedRelativeTo.[[Calendar]], sign). b. Set _relativeResult_ to _oneDayFarther_.
b. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(oneDayFarther.[[EpochNanoseconds]], c. Set _days_ to _days_ + _sign_.
relativeResult.[[EpochNanoseconds]]). d. Set _oneDayFarther_ to ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_).
c. Let oneDayLess be ? SubtractNormalizedTimeDuration(norm, dayLengthNs). e. Set dayLengthNs to NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther.[[EpochNanoseconds]], relativeResult.[[EpochNanoseconds]]).
c. If NormalizedTimeDurationSign(oneDayLess) × sign 0, then f. If NormalizedTimeDurationSign(? SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_)) × _sign_ 0, then
i. Set norm to oneDayLess. i. Throw a *RangeError* exception.
ii. Set relativeResult to oneDayFarther.
iii. Set days to days + sign.
d. Else,
i. Set done to true.
includes: [temporalHelpers.js]
features: [Temporal] features: [Temporal]
---*/ ---*/
const calls = [];
const dayLengthNs = 86400000000000n; 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 zdt = new Temporal.ZonedDateTime(0n, timeZone);
const other = new Temporal.ZonedDateTime(dayLengthNs, "UTC", "iso8601"); const other = new Temporal.ZonedDateTime(dayLengthNs, "UTC", "iso8601");
function createRelativeTo(count) { assert.throws(RangeError, () => zdt.until(other, { largestUnit: "day" }), "indefinite loop is prevented");
const dayInstant = new Temporal.Instant(dayLengthNs); assert.sameValue(calls, 3, "getPossibleInstantsFor is not called indefinitely");
const substitutions = []; // Expected calls:
const timeZone = new Temporal.TimeZone("UTC"); // DifferenceZonedDateTime -> NormalizedTimeDurationToDays ->
// Return constant value for first _count_ calls // AddDaysToZonedDateTime (3, step 12)
TemporalHelpers.substituteMethod( // AddDaysToZonedDateTime (4, step 15)
timeZone, // AddDaysToZonedDateTime (5, step 18.d)
"getPossibleInstantsFor",
substitutions
);
substitutions.length = count;
let i = 0;
for (i = 0; i < substitutions.length; i++) {
// (this value)
substitutions[i] = [dayInstant];
}
// Record calls in calls[]
TemporalHelpers.observeMethod(calls, timeZone, "getPossibleInstantsFor");
return new Temporal.ZonedDateTime(0n, timeZone);
}
let zdt = createRelativeTo(50);
calls.splice(0); // Reset calls list after ZonedDateTime construction
zdt.until(other, {
largestUnit: "day",
});
assert.sameValue(
calls.length,
50 + 1,
"Expected ZonedDateTime.until to call getPossibleInstantsFor correct number of times"
);
zdt = createRelativeTo(100);
calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
zdt.until(other, {
largestUnit: "day",
});
assert.sameValue(
calls.length,
100 + 1,
"Expected ZonedDateTime.until to call getPossibleInstantsFor correct number of times"
);
zdt = createRelativeTo(105);
assert.throws(RangeError, () => zdt.until(other, { largestUnit: "day" }), "105 days > 2⁵³ ns");

View File

@ -7,12 +7,12 @@ description: >
RangeErrors. RangeErrors.
info: | info: |
NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] ) 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] features: [Temporal, BigInt]
includes: [temporalHelpers.js] includes: [temporalHelpers.js]
---*/ ---*/
@ -41,7 +41,7 @@ const oneZDT = new Temporal.ZonedDateTime(1n, "UTC");
const epochInstant = new Temporal.Instant(0n); const epochInstant = new Temporal.Instant(0n);
const options = { largestUnit: "days" }; const options = { largestUnit: "days" };
// Step 22: days < 0 and sign = 1 // Step 23: days < 0 and sign = 1
let start = new Temporal.ZonedDateTime( let start = new Temporal.ZonedDateTime(
0n, // Sets DifferenceZonedDateTime _ns1_ 0n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues( timeZoneSubstituteValues(
@ -62,7 +62,7 @@ assert.throws(RangeError, () =>
) )
); );
// Step 23: days > 0 and sign = -1 // Step 24: days > 0 and sign = -1
start = new Temporal.ZonedDateTime( start = new Temporal.ZonedDateTime(
1n, // Sets DifferenceZonedDateTime _ns1_ 1n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues( timeZoneSubstituteValues(
@ -83,7 +83,7 @@ assert.throws(RangeError, () =>
) )
); );
// Step 25: nanoseconds > 0 and sign = -1 // Step 26: nanoseconds > 0 and sign = -1
start = new Temporal.ZonedDateTime( start = new Temporal.ZonedDateTime(
1n, // Sets DifferenceZonedDateTime _ns1_ 1n, // Sets DifferenceZonedDateTime _ns1_
timeZoneSubstituteValues( timeZoneSubstituteValues(
@ -104,12 +104,12 @@ assert.throws(RangeError, () =>
) )
); );
// Step 28: day length is an unsafe integer // Step 29: day length is an unsafe integer
start = new Temporal.ZonedDateTime( start = new Temporal.ZonedDateTime(
0n, 0n,
timeZoneSubstituteValues( timeZoneSubstituteValues(
// Not called in step 16 because _days_ = 0 // Not called in step 16 because _days_ = 0
// Returned in step 21.a, making _oneDayFarther_ 2^53 ns later than _relativeResult_ // Returned in step 19, making _oneDayFarther_ 2^53 ns later than _relativeResult_
[[new Temporal.Instant(2n ** 53n)]], [[new Temporal.Instant(2n ** 53n)]],
[] []
) )

View File

@ -0,0 +1,49 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.with
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants can be at most 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
let calls = 0;
class Shift24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 0n;
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
calls++;
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12 })];
}
}
const timeZone = new Shift24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
for (const disambiguation of ["earlier", "later", "compatible"]) {
instance.with({ day: 1 }, { disambiguation });
assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice");
calls = 0;
}

View File

@ -0,0 +1,43 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.with
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants cannot be greater than 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 0n;
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9 + 1;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
}
}
const timeZone = new ShiftLonger24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
for (const disambiguation of ["earlier", "later", "compatible"]) {
assert.throws(RangeError, () => instance.with({ day: 1 }, { disambiguation }), "RangeError should be thrown");
}

View File

@ -0,0 +1,53 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.with
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
let calls = 0;
class Shift24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
calls++;
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12 }),
utcInstant.add({ hours: 12 })
];
}
}
const timeZone = new Shift24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
for (const disambiguation of ["earlier", "later", "compatible"]) {
instance.with({ day: 1 }, { disambiguation });
assert(calls >= 1, "getPossibleInstantsFor should be called at least once");
calls = 0;
}

View File

@ -0,0 +1,48 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.with
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
utcInstant.add({ hours: 12 }),
utcInstant, // add a third value in case the implementation doesn't sort
];
}
}
const timeZone = new ShiftLonger24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
for (const disambiguation of ["earlier", "later", "compatible"]) {
assert.throws(RangeError, () => instance.with({ day: 1 }, { disambiguation }), "RangeError should be thrown");
}

View File

@ -0,0 +1,46 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.withplaindate
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants can be at most 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
let calls = 0;
class Shift24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
calls++;
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12 })];
}
}
const timeZone = new Shift24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
instance.withPlainDate(new Temporal.PlainDate(1970, 1, 1));
assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice");

View File

@ -0,0 +1,41 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.withplaindate
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants cannot be greater than 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 12n * 3600n * 1_000_000_000n; // 1970-01-01T12:00Z
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9 + 1;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
}
}
const timeZone = new ShiftLonger24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
assert.throws(RangeError, () => instance.withPlainDate(new Temporal.PlainDate(1970, 1, 1)), "RangeError should be thrown");

View File

@ -0,0 +1,50 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.withplaindate
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
let calls = 0;
class Shift24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
calls++;
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12 }),
utcInstant.add({ hours: 12 })
];
}
}
const timeZone = new Shift24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
instance.withPlainDate(new Temporal.PlainDate(1970, 1, 1));
assert(calls >= 1, "getPossibleInstantsFor should be called at least once");

View File

@ -0,0 +1,46 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.withplaindate
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
utcInstant.add({ hours: 12 }),
utcInstant, // add a third value in case the implementation doesn't sort
];
}
}
const timeZone = new ShiftLonger24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
assert.throws(RangeError, () => instance.withPlainDate(new Temporal.PlainDate(1970, 1, 1)), "RangeError should be thrown");

View File

@ -0,0 +1,46 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.withplaintime
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants can be at most 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
let calls = 0;
class Shift24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 0n
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
calls++;
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12 })];
}
}
const timeZone = new Shift24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
instance.withPlainTime();
assert(calls >= 2, "getOffsetNanosecondsFor should be called at least twice");

View File

@ -0,0 +1,41 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.withplaintime
description: >
UTC offset shift returned by adjacent invocations of getOffsetNanosecondsFor
in DisambiguatePossibleInstants cannot be greater than 24 hours.
features: [Temporal]
info: |
DisambiguatePossibleInstants:
18. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
_shiftEpochNs = 0n
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
if (instant.epochNanoseconds < this._shiftEpochNs) return -12 * 3600e9;
return 12 * 3600e9 + 1;
}
getPossibleInstantsFor(plainDateTime) {
const [utcInstant] = super.getPossibleInstantsFor(plainDateTime);
const { year, month, day } = plainDateTime;
if (year < 1970) return [utcInstant.subtract({ hours: 12 })];
if (year === 1970 && month === 1 && day === 1) return [];
return [utcInstant.add({ hours: 12, nanoseconds: 1 })];
}
}
const timeZone = new ShiftLonger24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
assert.throws(RangeError, () => instance.withPlainTime(), "RangeError should be thrown");

View File

@ -0,0 +1,50 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.withplaintime
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
let calls = 0;
class Shift24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
calls++;
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12 }),
utcInstant.add({ hours: 12 })
];
}
}
const timeZone = new Shift24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
instance.withPlainTime();
assert(calls >= 1, "getPossibleInstantsFor should be called at least once");

View File

@ -0,0 +1,46 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.zoneddatetime.prototype.withplaintime
description: >
UTC offset shift returned by getPossibleInstantsFor can be at most 24 hours.
features: [Temporal]
info: |
GetPossibleInstantsFor:
5.b.i. Let _numResults_ be _list_'s length.
ii. If _numResults_ > 1, then
1. Let _epochNs_ be a new empty List.
2. For each value _instant_ in list, do
a. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_.
3. Let _min_ be the least element of the List _epochNs_.
4. Let _max_ be the greatest element of the List _epochNs_.
5. If abs((_max_ - _min_)) > nsPerDay, throw a *RangeError* exception.
---*/
class ShiftLonger24Hour extends Temporal.TimeZone {
id = 'TestTimeZone';
constructor() {
super('UTC');
}
getOffsetNanosecondsFor(instant) {
return 0;
}
getPossibleInstantsFor(plainDateTime) {
const utc = new Temporal.TimeZone("UTC");
const [utcInstant] = utc.getPossibleInstantsFor(plainDateTime);
return [
utcInstant.subtract({ hours: 12, nanoseconds: 1 }),
utcInstant.add({ hours: 12 }),
utcInstant, // add a third value in case the implementation doesn't sort
];
}
}
const timeZone = new ShiftLonger24Hour();
const instance = new Temporal.ZonedDateTime(0n, timeZone);
assert.throws(RangeError, () => instance.withPlainTime(), "RangeError should be thrown");