Temporal: Remove BigInt arithmetic and loops in UnbalanceDurationRelative

This commit is contained in:
Philip Chimento 2023-05-11 15:42:50 -07:00 committed by Philip Chimento
parent b218cf6dec
commit 50abc12d97
11 changed files with 62 additions and 231 deletions

View File

@ -1137,25 +1137,6 @@ var TemporalHelpers = {
return new CalendarDateAddPlainDateInstance();
},
/*
* A custom calendar that returns @returnValue from its dateUntil() method,
* recording the call in @calls.
*/
calendarDateUntilObservable(calls, returnValue) {
class CalendarDateUntilObservable extends Temporal.Calendar {
constructor() {
super("iso8601");
}
dateUntil() {
calls.push("call dateUntil");
return returnValue;
}
}
return new CalendarDateUntilObservable();
},
/*
* A custom calendar that returns an iterable instead of an array from its
* fields() method, otherwise identical to the ISO calendar.

View File

@ -181,14 +181,8 @@ actual.splice(0); // clear
// to days:
const expectedOpsForPlainDayBalancing = expectedOpsForPlainRelativeTo.concat(
[
// UnbalanceDurationRelative
"call options.relativeTo.calendar.dateAdd", // 11.a.iii.1 MoveRelativeDate
"call options.relativeTo.calendar.dateAdd", // 11.a.iv.1 MoveRelativeDate
"call options.relativeTo.calendar.dateAdd", // 11.a.v.1 MoveRelativeDate
// UnbalanceDurationRelative again for the second argument:
"call options.relativeTo.calendar.dateAdd", // 11.a.iii.1 MoveRelativeDate
"call options.relativeTo.calendar.dateAdd", // 11.a.iv.1 MoveRelativeDate
"call options.relativeTo.calendar.dateAdd", // 11.a.v.1 MoveRelativeDate
"call options.relativeTo.calendar.dateAdd", // UnbalanceDateDurationRelative on 1st argument
"call options.relativeTo.calendar.dateAdd", // UnbalanceDateDurationRelative on 2nd argument
]
);
Temporal.Duration.compare(

View File

@ -1,68 +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.compare
description: >
Duration components are precise mathematical integers.
info: |
Temporal.Duration.compare ( one, two [ , options ] )
...
7. If any of one.[[Years]], two.[[Years]], one.[[Months]], two.[[Months]], one.[[Weeks]], or
two.[[Weeks]] are not 0, then
a. Let unbalanceResult1 be ? UnbalanceDurationRelative(one.[[Years]], one.[[Months]],
one.[[Weeks]], one.[[Days]], "day", relativeTo).
...
9. Let ns1 be ! TotalDurationNanoseconds(days1, one.[[Hours]], one.[[Minutes]], one.[[Seconds]],
one.[[Milliseconds]], one.[[Microseconds]], one.[[Nanoseconds]], shift1).
10. Let ns2 be ! TotalDurationNanoseconds(days2, two.[[Hours]], two.[[Minutes]], two.[[Seconds]],
two.[[Milliseconds]], two.[[Microseconds]], two.[[Nanoseconds]], shift2).
11. If ns1 > ns2, return 1𝔽.
12. If ns1 < ns2, return -1𝔽.
13. Return +0𝔽.
UnbalanceDurationRelative ( years, months, weeks, days, largestUnit, relativeTo )
...
11. Else,
a. If any of years, months, and weeks are not zero, then
...
iv. Repeat, while weeks 0,
1. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneWeek).
2. Set relativeTo to moveResult.[[RelativeTo]].
3. Set days to days + moveResult.[[Days]].
4. Set weeks to weeks - sign.
12. Return ? CreateDateDurationRecord(years, months, weeks, days).
features: [Temporal]
---*/
var one = Temporal.Duration.from({
days: Number.MAX_SAFE_INTEGER,
weeks: 3,
});
var two = Temporal.Duration.from({
days: Number.MAX_SAFE_INTEGER + 3,
weeks: 0,
});
var cal = new class extends Temporal.Calendar {
dateAdd(date, duration, options) {
// Add one day when one week was requested.
if (duration.toString() === "P1W") {
return super.dateAdd(date, "P1D", options);
}
// Only expect to add one week.
throw new Test262Error("dateAdd called with unexpected value");
}
}("iso8601");
var pd = new Temporal.PlainDate(1970, 1, 1, cal);
// |Number.MAX_SAFE_INTEGER + 1 + 1 + 1| is unequal to |Number.MAX_SAFE_INTEGER + 3|
// when the addition is performed using IEEE-754 semantics. But for compare we have
// to ensure exact mathematical computations are performed.
assert.sameValue(Temporal.Duration.compare(one, two, {relativeTo: pd}), 0);

