Skip to content

Commit

Permalink
Normative: Remove loops in RoundDuration
Browse files Browse the repository at this point in the history
In RoundDuration, we already calculated years rounding without a loop. I'm
not sure why we didn't do this for months and weeks, but it's not
necessary to use a loop for those either.

This should not affect any results, but has an observable effect on calls
to custom calendar methods.
  • Loading branch information
ptomato committed Nov 14, 2023
1 parent 9ceedd8 commit ed38878
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 39 deletions.
76 changes: 51 additions & 25 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5580,7 +5580,7 @@ export function RoundDuration(
// dateAdd must be looked up if:
// - unit is year, month, or week
// - unit is day, zonedRelativeTo defined, and any of years...weeks != 0
// dateUntil must be looked up if unit is year
// dateUntil must be looked up if unit is year, month, or week, and any of years...weeks != 0
const TemporalDuration = GetIntrinsic('%Temporal.Duration%');

if ((unit === 'year' || unit === 'month' || unit === 'week') && !plainRelativeTo) {
Expand Down Expand Up @@ -5680,21 +5680,32 @@ export function RoundDuration(
plainRelativeTo = yearsMonthsLater;
days += weeksInDays;

// Months may be different lengths of days depending on the calendar,
// convert days to months in a loop as described above under 'years'.
const sign = MathSign(days);
const isoResult = AddISODate(
GetSlot(plainRelativeTo, ISO_YEAR),
GetSlot(plainRelativeTo, ISO_MONTH),
GetSlot(plainRelativeTo, ISO_DAY),
0,
0,
0,
days,
'constrain'
);
const wholeDaysLater = CreateTemporalDate(isoResult.year, isoResult.month, isoResult.day, calendarRec.receiver);
const untilOptions = ObjectCreate(null);
untilOptions.largestUnit = 'month';
const monthsPassed = GetSlot(DifferenceDate(calendarRec, plainRelativeTo, wholeDaysLater, untilOptions), MONTHS);
months += monthsPassed;
const monthsPassedDuration = new TemporalDuration(0, monthsPassed);
let daysPassed;
({ relativeTo: plainRelativeTo, days: daysPassed } = MoveRelativeDate(
calendarRec,
plainRelativeTo,
monthsPassedDuration
));
days -= daysPassed;
const oneMonth = new TemporalDuration(0, days < 0 ? -1 : 1);
let oneMonthDays;
({ relativeTo: plainRelativeTo, days: oneMonthDays } = MoveRelativeDate(calendarRec, plainRelativeTo, oneMonth));
while (MathAbs(days) >= MathAbs(oneMonthDays)) {
months += sign;
days -= oneMonthDays;
({ relativeTo: plainRelativeTo, days: oneMonthDays } = MoveRelativeDate(
calendarRec,
plainRelativeTo,
oneMonth
));
}
let { days: oneMonthDays } = MoveRelativeDate(calendarRec, plainRelativeTo, oneMonth);

oneMonthDays = MathAbs(oneMonthDays);
const divisor = bigInt(oneMonthDays).multiply(dayLengthNs);
nanoseconds = divisor.multiply(months).plus(bigInt(days).multiply(dayLengthNs)).plus(nanoseconds);
Expand All @@ -5706,17 +5717,32 @@ export function RoundDuration(
break;
}
case 'week': {
// Weeks may be different lengths of days depending on the calendar,
// convert days to weeks in a loop as described above under 'years'.
const sign = MathSign(days);
const isoResult = AddISODate(
GetSlot(plainRelativeTo, ISO_YEAR),
GetSlot(plainRelativeTo, ISO_MONTH),
GetSlot(plainRelativeTo, ISO_DAY),
0,
0,
0,
days,
'constrain'
);
const wholeDaysLater = CreateTemporalDate(isoResult.year, isoResult.month, isoResult.day, calendarRec.receiver);
const untilOptions = ObjectCreate(null);
untilOptions.largestUnit = 'week';
const weeksPassed = GetSlot(DifferenceDate(calendarRec, plainRelativeTo, wholeDaysLater, untilOptions), WEEKS);
weeks += weeksPassed;
const weeksPassedDuration = new TemporalDuration(0, 0, weeksPassed);
let daysPassed;
({ relativeTo: plainRelativeTo, days: daysPassed } = MoveRelativeDate(
calendarRec,
plainRelativeTo,
weeksPassedDuration
));
days -= daysPassed;
const oneWeek = new TemporalDuration(0, 0, days < 0 ? -1 : 1);
let oneWeekDays;
({ relativeTo: plainRelativeTo, days: oneWeekDays } = MoveRelativeDate(calendarRec, plainRelativeTo, oneWeek));
while (MathAbs(days) >= MathAbs(oneWeekDays)) {
weeks += sign;
days -= oneWeekDays;
({ relativeTo: plainRelativeTo, days: oneWeekDays } = MoveRelativeDate(calendarRec, plainRelativeTo, oneWeek));
}
let { days: oneWeekDays } = MoveRelativeDate(calendarRec, plainRelativeTo, oneWeek);

oneWeekDays = MathAbs(oneWeekDays);
const divisor = bigInt(oneWeekDays).multiply(dayLengthNs);
nanoseconds = divisor.multiply(weeks).plus(bigInt(days).multiply(dayLengthNs)).plus(nanoseconds);
Expand Down
40 changes: 26 additions & 14 deletions spec/duration.html
Original file line number Diff line number Diff line change
Expand Up @@ -1714,41 +1714,53 @@ <h1>
1. Set _months_ and _weeks_ to 0.
1. Else if _unit_ is *"month"*, then
1. Assert: CalendarMethodsRecordHasLookedUp(_calendarRec_, ~dateAdd~) is *true*.
1. Assert: CalendarMethodsRecordHasLookedUp(_calendarRec_, ~dateUntil~) is *true*.
1. Let _yearsMonths_ be ! CreateTemporalDuration(_years_, _months_, 0, 0, 0, 0, 0, 0, 0, 0).
1. Let _yearsMonthsLater_ be ? AddDate(_calendarRec_, _plainRelativeTo_, _yearsMonths_).
1. Let _yearsMonthsWeeks_ be ! CreateTemporalDuration(_years_, _months_, _weeks_, 0, 0, 0, 0, 0, 0, 0).
1. Let _yearsMonthsWeeksLater_ be ? AddDate(_calendarRec_, _plainRelativeTo_, _yearsMonthsWeeks_).
1. Let _weeksInDays_ be DaysUntil(_yearsMonthsLater_, _yearsMonthsWeeksLater_).
1. Set _plainRelativeTo_ to _yearsMonthsLater_.
1. Set _fractionalDays_ to _fractionalDays_ + _weeksInDays_.
1. Let _isoResult_ be ! AddISODate(_plainRelativeTo_.[[ISOYear]], _plainRelativeTo_.[[ISOMonth]], _plainRelativeTo_.[[ISODay]], 0, 0, 0, truncate(_fractionalDays_), *"constrain"*).
1. Let _wholeDaysLater_ be ? CreateTemporalDate(_isoResult_.[[Year]], _isoResult_.[[Month]], _isoResult_.[[Day]], _calendarRec_.[[Receiver]]).
1. Let _untilOptions_ be OrdinaryObjectCreate(*null*).
1. Perform ! CreateDataPropertyOrThrow(_untilOptions_, *"largestUnit"*, *"month"*).
1. Let _timePassed_ be ? DifferenceDate(_calendarRec_, _plainRelativeTo_, _wholeDaysLater_, _untilOptions_).
1. Let _monthsPassed_ be _timePassed_.[[Months]].
1. Set _months_ to _months_ + _monthsPassed_.
1. Let _monthsPassedDuration_ be ! CreateTemporalDuration(0, _monthsPassed_, 0, 0, 0, 0, 0, 0, 0, 0).
1. Let _moveResult_ be ? MoveRelativeDate(_calendarRec_, _plainRelativeTo_, _monthsPassedDuration_).
1. Set _plainRelativeTo_ to _moveResult_.[[RelativeTo]].
1. Let _daysPassed_ be _moveResult_.[[Days]].
1. Set _fractionalDays_ to _fractionalDays_ - _daysPassed_.
1. If _fractionalDays_ &lt; 0, let _sign_ be -1; else, let _sign_ be 1.
1. Let _oneMonth_ be ! CreateTemporalDuration(0, _sign_, 0, 0, 0, 0, 0, 0, 0, 0).
1. Let _moveResult_ be ? MoveRelativeDate(_calendarRec_, _plainRelativeTo_, _oneMonth_).
1. Set _plainRelativeTo_ to _moveResult_.[[RelativeTo]].
1. Let _oneMonthDays_ be _moveResult_.[[Days]].
1. Repeat, while abs(_fractionalDays_) &ge; abs(_oneMonthDays_),
1. Set _months_ to _months_ + _sign_.
1. Set _fractionalDays_ to _fractionalDays_ - _oneMonthDays_.
1. Set _moveResult_ to ? MoveRelativeDate(_calendarRec_, _plainRelativeTo_, _oneMonth_).
1. Set _plainRelativeTo_ to _moveResult_.[[RelativeTo]].
1. Set _oneMonthDays_ to _moveResult_.[[Days]].
1. Let _fractionalMonths_ be _months_ + _fractionalDays_ / abs(_oneMonthDays_).
1. Set _months_ to RoundNumberToIncrement(_fractionalMonths_, _increment_, _roundingMode_).
1. Set _total_ to _fractionalMonths_.
1. Set _weeks_ to 0.
1. Else if _unit_ is *"week"*, then
1. Assert: CalendarMethodsRecordHasLookedUp(_calendarRec_, ~dateAdd~) is *true*.
1. Assert: CalendarMethodsRecordHasLookedUp(_calendarRec_, ~dateUntil~) is *true*.
1. Let _isoResult_ be ! AddISODate(_plainRelativeTo_.[[ISOYear]], _plainRelativeTo_.[[ISOMonth]], _plainRelativeTo_.[[ISODay]], 0, 0, 0, truncate(_fractionalDays_), *"constrain"*).
1. Let _wholeDaysLater_ be ? CreateTemporalDate(_isoResult_.[[Year]], _isoResult_.[[Month]], _isoResult_.[[Day]], _calendarRec_.[[Receiver]]).
1. Let _untilOptions_ be OrdinaryObjectCreate(*null*).
1. Perform ! CreateDataPropertyOrThrow(_untilOptions_, *"largestUnit"*, *"week"*).
1. Let _timePassed_ be ? DifferenceDate(_calendarRec_, _plainRelativeTo_, _wholeDaysLater_, _untilOptions_).
1. Let _weeksPassed_ be _timePassed_.[[Weeks]].
1. Set _weeks_ to _weeks_ + _weeksPassed_.
1. Let _weeksPassedDuration_ be ! CreateTemporalDuration(0, 0, _weeksPassed_, 0, 0, 0, 0, 0, 0, 0).
1. Let _moveResult_ be ? MoveRelativeDate(_calendarRec_, _plainRelativeTo_, _weeksPassedDuration_).
1. Set _plainRelativeTo_ to _moveResult_.[[RelativeTo]].
1. Let _daysPassed_ be _moveResult_.[[Days]].
1. Set _fractionalDays_ to _fractionalDays_ - _daysPassed_.
1. If _fractionalDays_ &lt; 0, let _sign_ be -1; else, let _sign_ be 1.
1. Let _oneWeek_ be ! CreateTemporalDuration(0, 0, _sign_, 0, 0, 0, 0, 0, 0, 0).
1. Let _moveResult_ be ? MoveRelativeDate(_calendarRec_, _plainRelativeTo_, _oneWeek_).
1. Set _plainRelativeTo_ to _moveResult_.[[RelativeTo]].
1. Let _oneWeekDays_ be _moveResult_.[[Days]].
1. Repeat, while abs(_fractionalDays_) &ge; abs(_oneWeekDays_),
1. Set _weeks_ to _weeks_ + _sign_.
1. Set _fractionalDays_ to _fractionalDays_ - _oneWeekDays_.
1. Set _moveResult_ to ? MoveRelativeDate(_calendarRec_, _plainRelativeTo_, _oneWeek_).
1. Set _plainRelativeTo_ to _moveResult_.[[RelativeTo]].
1. Set _oneWeekDays_ to _moveResult_.[[Days]].
1. Let _fractionalWeeks_ be _weeks_ + _fractionalDays_ / abs(_oneWeekDays_).
1. Set _weeks_ to RoundNumberToIncrement(_fractionalWeeks_, _increment_, _roundingMode_).
1. Set _total_ to _fractionalWeeks_.
Expand Down

0 comments on commit ed38878

Please sign in to comment.