mirror of https://github.com/tc39/test262.git
Temporal: Fast-path dateUntil() when difference largest unit is days
This removes several loopholes where it was possible to return particular values from user calls that would cause infinite loops, or calculate zero-length days.
This commit is contained in:
parent
7a3944c0cc
commit
9b8d9cf66c
|
@ -46,13 +46,13 @@ TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
|||
years: ["year"],
|
||||
months: ["month"],
|
||||
weeks: ["week"],
|
||||
days: ["day"],
|
||||
hours: ["day"],
|
||||
minutes: ["day"],
|
||||
seconds: ["day"],
|
||||
milliseconds: ["day"],
|
||||
microseconds: ["day"],
|
||||
nanoseconds: ["day"]
|
||||
days: [],
|
||||
hours: [],
|
||||
minutes: [],
|
||||
seconds: [],
|
||||
milliseconds: [],
|
||||
microseconds: [],
|
||||
nanoseconds: []
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -64,10 +64,10 @@ TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
|||
one.add(two, { relativeTo, largestUnit });
|
||||
},
|
||||
{
|
||||
years: ["year", "day"],
|
||||
months: ["month", "day"],
|
||||
weeks: ["week", "day"],
|
||||
days: ["day", "day"],
|
||||
years: ["year"],
|
||||
months: ["month"],
|
||||
weeks: ["week"],
|
||||
days: [],
|
||||
hours: [],
|
||||
minutes: [],
|
||||
seconds: [],
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
// Copyright (C) 2022 Igalia, S.L. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-temporal.duration.prototype.add
|
||||
description: >
|
||||
NanosecondsToDays can loop arbitrarily long, performing observable operations each iteration.
|
||||
info: |
|
||||
NanosecondsToDays ( nanoseconds, relativeTo )
|
||||
|
||||
...
|
||||
15. If sign is 1, then
|
||||
a. Repeat, while days > 0 and intermediateNs > endNs,
|
||||
i. Set days to days - 1.
|
||||
ii. Set intermediateNs to ℝ(? AddZonedDateTime(ℤ(startNs), relativeTo.[[TimeZone]],
|
||||
relativeTo.[[Calendar]], 0, 0, 0, days, 0, 0, 0, 0, 0, 0)).
|
||||
...
|
||||
includes: [temporalHelpers.js]
|
||||
features: [Temporal]
|
||||
---*/
|
||||
|
||||
const calls = [];
|
||||
const duration = Temporal.Duration.from({ days: 1 });
|
||||
|
||||
function createRelativeTo(count) {
|
||||
const tz = new Temporal.TimeZone("UTC");
|
||||
// Record calls in calls[]
|
||||
TemporalHelpers.observeMethod(calls, tz, "getPossibleInstantsFor");
|
||||
const cal = new Temporal.Calendar("iso8601");
|
||||
// Return _count_ days for the second call to dateUntil, behaving normally after
|
||||
TemporalHelpers.substituteMethod(cal, "dateUntil", [
|
||||
TemporalHelpers.SUBSTITUTE_SKIP,
|
||||
Temporal.Duration.from({ days: count }),
|
||||
]);
|
||||
return new Temporal.ZonedDateTime(0n, tz, cal);
|
||||
}
|
||||
|
||||
let zdt = createRelativeTo(200);
|
||||
calls.splice(0); // Reset calls list after ZonedDateTime construction
|
||||
duration.add(duration, {
|
||||
relativeTo: zdt,
|
||||
});
|
||||
assert.sameValue(
|
||||
calls.length,
|
||||
200 + 2,
|
||||
"Expected duration.add to call getPossibleInstantsFor correct number of times"
|
||||
);
|
||||
|
||||
zdt = createRelativeTo(300);
|
||||
calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
|
||||
duration.add(duration, {
|
||||
relativeTo: zdt,
|
||||
});
|
||||
assert.sameValue(
|
||||
calls.length,
|
||||
300 + 2,
|
||||
"Expected duration.add to call getPossibleInstantsFor correct number of times"
|
||||
);
|
|
@ -229,9 +229,6 @@ const expectedOpsForPlainRelativeToNoCalendarOperations = [
|
|||
// InterpretTemporalDateTimeFields
|
||||
"get options.relativeTo.calendar.dateFromFields",
|
||||
"call options.relativeTo.calendar.dateFromFields",
|
||||
// AddDuration
|
||||
"get options.relativeTo.calendar.dateUntil",
|
||||
"call options.relativeTo.calendar.dateUntil",
|
||||
];
|
||||
|
||||
const noCalendarInstance = new Temporal.Duration(0, 0, 0, 4, 5, 6, 7, 987, 654, 321);
|
||||
|
@ -355,9 +352,6 @@ const expectedOpsForZonedRelativeTo = expected.concat([
|
|||
"call options.relativeTo.timeZone.getOffsetNanosecondsFor",
|
||||
"get options.relativeTo.timeZone.getOffsetNanosecondsFor",
|
||||
"call options.relativeTo.timeZone.getOffsetNanosecondsFor",
|
||||
// AddDuration → DifferenceZonedDateTime → NanosecondsToDays → DifferenceISODateTime
|
||||
"get options.relativeTo.calendar.dateUntil",
|
||||
"call options.relativeTo.calendar.dateUntil",
|
||||
// AddDuration → DifferenceZonedDateTime → NanosecondsToDays → AddZonedDateTime 1
|
||||
"get options.relativeTo.timeZone.getPossibleInstantsFor",
|
||||
"call options.relativeTo.timeZone.getPossibleInstantsFor",
|
||||
|
|
|
@ -76,13 +76,13 @@ features: [Temporal]
|
|||
---*/
|
||||
|
||||
// Check with smallestUnit nanoseconds but roundingIncrement > 1; each call
|
||||
// should result in two calls to dateUntil() originating from
|
||||
// AdjustRoundedDurationDays, one with largestUnit equal to the largest unit in
|
||||
// the duration higher than "day", and one with largestUnit: "day".
|
||||
// should result in one call to dateUntil() originating from
|
||||
// AdjustRoundedDurationDays, with largestUnit equal to the largest unit in
|
||||
// the duration higher than "day".
|
||||
// Additionally one call with largestUnit: "month" in BalanceDurationRelative
|
||||
// when the largestUnit given to round() is "year", and one call with
|
||||
// largestUnit: "day" when the largestUnit given to round() is "year", "month",
|
||||
// "week", or "day".
|
||||
// when the largestUnit given to round() is "year".
|
||||
// Other calls have largestUnit: "day" so the difference is taken in ISO
|
||||
// calendar space.
|
||||
|
||||
const durations = [
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0, 86399_999_999_999],
|
||||
|
@ -104,16 +104,16 @@ TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
|||
duration.round({ largestUnit, roundingIncrement: 2, roundingMode: 'ceil', relativeTo });
|
||||
},
|
||||
{
|
||||
years: ["year", "day", "day", "month"],
|
||||
months: ["month", "day", "day"],
|
||||
weeks: ["week", "day", "day"],
|
||||
days: ["day", "day", "day"],
|
||||
hours: ["day", "day"],
|
||||
minutes: ["day", "day"],
|
||||
seconds: ["day", "day"],
|
||||
milliseconds: ["day", "day"],
|
||||
microseconds: ["day", "day"],
|
||||
nanoseconds: ["day", "day"]
|
||||
years: ["year", "month"],
|
||||
months: ["month"],
|
||||
weeks: ["week"],
|
||||
days: [],
|
||||
hours: [],
|
||||
minutes: [],
|
||||
seconds: [],
|
||||
milliseconds: [],
|
||||
microseconds: [],
|
||||
nanoseconds: []
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -142,18 +142,18 @@ TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
|||
|
||||
// Check the paths that call dateUntil() in RoundDuration. These paths do not
|
||||
// call dateUntil() in AdjustRoundedDurationDays. Note that there is no
|
||||
// largestUnit: "month" call in BalanceDurationRelative and no largestUnit:
|
||||
// "day" call in BalanceDuration, because the durations have rounded down to 0.
|
||||
// largestUnit: "month" call in BalanceDurationRelative, because the durations
|
||||
// have rounded down to 0.
|
||||
|
||||
TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
||||
(calendar, largestUnit) => {
|
||||
const duration = new Temporal.Duration(0, 0, 0, 0, 1, 1, 1, 1, 1, 1);
|
||||
const duration = new Temporal.Duration(0, 1, 0, 0, 1, 1, 1, 1, 1, 1);
|
||||
const relativeTo = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
|
||||
duration.round({ largestUnit, smallestUnit: largestUnit, relativeTo });
|
||||
}, {
|
||||
years: ["day", "year"],
|
||||
months: ["day"],
|
||||
weeks: ["day"],
|
||||
days: ["day"]
|
||||
years: ["year"],
|
||||
months: [],
|
||||
weeks: [],
|
||||
days: []
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
// Copyright (C) 2022 André Bargull. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
/*---
|
||||
esid: sec-temporal.duration.prototype.round
|
||||
description: >
|
||||
NanosecondsToDays can loop arbitrarily long, performing observable operations each iteration.
|
||||
info: |
|
||||
NanosecondsToDays ( nanoseconds, relativeTo )
|
||||
|
||||
...
|
||||
15. If sign is 1, then
|
||||
a. Repeat, while days > 0 and intermediateNs > endNs,
|
||||
i. Set days to days - 1.
|
||||
ii. Set intermediateNs to ℝ(? AddZonedDateTime(ℤ(startNs), relativeTo.[[TimeZone]],
|
||||
relativeTo.[[Calendar]], 0, 0, 0, days, 0, 0, 0, 0, 0, 0)).
|
||||
...
|
||||
includes: [temporalHelpers.js]
|
||||
features: [Temporal]
|
||||
---*/
|
||||
|
||||
const calls = [];
|
||||
const duration = Temporal.Duration.from({ days: 1 });
|
||||
|
||||
function createRelativeTo(count) {
|
||||
const tz = new Temporal.TimeZone("UTC");
|
||||
// Record calls in calls[]
|
||||
TemporalHelpers.observeMethod(calls, tz, "getPossibleInstantsFor");
|
||||
const cal = new Temporal.Calendar("iso8601");
|
||||
// Return _count_ days for the first call to dateUntil, behaving normally after
|
||||
TemporalHelpers.substituteMethod(cal, "dateUntil", [
|
||||
Temporal.Duration.from({ days: count }),
|
||||
]);
|
||||
return new Temporal.ZonedDateTime(0n, tz, cal);
|
||||
}
|
||||
|
||||
let zdt = createRelativeTo(200);
|
||||
calls.splice(0); // Reset calls list after ZonedDateTime construction
|
||||
duration.round({
|
||||
largestUnit: "days",
|
||||
relativeTo: zdt,
|
||||
});
|
||||
assert.sameValue(
|
||||
calls.length,
|
||||
200 + 2,
|
||||
"Expected duration.round to call getPossibleInstantsFor correct number of times"
|
||||
);
|
||||
|
||||
zdt = createRelativeTo(300);
|
||||
calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
|
||||
duration.round({
|
||||
largestUnit: "days",
|
||||
relativeTo: zdt,
|
||||
});
|
||||
assert.sameValue(
|
||||
calls.length,
|
||||
300 + 2,
|
||||
"Expected duration.round to call getPossibleInstantsFor correct number of times"
|
||||
);
|
|
@ -380,8 +380,6 @@ const expectedOpsForMinimalYearRoundingZoned = expectedOpsForZonedRelativeTo.con
|
|||
"call options.relativeTo.timeZone.getOffsetNanosecondsFor",
|
||||
"get options.relativeTo.timeZone.getOffsetNanosecondsFor", // 11. GetPlainDateTimeFor
|
||||
"call options.relativeTo.timeZone.getOffsetNanosecondsFor",
|
||||
"get options.relativeTo.calendar.dateUntil", // 12. DifferenceISODateTime
|
||||
"call options.relativeTo.calendar.dateUntil",
|
||||
// NanosecondsToDays → AddDaysToZonedDateTime
|
||||
"get options.relativeTo.timeZone.getPossibleInstantsFor",
|
||||
"call options.relativeTo.timeZone.getPossibleInstantsFor",
|
||||
|
@ -421,8 +419,6 @@ const expectedOpsForYearRoundingZoned = expectedOpsForZonedRelativeTo.concat([
|
|||
"call options.relativeTo.timeZone.getOffsetNanosecondsFor",
|
||||
"get options.relativeTo.timeZone.getOffsetNanosecondsFor", // 11. GetPlainDateTimeFor
|
||||
"call options.relativeTo.timeZone.getOffsetNanosecondsFor",
|
||||
"get options.relativeTo.calendar.dateUntil", // 12. DifferenceISODateTime
|
||||
"call options.relativeTo.calendar.dateUntil",
|
||||
// NanosecondsToDays → AddDaysToZonedDateTime
|
||||
"get options.relativeTo.timeZone.getPossibleInstantsFor",
|
||||
"call options.relativeTo.timeZone.getPossibleInstantsFor",
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
// Copyright (C) 2023 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: A malicious time zone resulting a day length of zero is handled correctly
|
||||
info: |
|
||||
Based on a test by André Bargull.
|
||||
|
||||
RoundDuration step 6:
|
||||
d. Let _result_ be ? NanosecondsToDays(_nanoseconds_, _intermediate_).
|
||||
e. Set _days_ to _days_ + _result_.[[Days]] + _result_.[[Nanoseconds]] / _result_.[[DayLength]].
|
||||
|
||||
NanosecondsToDays steps 19-23:
|
||||
19. If _days_ < 0 and _sign_ = 1, throw a *RangeError* exception.
|
||||
20. If _days_ > 0 and _sign_ = -1, throw a *RangeError* exception.
|
||||
21. If _nanoseconds_ < 0, then
|
||||
a. Assert: sign is -1.
|
||||
22. If _nanoseconds_ > 0 and _sign_ = -1, throw a *RangeError* exception.
|
||||
23. Assert: The inequality abs(_nanoseconds_) < abs(_dayLengthNs_) holds.
|
||||
features: [Temporal]
|
||||
---*/
|
||||
|
||||
const instance = new Temporal.Duration(0, 0, 0, 0, 24, 0, 0, 0, 0, 1);
|
||||
|
||||
const tz = new class extends Temporal.TimeZone {
|
||||
#getPossibleInstantsForCalls = 0;
|
||||
|
||||
getPossibleInstantsFor(dt) {
|
||||
this.#getPossibleInstantsForCalls++;
|
||||
|
||||
if (this.#getPossibleInstantsForCalls <= 2) {
|
||||
return [new Temporal.Instant(86400_000_000_000n + 2n)]
|
||||
}
|
||||
return super.getPossibleInstantsFor(dt);
|
||||
}
|
||||
}("UTC");
|
||||
|
||||
const cal = new class extends Temporal.Calendar {
|
||||
#dateUntilCalls = 0;
|
||||
|
||||
dateUntil(one, two, options) {
|
||||
this.#dateUntilCalls++;
|
||||
|
||||
if (this.#dateUntilCalls === 1) {
|
||||
return new Temporal.Duration(0, 0, 0, -2);
|
||||
}
|
||||
return super.dateUntil(one, two, options);
|
||||
}
|
||||
}("iso8601");
|
||||
|
||||
const relativeTo = new Temporal.ZonedDateTime(0n, tz, cal);
|
||||
assert.throws(RangeError, () => instance.round({ relativeTo, smallestUnit: "days" }));
|
|
@ -46,13 +46,13 @@ TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
|||
years: ["year"],
|
||||
months: ["month"],
|
||||
weeks: ["week"],
|
||||
days: ["day"],
|
||||
hours: ["day"],
|
||||
minutes: ["day"],
|
||||
seconds: ["day"],
|
||||
milliseconds: ["day"],
|
||||
microseconds: ["day"],
|
||||
nanoseconds: ["day"]
|
||||
days: [],
|
||||
hours: [],
|
||||
minutes: [],
|
||||
seconds: [],
|
||||
milliseconds: [],
|
||||
microseconds: [],
|
||||
nanoseconds: []
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -64,10 +64,10 @@ TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
|||
two.subtract(one, { relativeTo });
|
||||
},
|
||||
{
|
||||
years: ["year", "day"],
|
||||
months: ["month", "day"],
|
||||
weeks: ["week", "day"],
|
||||
days: ["day", "day"],
|
||||
years: ["year"],
|
||||
months: ["month"],
|
||||
weeks: ["week"],
|
||||
days: [],
|
||||
hours: [],
|
||||
minutes: [],
|
||||
seconds: [],
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
// Copyright (C) 2022 Igalia, S.L. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-temporal.duration.prototype.subtract
|
||||
description: >
|
||||
NanosecondsToDays can loop arbitrarily long, performing observable operations each iteration.
|
||||
info: |
|
||||
NanosecondsToDays ( nanoseconds, relativeTo )
|
||||
|
||||
...
|
||||
15. If sign is 1, then
|
||||
a. Repeat, while days > 0 and intermediateNs > endNs,
|
||||
i. Set days to days - 1.
|
||||
ii. Set intermediateNs to ℝ(? AddZonedDateTime(ℤ(startNs), relativeTo.[[TimeZone]],
|
||||
relativeTo.[[Calendar]], 0, 0, 0, days, 0, 0, 0, 0, 0, 0)).
|
||||
...
|
||||
includes: [temporalHelpers.js]
|
||||
features: [Temporal]
|
||||
---*/
|
||||
|
||||
const calls = [];
|
||||
const duration = Temporal.Duration.from({ days: 1 });
|
||||
const other = Temporal.Duration.from({ hours: 1 });
|
||||
|
||||
function createRelativeTo(count) {
|
||||
const tz = new Temporal.TimeZone("UTC");
|
||||
// Record calls in calls[]
|
||||
TemporalHelpers.observeMethod(calls, tz, "getPossibleInstantsFor");
|
||||
const cal = new Temporal.Calendar("iso8601");
|
||||
// Return _count_ days for the second call to dateUntil, behaving normally after
|
||||
TemporalHelpers.substituteMethod(cal, "dateUntil", [
|
||||
TemporalHelpers.SUBSTITUTE_SKIP,
|
||||
Temporal.Duration.from({ days: count }),
|
||||
]);
|
||||
return new Temporal.ZonedDateTime(0n, tz, cal);
|
||||
}
|
||||
|
||||
let zdt = createRelativeTo(200);
|
||||
calls.splice(0); // Reset calls list after ZonedDateTime construction
|
||||
duration.subtract(other, {
|
||||
relativeTo: zdt,
|
||||
});
|
||||
assert.sameValue(
|
||||
calls.length,
|
||||
200 + 2,
|
||||
"Expected duration.subtract to call getPossibleInstantsFor correct number of times"
|
||||
);
|
||||
|
||||
zdt = createRelativeTo(300);
|
||||
calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
|
||||
duration.subtract(other, {
|
||||
relativeTo: zdt,
|
||||
});
|
||||
assert.sameValue(
|
||||
calls.length,
|
||||
300 + 2,
|
||||
"Expected duration.subtract to call getPossibleInstantsFor correct number of times"
|
||||
);
|
|
@ -229,9 +229,6 @@ const expectedOpsForPlainRelativeToNoCalendarOperations = [
|
|||
// InterpretTemporalDateTimeFields
|
||||
"get options.relativeTo.calendar.dateFromFields",
|
||||
"call options.relativeTo.calendar.dateFromFields",
|
||||
// AddDuration
|
||||
"get options.relativeTo.calendar.dateUntil",
|
||||
"call options.relativeTo.calendar.dateUntil",
|
||||
];
|
||||
|
||||
const noCalendarInstance = new Temporal.Duration(0, 0, 0, 4, 5, 6, 7, 987, 654, 321);
|
||||
|
@ -355,9 +352,6 @@ const expectedOpsForZonedRelativeTo = expected.concat([
|
|||
"call options.relativeTo.timeZone.getOffsetNanosecondsFor",
|
||||
"get options.relativeTo.timeZone.getOffsetNanosecondsFor",
|
||||
"call options.relativeTo.timeZone.getOffsetNanosecondsFor",
|
||||
// AddDuration → DifferenceZonedDateTime → NanosecondsToDays → DifferenceISODateTime
|
||||
"get options.relativeTo.calendar.dateUntil",
|
||||
"call options.relativeTo.calendar.dateUntil",
|
||||
// AddDuration → DifferenceZonedDateTime → NanosecondsToDays → AddZonedDateTime 1
|
||||
"get options.relativeTo.timeZone.getPossibleInstantsFor",
|
||||
"call options.relativeTo.timeZone.getPossibleInstantsFor",
|
||||
|
|
|
@ -44,10 +44,9 @@ includes: [compareArray.js, temporalHelpers.js]
|
|||
features: [Temporal]
|
||||
---*/
|
||||
|
||||
// Check the paths that go through NanosecondsToDays: one call to dateUntil() in
|
||||
// BalanceDuration and one in RoundDuration with largestUnit: "day" when the
|
||||
// unit is "year", "month", "week", or "day", and one extra call with
|
||||
// largestUnit: "year" in RoundDuration when the unit is "year".
|
||||
// Check the paths that go through NanosecondsToDays: only one call with
|
||||
// largestUnit: "year" in RoundDuration when the unit is "year". The others all
|
||||
// have largestUnit: "day" so the difference is taken in ISO calendar space.
|
||||
|
||||
const duration = new Temporal.Duration(0, 1, 1, 1, 1, 1, 1, 1, 1, 1);
|
||||
|
||||
|
@ -57,10 +56,10 @@ TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
|||
duration.total({ unit, relativeTo });
|
||||
},
|
||||
{
|
||||
years: ["day", "day", "year"],
|
||||
months: ["day", "day"],
|
||||
weeks: ["day", "day"],
|
||||
days: ["day", "day"],
|
||||
years: ["year"],
|
||||
months: [],
|
||||
weeks: [],
|
||||
days: [],
|
||||
hours: [],
|
||||
minutes: [],
|
||||
seconds: [],
|
||||
|
@ -74,7 +73,7 @@ TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
|||
|
||||
TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
||||
(calendar, unit) => {
|
||||
const duration = new Temporal.Duration(5);
|
||||
const duration = new Temporal.Duration(5, 1);
|
||||
const relativeTo = new Temporal.PlainDateTime(2000, 5, 2, 0, 0, 0, 0, 0, 0, calendar);
|
||||
duration.total({ unit, relativeTo });
|
||||
},
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
// Copyright (C) 2022 Igalia, S.L. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-temporal.duration.prototype.total
|
||||
description: >
|
||||
NanosecondsToDays can loop arbitrarily long, performing observable operations each iteration.
|
||||
info: |
|
||||
NanosecondsToDays ( nanoseconds, relativeTo )
|
||||
|
||||
...
|
||||
15. If sign is 1, then
|
||||
a. Repeat, while days > 0 and intermediateNs > endNs,
|
||||
i. Set days to days - 1.
|
||||
ii. Set intermediateNs to ℝ(? AddZonedDateTime(ℤ(startNs), relativeTo.[[TimeZone]],
|
||||
relativeTo.[[Calendar]], 0, 0, 0, days, 0, 0, 0, 0, 0, 0)).
|
||||
...
|
||||
includes: [temporalHelpers.js]
|
||||
features: [Temporal]
|
||||
---*/
|
||||
|
||||
const calls = [];
|
||||
const duration = Temporal.Duration.from({ days: 1 });
|
||||
|
||||
function createRelativeTo(count) {
|
||||
const tz = new Temporal.TimeZone("UTC");
|
||||
// Record calls in calls[]
|
||||
TemporalHelpers.observeMethod(calls, tz, "getPossibleInstantsFor");
|
||||
const cal = new Temporal.Calendar("iso8601");
|
||||
// Return _count_ days for the first call to dateUntil, behaving normally after
|
||||
TemporalHelpers.substituteMethod(cal, "dateUntil", [
|
||||
Temporal.Duration.from({ days: count }),
|
||||
]);
|
||||
return new Temporal.ZonedDateTime(0n, tz, cal);
|
||||
}
|
||||
|
||||
let zdt = createRelativeTo(200);
|
||||
calls.splice(0); // Reset calls list after ZonedDateTime construction
|
||||
duration.total({
|
||||
unit: "day",
|
||||
relativeTo: zdt,
|
||||
});
|
||||
assert.sameValue(
|
||||
calls.length,
|
||||
200 + 3,
|
||||
"Expected duration.total to call getPossibleInstantsFor correct number of times"
|
||||
);
|
||||
|
||||
zdt = createRelativeTo(300);
|
||||
calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
|
||||
duration.total({
|
||||
unit: "day",
|
||||
relativeTo: zdt,
|
||||
});
|
||||
assert.sameValue(
|
||||
calls.length,
|
||||
300 + 3,
|
||||
"Expected duration.total to call getPossibleInstantsFor correct number of times"
|
||||
);
|
|
@ -281,8 +281,6 @@ const expectedOpsForMinimalYearRoundingZoned = expectedOpsForZonedRelativeTo.con
|
|||
"call options.relativeTo.timeZone.getOffsetNanosecondsFor",
|
||||
"get options.relativeTo.timeZone.getOffsetNanosecondsFor", // 11. GetPlainDateTimeFor
|
||||
"call options.relativeTo.timeZone.getOffsetNanosecondsFor",
|
||||
"get options.relativeTo.calendar.dateUntil", // 12. DifferenceISODateTime
|
||||
"call options.relativeTo.calendar.dateUntil",
|
||||
// BalancePossiblyInfiniteDuration → NanosecondsToDays → AddDaysToZonedDateTime
|
||||
"get options.relativeTo.timeZone.getPossibleInstantsFor",
|
||||
"call options.relativeTo.timeZone.getPossibleInstantsFor",
|
||||
|
@ -328,8 +326,6 @@ const expectedOpsForYearRoundingZoned = expectedOpsForZonedRelativeTo.concat([
|
|||
"call options.relativeTo.timeZone.getOffsetNanosecondsFor",
|
||||
"get options.relativeTo.timeZone.getOffsetNanosecondsFor", // 11. GetPlainDateTimeFor
|
||||
"call options.relativeTo.timeZone.getOffsetNanosecondsFor",
|
||||
"get options.relativeTo.calendar.dateUntil", // 12. DifferenceISODateTime
|
||||
"call options.relativeTo.calendar.dateUntil",
|
||||
// BalancePossiblyInfiniteTimeDurationRelative → NanosecondsToDays → AddDaysToZonedDateTime
|
||||
"get options.relativeTo.timeZone.getPossibleInstantsFor",
|
||||
"call options.relativeTo.timeZone.getPossibleInstantsFor",
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
// Copyright (C) 2023 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: A malicious time zone resulting a day length of zero is handled correctly
|
||||
info: |
|
||||
Based on a test by André Bargull.
|
||||
|
||||
RoundDuration step 6:
|
||||
d. Let _result_ be ? NanosecondsToDays(_nanoseconds_, _intermediate_).
|
||||
e. Set _days_ to _days_ + _result_.[[Days]] + _result_.[[Nanoseconds]] / _result_.[[DayLength]].
|
||||
|
||||
NanosecondsToDays steps 19-23:
|
||||
19. If _days_ < 0 and _sign_ = 1, throw a *RangeError* exception.
|
||||
20. If _days_ > 0 and _sign_ = -1, throw a *RangeError* exception.
|
||||
21. If _nanoseconds_ < 0, then
|
||||
a. Assert: sign is -1.
|
||||
22. If _nanoseconds_ > 0 and _sign_ = -1, throw a *RangeError* exception.
|
||||
23. Assert: The inequality abs(_nanoseconds_) < abs(_dayLengthNs_) holds.
|
||||
features: [Temporal]
|
||||
---*/
|
||||
|
||||
const instance = new Temporal.Duration(0, 0, 0, 0, 24, 0, 0, 0, 0, 1);
|
||||
|
||||
const tz = new class extends Temporal.TimeZone {
|
||||
#getPossibleInstantsForCalls = 0;
|
||||
|
||||
getPossibleInstantsFor(dt) {
|
||||
this.#getPossibleInstantsForCalls++;
|
||||
|
||||
if (this.#getPossibleInstantsForCalls <= 2) {
|
||||
return [new Temporal.Instant(86400_000_000_000n + 2n)]
|
||||
}
|
||||
return super.getPossibleInstantsFor(dt);
|
||||
}
|
||||
}("UTC");
|
||||
|
||||
const cal = new class extends Temporal.Calendar {
|
||||
#dateUntilCalls = 0;
|
||||
|
||||
dateUntil(one, two, options) {
|
||||
this.#dateUntilCalls++;
|
||||
|
||||
if (this.#dateUntilCalls === 1) {
|
||||
return new Temporal.Duration(0, 0, 0, -2);
|
||||
}
|
||||
return super.dateUntil(one, two, options);
|
||||
}
|
||||
}("iso8601");
|
||||
|
||||
const relativeTo = new Temporal.ZonedDateTime(0n, tz, cal);
|
||||
assert.throws(RangeError, () => instance.total({ relativeTo, unit: "days" }));
|
|
@ -13,5 +13,5 @@ features: [Temporal]
|
|||
const calendar = TemporalHelpers.calendarCheckOptionsPrototypePollution();
|
||||
const instance = new Temporal.PlainDate(2000, 5, 2, calendar);
|
||||
const argument = new Temporal.PlainDate(2022, 6, 14, calendar);
|
||||
instance.since(argument);
|
||||
instance.since(argument, { largestUnit: "months" });
|
||||
assert.sameValue(calendar.dateUntilCallCount, 1, "dateUntil should have been called on the calendar");
|
||||
|
|
|
@ -22,6 +22,6 @@ TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
|||
years: ["year"],
|
||||
months: ["month"],
|
||||
weeks: ["week"],
|
||||
days: ["day"]
|
||||
days: []
|
||||
}
|
||||
);
|
||||
|
|
|
@ -32,5 +32,9 @@ const tests = [
|
|||
];
|
||||
for (const [test, description = typeof test] of tests) {
|
||||
const plainDate = new Temporal.PlainDate(2000, 5, 2, new CustomCalendar(test));
|
||||
assert.throws(TypeError, () => plainDate.since("2022-06-20"), `Expected error with ${description}`);
|
||||
assert.throws(
|
||||
TypeError,
|
||||
() => plainDate.since("2022-06-20", { largestUnit: "years" }),
|
||||
`Expected error with ${description}`
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ features: [Temporal]
|
|||
---*/
|
||||
|
||||
const result = new Temporal.Duration(1, 3, 5, 7, 9);
|
||||
const options = {};
|
||||
const options = { largestUnit: "years" };
|
||||
let calls = 0;
|
||||
class CustomCalendar extends Temporal.Calendar {
|
||||
constructor() {
|
||||
|
@ -20,7 +20,7 @@ class CustomCalendar extends Temporal.Calendar {
|
|||
assert.sameValue(args.length, 3, "Three arguments");
|
||||
assert.sameValue(args[0], plainDate, "First argument");
|
||||
assert.sameValue(args[1], other, "Second argument");
|
||||
assert.sameValue(args[2].largestUnit, "day", "Third argument: largestUnit");
|
||||
assert.sameValue(args[2].largestUnit, "year", "Third argument: largestUnit");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,8 +86,8 @@ const instance = new Temporal.PlainDate(2000, 5, 2, ownCalendar);
|
|||
|
||||
const otherDatePropertyBag = TemporalHelpers.propertyBagObserver(actual, {
|
||||
year: 2001,
|
||||
month: 5,
|
||||
monthCode: 'M05',
|
||||
month: 6,
|
||||
monthCode: "M06",
|
||||
day: 2,
|
||||
calendar: TemporalHelpers.calendarObserver(actual, "other.calendar"),
|
||||
}, "other");
|
||||
|
@ -107,8 +107,8 @@ function createOptionsObserver({ smallestUnit = "days", largestUnit = "auto", ro
|
|||
// clear any observable things that happened while constructing the objects
|
||||
actual.splice(0);
|
||||
|
||||
// basic order of observable operations, without rounding:
|
||||
instance.since(otherDatePropertyBag, createOptionsObserver());
|
||||
// basic order of observable operations with calendar call, without rounding:
|
||||
instance.since(otherDatePropertyBag, createOptionsObserver({ largestUnit: "years" }));
|
||||
assert.compareArray(actual, expected, "order of operations");
|
||||
actual.splice(0); // clear
|
||||
|
||||
|
@ -138,6 +138,24 @@ instance.since(otherDatePropertyBag, createOptionsObserver({ smallestUnit: "year
|
|||
assert.compareArray(actual, expectedOpsForYearRounding, "order of operations with smallestUnit = years");
|
||||
actual.splice(0); // clear
|
||||
|
||||
// code path through RoundDuration that rounds to the nearest year and skips a DateUntil call:
|
||||
const otherDatePropertyBagSameMonth = TemporalHelpers.propertyBagObserver(actual, {
|
||||
year: 2001,
|
||||
month: 5,
|
||||
monthCode: "M05",
|
||||
day: 2,
|
||||
calendar: TemporalHelpers.calendarObserver(actual, "other.calendar"),
|
||||
}, "other");
|
||||
const expectedOpsForYearRoundingSameMonth = expected.concat([
|
||||
"get this.calendar.dateAdd", // 7.c.i
|
||||
"call this.calendar.dateAdd", // 7.e
|
||||
"call this.calendar.dateAdd", // 7.g
|
||||
"call this.calendar.dateAdd", // 7.y MoveRelativeDate
|
||||
]); // (7.o not called because months and weeks == 0)
|
||||
instance.since(otherDatePropertyBagSameMonth, createOptionsObserver({ smallestUnit: "years" }));
|
||||
assert.compareArray(actual, expectedOpsForYearRoundingSameMonth, "order of operations with smallestUnit = years and no excess months/weeks");
|
||||
actual.splice(0); // clear
|
||||
|
||||
// code path through RoundDuration that rounds to the nearest month:
|
||||
const expectedOpsForMonthRounding = expected.concat([
|
||||
"get this.calendar.dateAdd", // 10.b
|
||||
|
|
|
@ -13,5 +13,5 @@ features: [Temporal]
|
|||
const calendar = TemporalHelpers.calendarCheckOptionsPrototypePollution();
|
||||
const instance = new Temporal.PlainDate(2000, 5, 2, calendar);
|
||||
const argument = new Temporal.PlainDate(2022, 6, 14, calendar);
|
||||
instance.until(argument);
|
||||
instance.until(argument, { largestUnit: "months" });
|
||||
assert.sameValue(calendar.dateUntilCallCount, 1, "dateUntil should have been called on the calendar");
|
||||
|
|
|
@ -22,6 +22,6 @@ TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
|||
years: ["year"],
|
||||
months: ["month"],
|
||||
weeks: ["week"],
|
||||
days: ["day"]
|
||||
days: []
|
||||
}
|
||||
);
|
||||
|
|
|
@ -32,5 +32,8 @@ const tests = [
|
|||
];
|
||||
for (const [test, description = typeof test] of tests) {
|
||||
const plainDate = new Temporal.PlainDate(2000, 5, 2, new CustomCalendar(test));
|
||||
assert.throws(TypeError, () => plainDate.until("2022-06-20"), `Expected error with ${description}`);
|
||||
assert.throws(
|
||||
TypeError, () => plainDate.until("2022-06-20", { largestUnit: "years" }),
|
||||
`Expected error with ${description}`
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ features: [Temporal]
|
|||
---*/
|
||||
|
||||
const result = new Temporal.Duration(1, 3, 5, 7, 9);
|
||||
const options = {};
|
||||
const options = { largestUnit: "years" };
|
||||
let calls = 0;
|
||||
class CustomCalendar extends Temporal.Calendar {
|
||||
constructor() {
|
||||
|
@ -20,7 +20,7 @@ class CustomCalendar extends Temporal.Calendar {
|
|||
assert.sameValue(args.length, 3, "Three arguments");
|
||||
assert.sameValue(args[0], plainDate, "First argument");
|
||||
assert.sameValue(args[1], other, "Second argument");
|
||||
assert.sameValue(args[2].largestUnit, "day", "Third argument: largestUnit");
|
||||
assert.sameValue(args[2].largestUnit, "year", "Third argument: largestUnit");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,8 +86,8 @@ const instance = new Temporal.PlainDate(2000, 5, 2, ownCalendar);
|
|||
|
||||
const otherDatePropertyBag = TemporalHelpers.propertyBagObserver(actual, {
|
||||
year: 2001,
|
||||
month: 5,
|
||||
monthCode: "M05",
|
||||
month: 6,
|
||||
monthCode: "M06",
|
||||
day: 2,
|
||||
calendar: TemporalHelpers.calendarObserver(actual, "other.calendar"),
|
||||
}, "other");
|
||||
|
@ -107,8 +107,8 @@ function createOptionsObserver({ smallestUnit = "days", largestUnit = "auto", ro
|
|||
// clear any observable things that happened while constructing the objects
|
||||
actual.splice(0);
|
||||
|
||||
// basic order of observable operations, without rounding:
|
||||
instance.until(otherDatePropertyBag, createOptionsObserver());
|
||||
// basic order of observable operations with calendar call, without rounding:
|
||||
instance.until(otherDatePropertyBag, createOptionsObserver({ largestUnit: "years" }));
|
||||
assert.compareArray(actual, expected, "order of operations");
|
||||
actual.splice(0); // clear
|
||||
|
||||
|
@ -139,6 +139,24 @@ instance.until(otherDatePropertyBag, createOptionsObserver({ smallestUnit: "year
|
|||
assert.compareArray(actual, expectedOpsForYearRounding, "order of operations with smallestUnit = years");
|
||||
actual.splice(0); // clear
|
||||
|
||||
// code path through RoundDuration that rounds to the nearest year and skips a DateUntil call:
|
||||
const otherDatePropertyBagSameMonth = TemporalHelpers.propertyBagObserver(actual, {
|
||||
year: 2001,
|
||||
month: 5,
|
||||
monthCode: "M05",
|
||||
day: 2,
|
||||
calendar: TemporalHelpers.calendarObserver(actual, "other.calendar"),
|
||||
}, "other");
|
||||
const expectedOpsForYearRoundingSameMonth = expected.concat([
|
||||
"get this.calendar.dateAdd", // 7.c.i
|
||||
"call this.calendar.dateAdd", // 7.e
|
||||
"call this.calendar.dateAdd", // 7.g
|
||||
"call this.calendar.dateAdd", // 7.y MoveRelativeDate
|
||||
]); // (7.o not called because months and weeks == 0)
|
||||
instance.until(otherDatePropertyBagSameMonth, createOptionsObserver({ smallestUnit: "years" }));
|
||||
assert.compareArray(actual, expectedOpsForYearRoundingSameMonth, "order of operations with smallestUnit = years and no excess months/weeks");
|
||||
actual.splice(0); // clear
|
||||
|
||||
// code path through RoundDuration that rounds to the nearest month:
|
||||
const expectedOpsForMonthRounding = expected.concat([
|
||||
"get this.calendar.dateAdd", // 10.b
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
// Copyright (C) 2022 André Bargull. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-temporal.plaindatetime.prototype.since
|
||||
description: >
|
||||
BalanceDuration throws when the result duration is invalid.
|
||||
info: |
|
||||
DifferenceISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1,
|
||||
y2, mon2, d2, h2, min2, s2, ms2, mus2, ns2,
|
||||
calendar, largestUnit, options )
|
||||
|
||||
...
|
||||
12. Let dateDifference be ? CalendarDateUntil(calendar, date1, date2, untilOptions).
|
||||
13. Let balanceResult be ? BalanceDuration(dateDifference.[[Days]], timeDifference.[[Hours]],
|
||||
timeDifference.[[Minutes]], timeDifference.[[Seconds]], timeDifference.[[Milliseconds]],
|
||||
timeDifference.[[Microseconds]], timeDifference.[[Nanoseconds]], largestUnit).
|
||||
...
|
||||
|
||||
BalanceDuration ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds,
|
||||
largestUnit [ , relativeTo ] )
|
||||
|
||||
...
|
||||
3. Else,
|
||||
a. Set nanoseconds to ! TotalDurationNanoseconds(days, hours, minutes, seconds, milliseconds,
|
||||
microseconds, nanoseconds, 0).
|
||||
...
|
||||
15. Return ? CreateTimeDurationRecord(days, hours × sign, minutes × sign, seconds × sign,
|
||||
milliseconds × sign, microseconds × sign, nanoseconds × sign).
|
||||
features: [Temporal]
|
||||
---*/
|
||||
|
||||
var cal = new class extends Temporal.Calendar {
|
||||
dateUntil(date1, date2, options) {
|
||||
return Temporal.Duration.from({days: Number.MAX_VALUE});
|
||||
}
|
||||
}("iso8601");
|
||||
|
||||
var dt1 = new Temporal.PlainDateTime(1970, 1, 1, 0, 0, 0, 0, 0, 0, cal);
|
||||
var dt2 = new Temporal.PlainDateTime(1970, 1, 2, 0, 0, 0, 0, 0, 0, cal);
|
||||
var options = {largestUnit: "nanoseconds"};
|
||||
|
||||
assert.throws(RangeError, () => dt1.since(dt2, options));
|
||||
assert.throws(RangeError, () => dt2.since(dt1, options));
|
|
@ -13,5 +13,5 @@ features: [Temporal]
|
|||
const calendar = TemporalHelpers.calendarCheckOptionsPrototypePollution();
|
||||
const instance = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
|
||||
const argument = new Temporal.PlainDateTime(2022, 6, 14, 18, 21, 36, 660, 690, 387, calendar);
|
||||
instance.since(argument);
|
||||
instance.since(argument, { largestUnit: "months" });
|
||||
assert.sameValue(calendar.dateUntilCallCount, 1, "dateUntil should have been called on the calendar");
|
||||
|
|
|
@ -25,12 +25,12 @@ TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
|||
years: ["year"],
|
||||
months: ["month"],
|
||||
weeks: ["week"],
|
||||
days: ["day"],
|
||||
hours: ["day"],
|
||||
minutes: ["day"],
|
||||
seconds: ["day"],
|
||||
milliseconds: ["day"],
|
||||
microseconds: ["day"],
|
||||
nanoseconds: ["day"]
|
||||
days: [],
|
||||
hours: [],
|
||||
minutes: [],
|
||||
seconds: [],
|
||||
milliseconds: [],
|
||||
microseconds: [],
|
||||
nanoseconds: []
|
||||
}
|
||||
);
|
||||
|
|
|
@ -131,8 +131,8 @@ function createOptionsObserver({ smallestUnit = "nanoseconds", largestUnit = "au
|
|||
// clear any observable things that happened while constructing the objects
|
||||
actual.splice(0);
|
||||
|
||||
// basic order of observable operations, without rounding:
|
||||
instance.since(otherDateTimePropertyBag, createOptionsObserver());
|
||||
// basic order of observable operations with calendar call, without rounding:
|
||||
instance.since(otherDateTimePropertyBag, createOptionsObserver({ largestUnit: "years" }));
|
||||
assert.compareArray(actual, expected, "order of operations");
|
||||
actual.splice(0); // clear
|
||||
|
||||
|
@ -168,6 +168,30 @@ instance.since(otherDateTimePropertyBag, createOptionsObserver({ smallestUnit: "
|
|||
assert.compareArray(actual, expectedOpsForYearRounding, "order of operations with smallestUnit = years");
|
||||
actual.splice(0); // clear
|
||||
|
||||
// code path through RoundDuration that rounds to the nearest year and skips a DateUntil call:
|
||||
const otherDatePropertyBagSameMonth = TemporalHelpers.propertyBagObserver(actual, {
|
||||
year: 2001,
|
||||
month: 5,
|
||||
monthCode: "M05",
|
||||
day: 2,
|
||||
hour: 12,
|
||||
minute: 34,
|
||||
second: 56,
|
||||
millisecond: 987,
|
||||
microsecond: 654,
|
||||
nanosecond: 321,
|
||||
calendar: TemporalHelpers.calendarObserver(actual, "other.calendar"),
|
||||
}, "other");
|
||||
const expectedOpsForYearRoundingSameMonth = expected.concat([
|
||||
"get this.calendar.dateAdd", // 7.c.i
|
||||
"call this.calendar.dateAdd", // 7.e
|
||||
"call this.calendar.dateAdd", // 7.g
|
||||
"call this.calendar.dateAdd", // 7.y MoveRelativeDate
|
||||
]); // (7.o not called because months and weeks == 0)
|
||||
instance.until(otherDatePropertyBagSameMonth, createOptionsObserver({ smallestUnit: "years" }));
|
||||
assert.compareArray(actual, expectedOpsForYearRoundingSameMonth, "order of operations with smallestUnit = years and no excess months/weeks");
|
||||
actual.splice(0); // clear
|
||||
|
||||
// code path through RoundDuration that rounds to the nearest month:
|
||||
const expectedOpsForMonthRounding = expected.concat([
|
||||
"get this.calendar.dateAdd", // 10.b
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
// Copyright (C) 2022 André Bargull. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-temporal.plaindatetime.prototype.since
|
||||
description: >
|
||||
BalanceDuration throws when the result duration is invalid.
|
||||
info: |
|
||||
DifferenceISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1,
|
||||
y2, mon2, d2, h2, min2, s2, ms2, mus2, ns2,
|
||||
calendar, largestUnit, options )
|
||||
|
||||
...
|
||||
12. Let dateDifference be ? CalendarDateUntil(calendar, date1, date2, untilOptions).
|
||||
13. Let balanceResult be ? BalanceDuration(dateDifference.[[Days]], timeDifference.[[Hours]],
|
||||
timeDifference.[[Minutes]], timeDifference.[[Seconds]], timeDifference.[[Milliseconds]],
|
||||
timeDifference.[[Microseconds]], timeDifference.[[Nanoseconds]], largestUnit).
|
||||
...
|
||||
|
||||
BalanceDuration ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds,
|
||||
largestUnit [ , relativeTo ] )
|
||||
|
||||
...
|
||||
3. Else,
|
||||
a. Set nanoseconds to ! TotalDurationNanoseconds(days, hours, minutes, seconds, milliseconds,
|
||||
microseconds, nanoseconds, 0).
|
||||
...
|
||||
15. Return ? CreateTimeDurationRecord(days, hours × sign, minutes × sign, seconds × sign,
|
||||
milliseconds × sign, microseconds × sign, nanoseconds × sign).
|
||||
features: [Temporal]
|
||||
---*/
|
||||
|
||||
var cal = new class extends Temporal.Calendar {
|
||||
dateUntil(date1, date2, options) {
|
||||
return Temporal.Duration.from({days: Number.MAX_VALUE});
|
||||
}
|
||||
}("iso8601");
|
||||
|
||||
var dt1 = new Temporal.PlainDateTime(1970, 1, 1, 0, 0, 0, 0, 0, 0, cal);
|
||||
var dt2 = new Temporal.PlainDateTime(1970, 1, 2, 0, 0, 0, 0, 0, 0, cal);
|
||||
var options = {largestUnit: "nanoseconds"};
|
||||
|
||||
assert.throws(RangeError, () => dt1.until(dt2, options));
|
||||
assert.throws(RangeError, () => dt2.until(dt1, options));
|
|
@ -13,5 +13,5 @@ features: [Temporal]
|
|||
const calendar = TemporalHelpers.calendarCheckOptionsPrototypePollution();
|
||||
const instance = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
|
||||
const argument = new Temporal.PlainDateTime(2022, 6, 14, 18, 21, 36, 660, 690, 387, calendar);
|
||||
instance.until(argument);
|
||||
instance.until(argument, { largestUnit: "months" });
|
||||
assert.sameValue(calendar.dateUntilCallCount, 1, "dateUntil should have been called on the calendar");
|
||||
|
|
|
@ -25,12 +25,12 @@ TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
|||
years: ["year"],
|
||||
months: ["month"],
|
||||
weeks: ["week"],
|
||||
days: ["day"],
|
||||
hours: ["day"],
|
||||
minutes: ["day"],
|
||||
seconds: ["day"],
|
||||
milliseconds: ["day"],
|
||||
microseconds: ["day"],
|
||||
nanoseconds: ["day"]
|
||||
days: [],
|
||||
hours: [],
|
||||
minutes: [],
|
||||
seconds: [],
|
||||
milliseconds: [],
|
||||
microseconds: [],
|
||||
nanoseconds: []
|
||||
}
|
||||
);
|
||||
|
|
|
@ -131,8 +131,8 @@ function createOptionsObserver({ smallestUnit = "nanoseconds", largestUnit = "au
|
|||
// clear any observable things that happened while constructing the objects
|
||||
actual.splice(0);
|
||||
|
||||
// basic order of observable operations, without rounding:
|
||||
instance.until(otherDateTimePropertyBag, createOptionsObserver());
|
||||
// basic order of observable operations with calendar call, without rounding:
|
||||
instance.until(otherDateTimePropertyBag, createOptionsObserver({ largestUnit: "years" }));
|
||||
assert.compareArray(actual, expected, "order of operations");
|
||||
actual.splice(0); // clear
|
||||
|
||||
|
@ -168,6 +168,30 @@ instance.until(otherDateTimePropertyBag, createOptionsObserver({ smallestUnit: "
|
|||
assert.compareArray(actual, expectedOpsForYearRounding, "order of operations with smallestUnit = years");
|
||||
actual.splice(0); // clear
|
||||
|
||||
// code path through RoundDuration that rounds to the nearest year and skips a DateUntil call:
|
||||
const otherDatePropertyBagSameMonth = TemporalHelpers.propertyBagObserver(actual, {
|
||||
year: 2001,
|
||||
month: 5,
|
||||
monthCode: "M05",
|
||||
day: 2,
|
||||
hour: 12,
|
||||
minute: 34,
|
||||
second: 56,
|
||||
millisecond: 987,
|
||||
microsecond: 654,
|
||||
nanosecond: 321,
|
||||
calendar: TemporalHelpers.calendarObserver(actual, "other.calendar"),
|
||||
}, "other");
|
||||
const expectedOpsForYearRoundingSameMonth = expected.concat([
|
||||
"get this.calendar.dateAdd", // 7.c.i
|
||||
"call this.calendar.dateAdd", // 7.e
|
||||
"call this.calendar.dateAdd", // 7.g
|
||||
"call this.calendar.dateAdd", // 7.y MoveRelativeDate
|
||||
]); // (7.o not called because months and weeks == 0)
|
||||
instance.until(otherDatePropertyBagSameMonth, createOptionsObserver({ smallestUnit: "years" }));
|
||||
assert.compareArray(actual, expectedOpsForYearRoundingSameMonth, "order of operations with smallestUnit = years and no excess months/weeks");
|
||||
actual.splice(0); // clear
|
||||
|
||||
// code path through RoundDuration that rounds to the nearest month:
|
||||
const expectedOpsForMonthRounding = expected.concat([
|
||||
"get this.calendar.dateAdd", // 10.b
|
||||
|
|
|
@ -100,8 +100,8 @@ const instance = new Temporal.PlainYearMonth(2000, 5, ownCalendar, 1);
|
|||
|
||||
const otherYearMonthPropertyBag = TemporalHelpers.propertyBagObserver(actual, {
|
||||
year: 2001,
|
||||
month: 5,
|
||||
monthCode: "M05",
|
||||
month: 6,
|
||||
monthCode: "M06",
|
||||
calendar: TemporalHelpers.calendarObserver(actual, "other.calendar"),
|
||||
}, "other");
|
||||
|
||||
|
@ -150,6 +150,23 @@ instance.since(otherYearMonthPropertyBag, createOptionsObserver({ smallestUnit:
|
|||
assert.compareArray(actual, expectedOpsForYearRounding, "order of operations with smallestUnit = years");
|
||||
actual.splice(0); // clear
|
||||
|
||||
// code path through RoundDuration that rounds to the nearest year and skips a DateUntil call
|
||||
const otherYearMonthPropertyBagSameMonth = TemporalHelpers.propertyBagObserver(actual, {
|
||||
year: 2001,
|
||||
month: 5,
|
||||
monthCode: "M05",
|
||||
calendar: TemporalHelpers.calendarObserver(actual, "other.calendar"),
|
||||
}, "other");
|
||||
const expectedOpsForYearRoundingSameMonth = expected.concat([
|
||||
"get this.calendar.dateAdd", // 7.c.i
|
||||
"call this.calendar.dateAdd", // 7.e
|
||||
"call this.calendar.dateAdd", // 7.g
|
||||
"call this.calendar.dateAdd", // 7.y MoveRelativeDate
|
||||
]); // (7.o not called because months and weeks == 0)
|
||||
instance.since(otherYearMonthPropertyBagSameMonth, createOptionsObserver({ smallestUnit: "years" }));
|
||||
assert.compareArray(actual, expectedOpsForYearRoundingSameMonth, "order of operations with smallestUnit = years");
|
||||
actual.splice(0); // clear
|
||||
|
||||
// code path through RoundDuration that rounds to the nearest month:
|
||||
const expectedOpsForMonthRounding = expected.concat([
|
||||
"get this.calendar.dateAdd", // 10.b
|
||||
|
|
|
@ -100,8 +100,8 @@ const instance = new Temporal.PlainYearMonth(2000, 5, ownCalendar, 1);
|
|||
|
||||
const otherYearMonthPropertyBag = TemporalHelpers.propertyBagObserver(actual, {
|
||||
year: 2001,
|
||||
month: 5,
|
||||
monthCode: "M05",
|
||||
month: 6,
|
||||
monthCode: "M06",
|
||||
calendar: TemporalHelpers.calendarObserver(actual, "other.calendar"),
|
||||
}, "other");
|
||||
|
||||
|
@ -150,6 +150,23 @@ instance.until(otherYearMonthPropertyBag, createOptionsObserver({ smallestUnit:
|
|||
assert.compareArray(actual, expectedOpsForYearRounding, "order of operations with smallestUnit = years");
|
||||
actual.splice(0); // clear
|
||||
|
||||
// code path through RoundDuration that rounds to the nearest year and skips a DateUntil call
|
||||
const otherYearMonthPropertyBagSameMonth = TemporalHelpers.propertyBagObserver(actual, {
|
||||
year: 2001,
|
||||
month: 5,
|
||||
monthCode: "M05",
|
||||
calendar: TemporalHelpers.calendarObserver(actual, "other.calendar"),
|
||||
}, "other");
|
||||
const expectedOpsForYearRoundingSameMonth = expected.concat([
|
||||
"get this.calendar.dateAdd", // 7.c.i
|
||||
"call this.calendar.dateAdd", // 7.e
|
||||
"call this.calendar.dateAdd", // 7.g
|
||||
"call this.calendar.dateAdd", // 7.y MoveRelativeDate
|
||||
]); // (7.o not called because months and weeks == 0)
|
||||
instance.until(otherYearMonthPropertyBagSameMonth, createOptionsObserver({ smallestUnit: "years" }));
|
||||
assert.compareArray(actual, expectedOpsForYearRoundingSameMonth, "order of operations with smallestUnit = years");
|
||||
actual.splice(0); // clear
|
||||
|
||||
// code path through RoundDuration that rounds to the nearest month:
|
||||
const expectedOpsForMonthRounding = expected.concat([
|
||||
"get this.calendar.dateAdd", // 10.b
|
||||
|
|
|
@ -14,4 +14,4 @@ const calendar = TemporalHelpers.calendarCheckOptionsPrototypePollution();
|
|||
const instance = new Temporal.ZonedDateTime(0n, "UTC", calendar);
|
||||
const argument = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
|
||||
instance.since(argument, { largestUnit: "year" });
|
||||
assert.sameValue(calendar.dateUntilCallCount, 2, "dateUntil should have been called on the calendar");
|
||||
assert.sameValue(calendar.dateUntilCallCount, 1, "dateUntil should have been called on the calendar");
|
||||
|
|
|
@ -55,10 +55,10 @@ TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
|||
later.since(earlier, { largestUnit });
|
||||
},
|
||||
{
|
||||
years: ["year", "day"],
|
||||
months: ["month", "day"],
|
||||
weeks: ["week", "day"],
|
||||
days: ["day", "day"],
|
||||
years: ["year"],
|
||||
months: ["month"],
|
||||
weeks: ["week"],
|
||||
days: [],
|
||||
hours: [],
|
||||
minutes: [],
|
||||
seconds: [],
|
||||
|
@ -72,15 +72,15 @@ TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
|||
|
||||
TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
||||
(calendar, largestUnit) => {
|
||||
const earlier = new Temporal.ZonedDateTime(0n, "UTC", calendar);
|
||||
const earlier = new Temporal.ZonedDateTime(-31536000_000_000_000n /* = -365 days */, "UTC", calendar);
|
||||
const later = new Temporal.ZonedDateTime(86_399_999_999_999n, "UTC", calendar);
|
||||
later.since(earlier, { largestUnit, roundingIncrement: 2, roundingMode: 'ceil' });
|
||||
},
|
||||
{
|
||||
years: ["year", "day", "day", "day"],
|
||||
months: ["month", "day", "day", "day"],
|
||||
weeks: ["week", "day", "day", "day"],
|
||||
days: ["day", "day", "day", "day"],
|
||||
years: ["year", "year"],
|
||||
months: ["month", "month"],
|
||||
weeks: ["week", "week"],
|
||||
days: [],
|
||||
hours: [],
|
||||
minutes: [],
|
||||
seconds: [],
|
||||
|
@ -100,10 +100,10 @@ TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
|||
later.since(earlier, { smallestUnit });
|
||||
},
|
||||
{
|
||||
years: ["year", "day", "day", "year"],
|
||||
months: ["month", "day", "day"],
|
||||
weeks: ["week", "day", "day"],
|
||||
days: ["day", "day", "day"],
|
||||
years: ["year", "year"],
|
||||
months: ["month"],
|
||||
weeks: ["week"],
|
||||
days: [],
|
||||
hours: [],
|
||||
minutes: [],
|
||||
seconds: [],
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
// Copyright (C) 2022 Igalia, S.L. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-temporal.zoneddatetime.prototype.since
|
||||
description: >
|
||||
NanosecondsToDays can loop arbitrarily long, performing observable operations each iteration.
|
||||
info: |
|
||||
NanosecondsToDays ( nanoseconds, relativeTo )
|
||||
|
||||
...
|
||||
15. If sign is 1, then
|
||||
a. Repeat, while days > 0 and intermediateNs > endNs,
|
||||
i. Set days to days - 1.
|
||||
ii. Set intermediateNs to ℝ(? AddZonedDateTime(ℤ(startNs), relativeTo.[[TimeZone]],
|
||||
relativeTo.[[Calendar]], 0, 0, 0, days, 0, 0, 0, 0, 0, 0)).
|
||||
...
|
||||
includes: [temporalHelpers.js]
|
||||
features: [Temporal]
|
||||
---*/
|
||||
|
||||
const calls = [];
|
||||
const dayLengthNs = 86400000000000n;
|
||||
const other = new Temporal.ZonedDateTime(dayLengthNs, "UTC", "iso8601");
|
||||
|
||||
function createRelativeTo(count) {
|
||||
const tz = new Temporal.TimeZone("UTC");
|
||||
// Record calls in calls[]
|
||||
TemporalHelpers.observeMethod(calls, tz, "getPossibleInstantsFor");
|
||||
const cal = new Temporal.Calendar("iso8601");
|
||||
// Return _count_ days for the second call to dateUntil, behaving normally after
|
||||
TemporalHelpers.substituteMethod(cal, "dateUntil", [
|
||||
TemporalHelpers.SUBSTITUTE_SKIP,
|
||||
Temporal.Duration.from({ days: count }),
|
||||
]);
|
||||
return new Temporal.ZonedDateTime(0n, tz, cal);
|
||||
}
|
||||
|
||||
let zdt = createRelativeTo(200);
|
||||
calls.splice(0); // Reset calls list after ZonedDateTime construction
|
||||
zdt.since(other, {
|
||||
largestUnit: "day",
|
||||
});
|
||||
assert.sameValue(
|
||||
calls.length,
|
||||
200 + 1,
|
||||
"Expected ZonedDateTime.since to call getPossibleInstantsFor correct number of times"
|
||||
);
|
||||
|
||||
zdt = createRelativeTo(300);
|
||||
calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
|
||||
zdt.since(other, {
|
||||
largestUnit: "day",
|
||||
});
|
||||
assert.sameValue(
|
||||
calls.length,
|
||||
300 + 1,
|
||||
"Expected ZonedDateTime.since to call getPossibleInstantsFor correct number of times"
|
||||
);
|
|
@ -108,6 +108,18 @@ const ownTimeZone = TemporalHelpers.timeZoneObserver(actual, "this.timeZone");
|
|||
const ownCalendar = TemporalHelpers.calendarObserver(actual, "this.calendar");
|
||||
const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, ownTimeZone, ownCalendar);
|
||||
|
||||
const dstTimeZone = TemporalHelpers.springForwardFallBackTimeZone();
|
||||
const ownDstTimeZone = TemporalHelpers.timeZoneObserver(actual, "this.timeZone", {
|
||||
getOffsetNanosecondsFor: dstTimeZone.getOffsetNanosecondsFor,
|
||||
getPossibleInstantsFor: dstTimeZone.getPossibleInstantsFor,
|
||||
});
|
||||
const otherDstTimeZone = TemporalHelpers.timeZoneObserver(actual, "other.timeZone", {
|
||||
getOffsetNanosecondsFor: dstTimeZone.getOffsetNanosecondsFor,
|
||||
getPossibleInstantsFor: dstTimeZone.getPossibleInstantsFor,
|
||||
});
|
||||
/* 2000-10-29T01:30-07:00, in the middle of the first repeated hour: */
|
||||
const fallBackInstance = new Temporal.ZonedDateTime(972808200_000_000_000n, ownDstTimeZone, ownCalendar);
|
||||
|
||||
const otherDateTimePropertyBag = TemporalHelpers.propertyBagObserver(actual, {
|
||||
year: 2004,
|
||||
month: 5,
|
||||
|
@ -169,6 +181,139 @@ assert.compareArray(actual, expected.concat([
|
|||
]), "order of operations with identical dates and largestUnit a calendar unit");
|
||||
actual.splice(0); // clear
|
||||
|
||||
// two ZonedDateTimes that denote the same wall-clock time in the time zone can
|
||||
// avoid calling some calendar methods:
|
||||
const fallBackPropertyBag = TemporalHelpers.propertyBagObserver(actual, {
|
||||
year: 2000,
|
||||
month: 10,
|
||||
monthCode: "M10",
|
||||
day: 29,
|
||||
hour: 1,
|
||||
minute: 30,
|
||||
second: 0,
|
||||
millisecond: 0,
|
||||
microsecond: 0,
|
||||
nanosecond: 0,
|
||||
offset: "-08:00",
|
||||
calendar: TemporalHelpers.calendarObserver(actual, "other.calendar"),
|
||||
timeZone: otherDstTimeZone,
|
||||
}, "other");
|
||||
fallBackInstance.since(fallBackPropertyBag, createOptionsObserver({ largestUnit: "days" }));
|
||||
assert.compareArray(actual, [
|
||||
// ToTemporalZonedDateTime
|
||||
"get other.calendar",
|
||||
"has other.calendar.dateAdd",
|
||||
"has other.calendar.dateFromFields",
|
||||
"has other.calendar.dateUntil",
|
||||
"has other.calendar.day",
|
||||
"has other.calendar.dayOfWeek",
|
||||
"has other.calendar.dayOfYear",
|
||||
"has other.calendar.daysInMonth",
|
||||
"has other.calendar.daysInWeek",
|
||||
"has other.calendar.daysInYear",
|
||||
"has other.calendar.fields",
|
||||
"has other.calendar.id",
|
||||
"has other.calendar.inLeapYear",
|
||||
"has other.calendar.mergeFields",
|
||||
"has other.calendar.month",
|
||||
"has other.calendar.monthCode",
|
||||
"has other.calendar.monthDayFromFields",
|
||||
"has other.calendar.monthsInYear",
|
||||
"has other.calendar.weekOfYear",
|
||||
"has other.calendar.year",
|
||||
"has other.calendar.yearMonthFromFields",
|
||||
"has other.calendar.yearOfWeek",
|
||||
"get other.calendar.fields",
|
||||
"call other.calendar.fields",
|
||||
"get other.day",
|
||||
"get other.day.valueOf",
|
||||
"call other.day.valueOf",
|
||||
"get other.hour",
|
||||
"get other.hour.valueOf",
|
||||
"call other.hour.valueOf",
|
||||
"get other.microsecond",
|
||||
"get other.microsecond.valueOf",
|
||||
"call other.microsecond.valueOf",
|
||||
"get other.millisecond",
|
||||
"get other.millisecond.valueOf",
|
||||
"call other.millisecond.valueOf",
|
||||
"get other.minute",
|
||||
"get other.minute.valueOf",
|
||||
"call other.minute.valueOf",
|
||||
"get other.month",
|
||||
"get other.month.valueOf",
|
||||
"call other.month.valueOf",
|
||||
"get other.monthCode",
|
||||
"get other.monthCode.toString",
|
||||
"call other.monthCode.toString",
|
||||
"get other.nanosecond",
|
||||
"get other.nanosecond.valueOf",
|
||||
"call other.nanosecond.valueOf",
|
||||
"get other.offset",
|
||||
"get other.offset.toString",
|
||||
"call other.offset.toString",
|
||||
"get other.second",
|
||||
"get other.second.valueOf",
|
||||
"call other.second.valueOf",
|
||||
"get other.timeZone",
|
||||
"get other.year",
|
||||
"get other.year.valueOf",
|
||||
"call other.year.valueOf",
|
||||
"has other.timeZone.getOffsetNanosecondsFor",
|
||||
"has other.timeZone.getPossibleInstantsFor",
|
||||
"has other.timeZone.id",
|
||||
"get other.calendar.dateFromFields",
|
||||
"call other.calendar.dateFromFields",
|
||||
"get other.timeZone.getPossibleInstantsFor",
|
||||
"call other.timeZone.getPossibleInstantsFor",
|
||||
"get other.timeZone.getOffsetNanosecondsFor",
|
||||
"call other.timeZone.getOffsetNanosecondsFor",
|
||||
// NOTE: extra because of wall-clock time ambiguity:
|
||||
"get other.timeZone.getOffsetNanosecondsFor",
|
||||
"call other.timeZone.getOffsetNanosecondsFor",
|
||||
// CalendarEquals
|
||||
"get this.calendar.id",
|
||||
"get other.calendar.id",
|
||||
// CopyDataProperties
|
||||
"ownKeys options",
|
||||
"getOwnPropertyDescriptor options.roundingIncrement",
|
||||
"get options.roundingIncrement",
|
||||
"getOwnPropertyDescriptor options.roundingMode",
|
||||
"get options.roundingMode",
|
||||
"getOwnPropertyDescriptor options.largestUnit",
|
||||
"get options.largestUnit",
|
||||
"getOwnPropertyDescriptor options.smallestUnit",
|
||||
"get options.smallestUnit",
|
||||
"getOwnPropertyDescriptor options.additional",
|
||||
"get options.additional",
|
||||
// GetDifferenceSettings
|
||||
"get options.largestUnit.toString",
|
||||
"call options.largestUnit.toString",
|
||||
"get options.roundingIncrement.valueOf",
|
||||
"call options.roundingIncrement.valueOf",
|
||||
"get options.roundingMode.toString",
|
||||
"call options.roundingMode.toString",
|
||||
"get options.smallestUnit.toString",
|
||||
"call options.smallestUnit.toString",
|
||||
// TimeZoneEquals
|
||||
"get this.timeZone.id",
|
||||
"get other.timeZone.id",
|
||||
// DifferenceZonedDateTime
|
||||
"get this.timeZone.getOffsetNanosecondsFor",
|
||||
"call this.timeZone.getOffsetNanosecondsFor",
|
||||
"get this.timeZone.getOffsetNanosecondsFor",
|
||||
"call this.timeZone.getOffsetNanosecondsFor",
|
||||
// NanosecondsToDays
|
||||
"get this.timeZone.getOffsetNanosecondsFor",
|
||||
"call this.timeZone.getOffsetNanosecondsFor",
|
||||
"get this.timeZone.getOffsetNanosecondsFor",
|
||||
"call this.timeZone.getOffsetNanosecondsFor",
|
||||
// NanosecondsToDays → AddDaysToZonedDateTime
|
||||
"get this.timeZone.getPossibleInstantsFor",
|
||||
"call this.timeZone.getPossibleInstantsFor",
|
||||
], "order of operations with identical wall-clock times and largestUnit a calendar unit");
|
||||
actual.splice(0); // clear
|
||||
|
||||
// Making largestUnit a calendar unit adds the following observable operations:
|
||||
const expectedOpsForCalendarDifference = [
|
||||
// TimeZoneEquals
|
||||
|
@ -192,9 +337,6 @@ const expectedOpsForCalendarDifference = [
|
|||
"call this.timeZone.getOffsetNanosecondsFor",
|
||||
"get this.timeZone.getOffsetNanosecondsFor",
|
||||
"call this.timeZone.getOffsetNanosecondsFor",
|
||||
// NanosecondsToDays → DifferenceISODateTime
|
||||
"get this.calendar.dateUntil",
|
||||
"call this.calendar.dateUntil",
|
||||
// NanosecondsToDays → AddDaysToZonedDateTime
|
||||
"get this.timeZone.getPossibleInstantsFor",
|
||||
"call this.timeZone.getPossibleInstantsFor",
|
||||
|
@ -218,9 +360,6 @@ const expectedOpsForCalendarRounding = [
|
|||
"call this.timeZone.getOffsetNanosecondsFor",
|
||||
"get this.timeZone.getOffsetNanosecondsFor",
|
||||
"call this.timeZone.getOffsetNanosecondsFor",
|
||||
// RoundDuration → NanosecondsToDays → DifferenceISODateTime
|
||||
"get this.calendar.dateUntil",
|
||||
"call this.calendar.dateUntil",
|
||||
// RoundDuration → NanosecondsToDays → AddDaysToZonedDateTime
|
||||
"get this.timeZone.getPossibleInstantsFor",
|
||||
"call this.timeZone.getPossibleInstantsFor",
|
||||
|
|
|
@ -14,4 +14,4 @@ const calendar = TemporalHelpers.calendarCheckOptionsPrototypePollution();
|
|||
const instance = new Temporal.ZonedDateTime(0n, "UTC", calendar);
|
||||
const argument = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
|
||||
instance.until(argument, { largestUnit: "year" });
|
||||
assert.sameValue(calendar.dateUntilCallCount, 2, "dateUntil should have been called on the calendar");
|
||||
assert.sameValue(calendar.dateUntilCallCount, 1, "dateUntil should have been called on the calendar");
|
||||
|
|
|
@ -55,10 +55,10 @@ TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
|||
earlier.until(later, { largestUnit });
|
||||
},
|
||||
{
|
||||
years: ["year", "day"],
|
||||
months: ["month", "day"],
|
||||
weeks: ["week", "day"],
|
||||
days: ["day", "day"],
|
||||
years: ["year"],
|
||||
months: ["month"],
|
||||
weeks: ["week"],
|
||||
days: [],
|
||||
hours: [],
|
||||
minutes: [],
|
||||
seconds: [],
|
||||
|
@ -72,15 +72,15 @@ TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
|||
|
||||
TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
||||
(calendar, largestUnit) => {
|
||||
const earlier = new Temporal.ZonedDateTime(0n, "UTC", calendar);
|
||||
const earlier = new Temporal.ZonedDateTime(-31536000_000_000_000n /* = -365 days */, "UTC", calendar);
|
||||
const later = new Temporal.ZonedDateTime(86_399_999_999_999n, "UTC", calendar);
|
||||
earlier.until(later, { largestUnit, roundingIncrement: 2, roundingMode: 'ceil' });
|
||||
},
|
||||
{
|
||||
years: ["year", "day", "day", "day"],
|
||||
months: ["month", "day", "day", "day"],
|
||||
weeks: ["week", "day", "day", "day"],
|
||||
days: ["day", "day", "day", "day"],
|
||||
years: ["year", "year"],
|
||||
months: ["month", "month"],
|
||||
weeks: ["week", "week"],
|
||||
days: [],
|
||||
hours: [],
|
||||
minutes: [],
|
||||
seconds: [],
|
||||
|
@ -100,10 +100,10 @@ TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
|
|||
earlier.until(later, { smallestUnit });
|
||||
},
|
||||
{
|
||||
years: ["year", "day", "day", "year"],
|
||||
months: ["month", "day", "day"],
|
||||
weeks: ["week", "day", "day"],
|
||||
days: ["day", "day", "day"],
|
||||
years: ["year", "year"],
|
||||
months: ["month"],
|
||||
weeks: ["week"],
|
||||
days: [],
|
||||
hours: [],
|
||||
minutes: [],
|
||||
seconds: [],
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
// Copyright (C) 2022 Igalia, S.L. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-temporal.zoneddatetime.prototype.until
|
||||
description: >
|
||||
NanosecondsToDays can loop arbitrarily long, performing observable operations each iteration.
|
||||
info: |
|
||||
NanosecondsToDays ( nanoseconds, relativeTo )
|
||||
|
||||
...
|
||||
15. If sign is 1, then
|
||||
a. Repeat, while days > 0 and intermediateNs > endNs,
|
||||
i. Set days to days - 1.
|
||||
ii. Set intermediateNs to ℝ(? AddZonedDateTime(ℤ(startNs), relativeTo.[[TimeZone]],
|
||||
relativeTo.[[Calendar]], 0, 0, 0, days, 0, 0, 0, 0, 0, 0)).
|
||||
...
|
||||
includes: [temporalHelpers.js]
|
||||
features: [Temporal]
|
||||
---*/
|
||||
|
||||
const calls = [];
|
||||
const dayLengthNs = 86400000000000n;
|
||||
const other = new Temporal.ZonedDateTime(dayLengthNs, "UTC", "iso8601");
|
||||
|
||||
function createRelativeTo(count) {
|
||||
const tz = new Temporal.TimeZone("UTC");
|
||||
// Record calls in calls[]
|
||||
TemporalHelpers.observeMethod(calls, tz, "getPossibleInstantsFor");
|
||||
const cal = new Temporal.Calendar("iso8601");
|
||||
// Return _count_ days for the second call to dateUntil, behaving normally after
|
||||
TemporalHelpers.substituteMethod(cal, "dateUntil", [
|
||||
TemporalHelpers.SUBSTITUTE_SKIP,
|
||||
Temporal.Duration.from({ days: count }),
|
||||
]);
|
||||
return new Temporal.ZonedDateTime(0n, tz, cal);
|
||||
}
|
||||
|
||||
let zdt = createRelativeTo(200);
|
||||
calls.splice(0); // Reset calls list after ZonedDateTime construction
|
||||
zdt.until(other, {
|
||||
largestUnit: "day",
|
||||
});
|
||||
assert.sameValue(
|
||||
calls.length,
|
||||
200 + 1,
|
||||
"Expected ZonedDateTime.until to call getPossibleInstantsFor correct number of times"
|
||||
);
|
||||
|
||||
zdt = createRelativeTo(300);
|
||||
calls.splice(0); // Reset calls list after previous loop + ZonedDateTime construction
|
||||
zdt.until(other, {
|
||||
largestUnit: "day",
|
||||
});
|
||||
assert.sameValue(
|
||||
calls.length,
|
||||
300 + 1,
|
||||
"Expected ZonedDateTime.until to call getPossibleInstantsFor correct number of times"
|
||||
);
|
|
@ -108,6 +108,18 @@ const ownTimeZone = TemporalHelpers.timeZoneObserver(actual, "this.timeZone");
|
|||
const ownCalendar = TemporalHelpers.calendarObserver(actual, "this.calendar");
|
||||
const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, ownTimeZone, ownCalendar);
|
||||
|
||||
const dstTimeZone = TemporalHelpers.springForwardFallBackTimeZone();
|
||||
const ownDstTimeZone = TemporalHelpers.timeZoneObserver(actual, "this.timeZone", {
|
||||
getOffsetNanosecondsFor: dstTimeZone.getOffsetNanosecondsFor,
|
||||
getPossibleInstantsFor: dstTimeZone.getPossibleInstantsFor,
|
||||
});
|
||||
const otherDstTimeZone = TemporalHelpers.timeZoneObserver(actual, "other.timeZone", {
|
||||
getOffsetNanosecondsFor: dstTimeZone.getOffsetNanosecondsFor,
|
||||
getPossibleInstantsFor: dstTimeZone.getPossibleInstantsFor,
|
||||
});
|
||||
/* 2000-10-29T01:30-07:00, in the middle of the first repeated hour: */
|
||||
const fallBackInstance = new Temporal.ZonedDateTime(972808200_000_000_000n, ownDstTimeZone, ownCalendar);
|
||||
|
||||
const otherDateTimePropertyBag = TemporalHelpers.propertyBagObserver(actual, {
|
||||
year: 2004,
|
||||
month: 5,
|
||||
|
@ -169,6 +181,139 @@ assert.compareArray(actual, expected.concat([
|
|||
]), "order of operations with identical dates and largestUnit a calendar unit");
|
||||
actual.splice(0); // clear
|
||||
|
||||
// two ZonedDateTimes that denote the same wall-clock time in the time zone can
|
||||
// avoid calling some calendar methods:
|
||||
const fallBackPropertyBag = TemporalHelpers.propertyBagObserver(actual, {
|
||||
year: 2000,
|
||||
month: 10,
|
||||
monthCode: "M10",
|
||||
day: 29,
|
||||
hour: 1,
|
||||
minute: 30,
|
||||
second: 0,
|
||||
millisecond: 0,
|
||||
microsecond: 0,
|
||||
nanosecond: 0,
|
||||
offset: "-08:00",
|
||||
calendar: TemporalHelpers.calendarObserver(actual, "other.calendar"),
|
||||
timeZone: otherDstTimeZone,
|
||||
}, "other");
|
||||
fallBackInstance.until(fallBackPropertyBag, createOptionsObserver({ largestUnit: "days" }));
|
||||
assert.compareArray(actual, [
|
||||
// ToTemporalZonedDateTime
|
||||
"get other.calendar",
|
||||
"has other.calendar.dateAdd",
|
||||
"has other.calendar.dateFromFields",
|
||||
"has other.calendar.dateUntil",
|
||||
"has other.calendar.day",
|
||||
"has other.calendar.dayOfWeek",
|
||||
"has other.calendar.dayOfYear",
|
||||
"has other.calendar.daysInMonth",
|
||||
"has other.calendar.daysInWeek",
|
||||
"has other.calendar.daysInYear",
|
||||
"has other.calendar.fields",
|
||||
"has other.calendar.id",
|
||||
"has other.calendar.inLeapYear",
|
||||
"has other.calendar.mergeFields",
|
||||
"has other.calendar.month",
|
||||
"has other.calendar.monthCode",
|
||||
"has other.calendar.monthDayFromFields",
|
||||
"has other.calendar.monthsInYear",
|
||||
"has other.calendar.weekOfYear",
|
||||
"has other.calendar.year",
|
||||
"has other.calendar.yearMonthFromFields",
|
||||
"has other.calendar.yearOfWeek",
|
||||
"get other.calendar.fields",
|
||||
"call other.calendar.fields",
|
||||
"get other.day",
|
||||
"get other.day.valueOf",
|
||||
"call other.day.valueOf",
|
||||
"get other.hour",
|
||||
"get other.hour.valueOf",
|
||||
"call other.hour.valueOf",
|
||||
"get other.microsecond",
|
||||
"get other.microsecond.valueOf",
|
||||
"call other.microsecond.valueOf",
|
||||
"get other.millisecond",
|
||||
"get other.millisecond.valueOf",
|
||||
"call other.millisecond.valueOf",
|
||||
"get other.minute",
|
||||
"get other.minute.valueOf",
|
||||
"call other.minute.valueOf",
|
||||
"get other.month",
|
||||
"get other.month.valueOf",
|
||||
"call other.month.valueOf",
|
||||
"get other.monthCode",
|
||||
"get other.monthCode.toString",
|
||||
"call other.monthCode.toString",
|
||||
"get other.nanosecond",
|
||||
"get other.nanosecond.valueOf",
|
||||
"call other.nanosecond.valueOf",
|
||||
"get other.offset",
|
||||
"get other.offset.toString",
|
||||
"call other.offset.toString",
|
||||
"get other.second",
|
||||
"get other.second.valueOf",
|
||||
"call other.second.valueOf",
|
||||
"get other.timeZone",
|
||||
"get other.year",
|
||||
"get other.year.valueOf",
|
||||
"call other.year.valueOf",
|
||||
"has other.timeZone.getOffsetNanosecondsFor",
|
||||
"has other.timeZone.getPossibleInstantsFor",
|
||||
"has other.timeZone.id",
|
||||
"get other.calendar.dateFromFields",
|
||||
"call other.calendar.dateFromFields",
|
||||
"get other.timeZone.getPossibleInstantsFor",
|
||||
"call other.timeZone.getPossibleInstantsFor",
|
||||
"get other.timeZone.getOffsetNanosecondsFor",
|
||||
"call other.timeZone.getOffsetNanosecondsFor",
|
||||
// NOTE: extra because of wall-clock time ambiguity:
|
||||
"get other.timeZone.getOffsetNanosecondsFor",
|
||||
"call other.timeZone.getOffsetNanosecondsFor",
|
||||
// CalendarEquals
|
||||
"get this.calendar.id",
|
||||
"get other.calendar.id",
|
||||
// CopyDataProperties
|
||||
"ownKeys options",
|
||||
"getOwnPropertyDescriptor options.roundingIncrement",
|
||||
"get options.roundingIncrement",
|
||||
"getOwnPropertyDescriptor options.roundingMode",
|
||||
"get options.roundingMode",
|
||||
"getOwnPropertyDescriptor options.largestUnit",
|
||||
"get options.largestUnit",
|
||||
"getOwnPropertyDescriptor options.smallestUnit",
|
||||
"get options.smallestUnit",
|
||||
"getOwnPropertyDescriptor options.additional",
|
||||
"get options.additional",
|
||||
// GetDifferenceSettings
|
||||
"get options.largestUnit.toString",
|
||||
"call options.largestUnit.toString",
|
||||
"get options.roundingIncrement.valueOf",
|
||||
"call options.roundingIncrement.valueOf",
|
||||
"get options.roundingMode.toString",
|
||||
"call options.roundingMode.toString",
|
||||
"get options.smallestUnit.toString",
|
||||
"call options.smallestUnit.toString",
|
||||
// TimeZoneEquals
|
||||
"get this.timeZone.id",
|
||||
"get other.timeZone.id",
|
||||
// DifferenceZonedDateTime
|
||||
"get this.timeZone.getOffsetNanosecondsFor",
|
||||
"call this.timeZone.getOffsetNanosecondsFor",
|
||||
"get this.timeZone.getOffsetNanosecondsFor",
|
||||
"call this.timeZone.getOffsetNanosecondsFor",
|
||||
// NanosecondsToDays
|
||||
"get this.timeZone.getOffsetNanosecondsFor",
|
||||
"call this.timeZone.getOffsetNanosecondsFor",
|
||||
"get this.timeZone.getOffsetNanosecondsFor",
|
||||
"call this.timeZone.getOffsetNanosecondsFor",
|
||||
// NanosecondsToDays → AddDaysToZonedDateTime
|
||||
"get this.timeZone.getPossibleInstantsFor",
|
||||
"call this.timeZone.getPossibleInstantsFor",
|
||||
], "order of operations with identical wall-clock times and largestUnit a calendar unit");
|
||||
actual.splice(0); // clear
|
||||
|
||||
// Making largestUnit a calendar unit adds the following observable operations:
|
||||
const expectedOpsForCalendarDifference = [
|
||||
// TimeZoneEquals
|
||||
|
@ -192,9 +337,6 @@ const expectedOpsForCalendarDifference = [
|
|||
"call this.timeZone.getOffsetNanosecondsFor",
|
||||
"get this.timeZone.getOffsetNanosecondsFor",
|
||||
"call this.timeZone.getOffsetNanosecondsFor",
|
||||
// NanosecondsToDays → DifferenceISODateTime
|
||||
"get this.calendar.dateUntil",
|
||||
"call this.calendar.dateUntil",
|
||||
// NanosecondsToDays → AddDaysToZonedDateTime
|
||||
"get this.timeZone.getPossibleInstantsFor",
|
||||
"call this.timeZone.getPossibleInstantsFor",
|
||||
|
@ -218,9 +360,6 @@ const expectedOpsForCalendarRounding = [
|
|||
"call this.timeZone.getOffsetNanosecondsFor",
|
||||
"get this.timeZone.getOffsetNanosecondsFor",
|
||||
"call this.timeZone.getOffsetNanosecondsFor",
|
||||
// RoundDuration → NanosecondsToDays → DifferenceISODateTime
|
||||
"get this.calendar.dateUntil",
|
||||
"call this.calendar.dateUntil",
|
||||
// RoundDuration → NanosecondsToDays → AddDaysToZonedDateTime
|
||||
"get this.timeZone.getPossibleInstantsFor",
|
||||
"call this.timeZone.getPossibleInstantsFor",
|
||||
|
|
Loading…
Reference in New Issue