View File

@ -1,69 +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.compare
description: >
Duration components are precise mathematical integers.
info: |
Temporal.Duration.compare ( one, two [ , options ] )
...
7. If any of one.[[Years]], two.[[Years]], one.[[Months]], two.[[Months]], one.[[Weeks]], or
two.[[Weeks]] are not 0, then
a. Let unbalanceResult1 be ? UnbalanceDurationRelative(one.[[Years]], one.[[Months]],
one.[[Weeks]], one.[[Days]], "day", relativeTo).
...
9. Let ns1 be ! TotalDurationNanoseconds(days1, one.[[Hours]], one.[[Minutes]], one.[[Seconds]],
one.[[Milliseconds]], one.[[Microseconds]], one.[[Nanoseconds]], shift1).
10. Let ns2 be ! TotalDurationNanoseconds(days2, two.[[Hours]], two.[[Minutes]], two.[[Seconds]],
two.[[Milliseconds]], two.[[Microseconds]], two.[[Nanoseconds]], shift2).
11. If ns1 > ns2, return 1𝔽.
12. If ns1 < ns2, return -1𝔽.
13. Return +0𝔽.
UnbalanceDurationRelative ( years, months, weeks, days, largestUnit, relativeTo )
...
11. Else,
a. If any of years, months, and weeks are not zero, then
...
iv. Repeat, while weeks 0,
1. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneWeek).
2. Set relativeTo to moveResult.[[RelativeTo]].
3. Set days to days + moveResult.[[Days]].
4. Set weeks to weeks - sign.
12. Return ? CreateDateDurationRecord(years, months, weeks, days).
features: [Temporal]
---*/
var one = Temporal.Duration.from({
days: Number.MAX_SAFE_INTEGER,
weeks: 3,
years: 1,
});
var two = Temporal.Duration.from({
days: Number.MAX_SAFE_INTEGER + 3,
years: 1,
});
var cal = new class extends Temporal.Calendar {
dateAdd(date, duration, options) {
// Add one day when one week was requested.
if (duration.toString() === "P1W") {
return super.dateAdd(date, "P1D", options);
}
// Use zero duration to avoid a RangeError during CalculateOffsetShift.
return super.dateAdd(date, "PT0S", options);
}
}("iso8601");
var zdt = new Temporal.ZonedDateTime(0n, "UTC", cal);
// |Number.MAX_SAFE_INTEGER + 1 + 1 + 1| is unequal to |Number.MAX_SAFE_INTEGER + 3|
// when the addition is performed using IEEE-754 semantics. But for compare we have
// to ensure exact mathematical computations are performed.
assert.sameValue(Temporal.Duration.compare(one, two, {relativeTo: zdt}), 0);

View File

@ -6,22 +6,17 @@ esid: sec-temporal.duration.prototype.round
description: The options object passed to calendar.dateUntil has a largestUnit property with its value in the singular form
info: |
sec-temporal.duration.prototype.round steps 2327:
23. Let _unbalanceResult_ be ? UnbalanceDurationRelative(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]], _largestUnit_, _relativeTo_).
23. Let _unbalanceResult_ be ? UnbalanceDateDurationRelative(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]], _largestUnit_, _relativeTo_).
24. Let _roundResult_ be (? RoundDuration(_unbalanceResult_.[[Years]], _unbalanceResult_.[[Months]], _unbalanceResult_.[[Weeks]], _unbalanceResult_.[[Days]], _duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]], _roundingIncrement_, _smallestUnit_, _roundingMode_, _relativeTo_)).[[DurationRecord]].
25. Let _adjustResult_ be ? AdjustRoundedDurationDays(_roundResult_.[[Years]], _roundResult_.[[Months]], _roundResult_.[[Weeks]], _roundResult_.[[Days]], _roundResult_.[[Hours]], _roundResult_.[[Minutes]], _roundResult_.[[Seconds]], _roundResult_.[[Milliseconds]], _roundResult_.[[Microseconds]], _roundResult_.[[Nanoseconds]], _roundingIncrement_, _smallestUnit_, _roundingMode_, _relativeTo_).
26. Let _balanceResult_ be ? BalanceDuration(_adjustResult_.[[Days]], _adjustResult_.[[Hours]], _adjustResult_.[[Minutes]], _adjustResult_.[[Seconds]], _adjustResult_.[[Milliseconds]], _adjustResult_.[[Microseconds]], _adjustResult_.[[Nanoseconds]], _largestUnit_, _relativeTo_).
27. Let _result_ be ? BalanceDurationRelative(_adjustResult_.[[Years]], _adjustResult_.[[Months]], _adjustResult_.[[Weeks]], _balanceResult_.[[Days]], _largestUnit_, _relativeTo_).
sec-temporal-unbalancedurationrelative steps 1 and 9.d.iiiv:
1. If _largestUnit_ is *"year"*, or _years_, _months_, _weeks_, and _days_ are all 0, then
a. Return ...
...
9. If _largestUnit_ is *"month"*, then
sec-temporal-unbalancedatedurationrelative step 3:
3. If _largestUnit_ is *"month"*, then
...
d. Repeat, while abs(_years_) > 0,
...
iii. Let _untilOptions_ be ! OrdinaryObjectCreate(*null*).
iv. Perform ! CreateDataPropertyOrThrow(_untilOptions_, *"largestUnit"*, *"month"*).
v. Let _untilResult_ be ? CalendarDateUntil(_calendar_, _relativeTo_, _newRelativeTo_, _untilOptions_, _dateUntil_).
g. Let _untilOptions_ be ! OrdinaryObjectCreate(*null*).
h. Perform ! CreateDataPropertyOrThrow(_untilOptions_, *"largestUnit"*, *"month"*).
i. Let _untilResult_ be ? CalendarDateUntil(_calendarRec_.[[Receiver]], _plainRelativeTo_, _later_, _untilOptions_, _calendarRec_.[[DateUntil]]).
sec-temporal-roundduration steps 5.d and 8.np:
5. If _unit_ is one of *"year"*, *"month"*, *"week"*, or *"day"*, then
...
@ -128,7 +123,7 @@ TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
},
{
years: ["month", "month", "month", "month", "month", "month"],
months: ["month", "month", "month", "month", "month"],
months: ["month"],
weeks: [],
days: [],
hours: [],

View File

@ -10,19 +10,25 @@ includes: [compareArray.js, temporalHelpers.js]
features: [Temporal]
---*/
// One path, through UnbalanceDurationRelative, calls dateUntil() in a loop for
// each year in the duration
const actual = [];
class CalendarDateUntilObservable extends Temporal.Calendar {
dateUntil(...args) {
actual.push("call dateUntil");
const returnValue = super.dateUntil(...args);
TemporalHelpers.observeProperty(actual, returnValue, "months", Infinity);
return returnValue;
}
}
const calendar = new CalendarDateUntilObservable("iso8601");
const relativeTo = new Temporal.PlainDate(2018, 10, 12, calendar);
// One path, through UnbalanceDateDurationRelative, calls dateUntil()
const expected1 = [
"call dateUntil",
"call dateUntil",
];
const duration = new Temporal.Duration(0, 12);
TemporalHelpers.observeProperty(actual, duration, "months", 1);
const calendar = TemporalHelpers.calendarDateUntilObservable(actual, duration);
const relativeTo = new Temporal.PlainDateTime(2018, 10, 12, 0, 0, 0, 0, 0, 0, calendar);
const years = new Temporal.Duration(2);
const result1 = years.round({ largestUnit: "months", relativeTo });

View File

@ -151,9 +151,9 @@ actual.splice(0); // clear
// code path through Duration.prototype.round that rounds to the nearest month:
const expectedOpsForMonthRounding = expectedOpsForPlainRelativeTo.concat([
// UnbalanceDurationRelative
"call options.relativeTo.calendar.dateAdd", // 9.d.i
"call options.relativeTo.calendar.dateUntil", // 9.d.iv
// UnbalanceDateDurationRelative
"call options.relativeTo.calendar.dateAdd", // 3.f
"call options.relativeTo.calendar.dateUntil", // 3.i
// RoundDuration
"call options.relativeTo.calendar.dateAdd", // 10.c
"call options.relativeTo.calendar.dateAdd", // 10.e
@ -169,9 +169,8 @@ actual.splice(0); // clear
// code path through Duration.prototype.round that rounds to the nearest week:
const expectedOpsForWeekRounding = expectedOpsForPlainRelativeTo.concat([
// UnbalanceDurationRelative
"call options.relativeTo.calendar.dateAdd", // 10.c.i MoveRelativeDate
"call options.relativeTo.calendar.dateAdd", // 10.d.i MoveRelativeDate
// UnbalanceDateDurationRelative
"call options.relativeTo.calendar.dateAdd", // 4.e
// RoundDuration
"call options.relativeTo.calendar.dateAdd", // 11.d MoveRelativeDate
], Array(58).fill("call options.relativeTo.calendar.dateAdd"), [ // 58× 11.g.iii MoveRelativeDate (52 + 4 + 2)
@ -185,9 +184,7 @@ actual.splice(0); // clear
// code path through UnbalanceDurationRelative that rounds to the nearest day:
const expectedOpsForDayRounding = expectedOpsForPlainRelativeTo.concat([
"call options.relativeTo.calendar.dateAdd", // 11.a.iii.1 MoveRelativeDate
"call options.relativeTo.calendar.dateAdd", // 11.a.iv.1 MoveRelativeDate
"call options.relativeTo.calendar.dateAdd", // 11.a.v.1 MoveRelativeDate
"call options.relativeTo.calendar.dateAdd", // 9
]);
const instance4 = new Temporal.Duration(1, 1, 1)
instance4.round(createOptionsObserver({ largestUnit: "days", smallestUnit: "days", relativeTo: plainRelativeTo }));
@ -427,7 +424,7 @@ const expectedOpsForUnbalanceRoundBalance = expectedOpsForZonedRelativeTo.concat
// lookup in Duration.p.round
"get options.relativeTo.calendar.dateAdd",
"get options.relativeTo.calendar.dateUntil",
// No user code calls in UnbalanceDurationRelative
// No user code calls in UnbalanceDateDurationRelative
// RoundDuration → MoveRelativeZonedDateTime → AddZonedDateTime
"call options.relativeTo.calendar.dateAdd",
"call options.relativeTo.timeZone.getPossibleInstantsFor", // 13. GetInstantFor

View File

@ -15,16 +15,13 @@ const timeZone = TemporalHelpers.oneShiftTimeZone(new Temporal.Instant(0n), 3600
const relativeTo = new Temporal.ZonedDateTime(0n, timeZone, calendar);
// Total of a calendar unit where larger calendar units have to be converted
// down, to cover the path that goes through UnbalanceDurationRelative
// The calls come from these paths:
// Duration.total() ->
// UnbalanceDurationRelative -> MoveRelativeDate -> calendar.dateAdd() (3x)
// BalanceDuration ->
// AddZonedDateTime -> BuiltinTimeZoneGetInstantFor -> calendar.dateAdd()
// down, to cover the path that goes through UnbalanceDateDurationRelative
// The calls come from the path:
// Duration.total() -> UnbalanceDateDurationRelative -> calendar.dateAdd()
const instance1 = new Temporal.Duration(1, 1, 1, 1, 1);
instance1.total({ unit: "days", relativeTo });
assert.sameValue(calendar.dateAddCallCount, 3, "converting larger calendar units down");
assert.sameValue(calendar.dateAddCallCount, 1, "converting larger calendar units down");
// Total of a calendar unit where smaller calendar units have to be converted
// up, to cover the path that goes through MoveRelativeZonedDateTime

View File

@ -6,21 +6,16 @@ esid: sec-temporal.duration.prototype.total
description: The options object passed to calendar.dateUntil has a largestUnit property with its value in the singular form
info: |
sec-temporal.duration.prototype.total steps 711:
7. Let _unbalanceResult_ be ? UnbalanceDurationRelative(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]], _unit_, _relativeTo_).
7. Let _unbalanceResult_ be ? UnbalanceDateDurationRelative(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]], _unit_, _relativeTo_).
...
10. Let _balanceResult_ be ? BalanceDuration(_unbalanceResult_.[[Days]], _unbalanceResult_.[[Hours]], _unbalanceResult_.[[Minutes]], _unbalanceResult_.[[Seconds]], _unbalanceResult_.[[Milliseconds]], _unbalanceResult_.[[Microseconds]], _unbalanceResult_.[[Nanoseconds]], _unit_, _intermediate_).
11. Let _roundResult_ be ? RoundDuration(_unbalanceResult_.[[Years]], _unbalanceResult_.[[Months]], _unbalanceResult_.[[Weeks]], _balanceResult_.[[Days]], _balanceResult_.[[Hours]], _balanceResult_.[[Minutes]], _balanceResult_.[[Seconds]], _balanceResult_.[[Milliseconds]], _balanceResult_.[[Microseconds]], _balanceResult_.[[Nanoseconds]], 1, _unit_, *"trunc"*, _relativeTo_).
sec-temporal-unbalancedurationrelative steps 1 and 9.d.iiiv:
1. If _largestUnit_ is *"year"*, or _years_, _months_, _weeks_, and _days_ are all 0, then
a. Return ...
...
9. If _largestUnit_ is *"month"*, then
sec-temporal-unbalancedatedurationrelative step 3:
3. If _largestUnit_ is *"month"*, then
...
d. Repeat, while abs(_years_) > 0,
...
iii. Let _untilOptions_ be ! OrdinaryObjectCreate(*null*).
iv. Perform ! CreateDataPropertyOrThrow(_untilOptions_, *"largestUnit"*, *"month"*).
v. Let _untilResult_ be ? CalendarDateUntil(_calendar_, _relativeTo_, _newRelativeTo_, _untilOptions_, _dateUntil_).
g. Let _untilOptions_ be ! OrdinaryObjectCreate(*null*).
h. Perform ! CreateDataPropertyOrThrow(_untilOptions_, *"largestUnit"*, *"month"*).
i. Let _untilResult_ be ? CalendarDateUntil(_calendarRec_.[[Receiver]], _plainRelativeTo_, _later_, _untilOptions_, _calendarRec_.[[DateUntil]]).
sec-temporal-balanceduration step 3.a:
3. If _largestUnit_ is one of *"year"*, *"month"*, *"week"*, or *"day"*, then
a. Let _result_ be ? NanosecondsToDays(_nanoseconds_, _relativeTo_).
@ -79,7 +74,7 @@ TemporalHelpers.checkCalendarDateUntilLargestUnitSingular(
},
{
years: ["year"],
months: ["month", "month", "month", "month", "month"],
months: ["month"],
weeks: [],
days: [],
hours: [],

View File

@ -11,17 +11,23 @@ features: [Temporal]
---*/
const actual = [];
class CalendarDateUntilObservable extends Temporal.Calendar {
dateUntil(...args) {
actual.push("call dateUntil");
const returnValue = super.dateUntil(...args);
TemporalHelpers.observeProperty(actual, returnValue, "months", Infinity);
return returnValue;
}
}
const calendar = new CalendarDateUntilObservable("iso8601");
const relativeTo = new Temporal.PlainDate(2018, 10, 12, calendar);
const expected = [
"call dateUntil",
"call dateUntil",
];
const duration = new Temporal.Duration(0, 12);
TemporalHelpers.observeProperty(actual, duration, "months", 1);
const calendar = TemporalHelpers.calendarDateUntilObservable(actual, duration);
const relativeTo = new Temporal.PlainDateTime(2018, 10, 12, 0, 0, 0, 0, 0, 0, calendar);
const years = new Temporal.Duration(2);
const result = years.total({ unit: "months", relativeTo });
assert.sameValue(result, 24, "result");

View File

@ -129,9 +129,9 @@ actual.splice(0); // clear
// code path through Duration.prototype.total that rounds to the nearest month:
const expectedOpsForMonthRounding = expectedOpsForPlainRelativeTo.concat([
// UnbalanceDurationRelative
"call options.relativeTo.calendar.dateAdd", // 9.d.i
"call options.relativeTo.calendar.dateUntil", // 9.d.iv
// UnbalanceDateDurationRelative
"call options.relativeTo.calendar.dateAdd", // 3.f
"call options.relativeTo.calendar.dateUntil", // 3.i
// RoundDuration
"call options.relativeTo.calendar.dateAdd", // 10.c
"call options.relativeTo.calendar.dateAdd", // 10.e
@ -144,9 +144,8 @@ actual.splice(0); // clear
// code path through Duration.prototype.total that rounds to the nearest week:
const expectedOpsForWeekRounding = expectedOpsForPlainRelativeTo.concat([
// UnbalanceDurationRelative
"call options.relativeTo.calendar.dateAdd", // 10.c.i MoveRelativeDate
"call options.relativeTo.calendar.dateAdd", // 10.d.i MoveRelativeDate
// UnbalanceDateDurationRelative
"call options.relativeTo.calendar.dateAdd", // 4.e
// RoundDuration
"call options.relativeTo.calendar.dateAdd", // 11.d MoveRelativeDate
], Array(58).fill("call options.relativeTo.calendar.dateAdd")); // 58× 11.g.iii MoveRelativeDate (52 + 4 + 2)
@ -155,11 +154,9 @@ instance3.total(createOptionsObserver({ unit: "weeks", relativeTo: plainRelative
assert.compareArray(actual, expectedOpsForWeekRounding, "order of operations with unit = weeks");
actual.splice(0); // clear
// code path through UnbalanceDurationRelative that rounds to the nearest day:
// code path through UnbalanceDateDurationRelative that rounds to the nearest day:
const expectedOpsForDayRounding = expectedOpsForPlainRelativeTo.concat([
"call options.relativeTo.calendar.dateAdd", // 11.a.iii.1 MoveRelativeDate
"call options.relativeTo.calendar.dateAdd", // 11.a.iv.1 MoveRelativeDate
"call options.relativeTo.calendar.dateAdd", // 11.a.v.1 MoveRelativeDate
"call options.relativeTo.calendar.dateAdd", // 10
]);
const instance4 = new Temporal.Duration(1, 1, 1)
instance4.total(createOptionsObserver({ unit: "days", relativeTo: plainRelativeTo }));