Skip to content

Commit

Permalink
Normative: Remove BigInt arithmetic and loops in BalanceDurationRelative
Browse files Browse the repository at this point in the history
Similar to previous commit, if we are limiting calendar units then we can
replace these loops with one dateAdd + dateUntil each, and it's no longer
necessary to calculate with BigInt.

This should not affect any results, but has an observable effect on calls
to custom calendar methods.
  • Loading branch information
ptomato committed Jun 20, 2023
1 parent ac759b0 commit 014e38f
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 157 deletions.
6 changes: 2 additions & 4 deletions polyfill/lib/duration.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -332,16 +332,14 @@ export class Duration {
years !== 0 ||
months !== 0 ||
weeks !== 0 ||
largestUnit === 'year' ||
largestUnit === 'month' ||
largestUnit === 'week' ||
(days !== 0 && (largestUnit === 'year' || largestUnit === 'month' || largestUnit === 'week')) ||
smallestUnit === 'year' ||
smallestUnit === 'month' ||
smallestUnit === 'week'
) {
calendarRec.lookup('dateAdd');
}
if (largestUnit === 'year' || (largestUnit === 'month' && years !== 0) || smallestUnit === 'year') {
if (largestUnit === 'year' || largestUnit === 'month' || largestUnit === 'week' || smallestUnit === 'year') {
calendarRec.lookup('dateUntil');
}
}
Expand Down
140 changes: 56 additions & 84 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3503,113 +3503,85 @@ export function UnbalanceDateDurationRelative(years, months, weeks, days, larges
}

export function BalanceDateDurationRelative(years, months, weeks, days, largestUnit, plainRelativeTo, calendarRec) {
// dateAdd, dateUntil must be looked up if units are not 0 and largestUnit == year
// dateAdd must be looked up if units are not 0 and largestUnit == month or weeks
// calendarRec must have looked up:
// - if largestUnit == year:
// - if years or months nonzero, dateAdd and dateUntil
// - if weeks or days nonzero, dateUntil
// - if largestUnit == month:
// - if months nonzero, dateAdd and dateUntil
// - if weeks or days nonzero, dateUntil
// - if largestUnit == week:
// - if weeks nonzero, dateAdd and dateUntil
// - if days nonzero, dateUntil
const TemporalDuration = GetIntrinsic('%Temporal.Duration%');
const sign = DurationSign(years, months, weeks, days, 0, 0, 0, 0, 0, 0);
if (sign === 0 || (largestUnit !== 'year' && largestUnit !== 'month' && largestUnit !== 'week')) {

if (
(years === 0 && months === 0 && weeks === 0 && days === 0) ||
(largestUnit !== 'year' && largestUnit !== 'month' && largestUnit !== 'week')
) {
return { years, months, weeks, days };
}

if (!plainRelativeTo) throw new RangeError(`a starting point is required for ${largestUnit}s balancing`);

const oneYear = new TemporalDuration(sign);
const oneMonth = new TemporalDuration(0, sign);
const oneWeek = new TemporalDuration(0, 0, sign);

// Perform arithmetic in the mathematical integer domain
years = bigInt(years);
months = bigInt(months);
weeks = bigInt(weeks);
days = bigInt(days);
const untilOptions = ObjectCreate(null);
untilOptions.largestUnit = largestUnit;

switch (largestUnit) {
case 'year': {
// balance days up to years
let newRelativeTo, oneYearDays;
({ relativeTo: newRelativeTo, days: oneYearDays } = MoveRelativeDate(calendarRec, plainRelativeTo, oneYear));
while (days.abs().geq(MathAbs(oneYearDays))) {
days = days.subtract(oneYearDays);
years = years.add(sign);
plainRelativeTo = newRelativeTo;
({ relativeTo: newRelativeTo, days: oneYearDays } = MoveRelativeDate(calendarRec, plainRelativeTo, oneYear));
}

// balance days up to months
let oneMonthDays;
({ relativeTo: newRelativeTo, days: oneMonthDays } = MoveRelativeDate(calendarRec, plainRelativeTo, oneMonth));
while (days.abs().geq(MathAbs(oneMonthDays))) {
days = days.subtract(oneMonthDays);
months = months.add(sign);
plainRelativeTo = newRelativeTo;
({ relativeTo: newRelativeTo, days: oneMonthDays } = MoveRelativeDate(calendarRec, plainRelativeTo, oneMonth));
}

// balance months up to years
newRelativeTo = CalendarDateAdd(calendarRec.receiver, plainRelativeTo, oneYear, undefined, calendarRec.dateAdd);
const untilOptions = ObjectCreate(null);
untilOptions.largestUnit = 'month';
let untilResult = CalendarDateUntil(
// balance months and days up to years
const later = AddDate(calendarRec, plainRelativeTo, new TemporalDuration(years, months, 0, days));
const untilResult = CalendarDateUntil(
calendarRec.receiver,
plainRelativeTo,
newRelativeTo,
later,
untilOptions,
calendarRec.dateUntil
);
let oneYearMonths = GetSlot(untilResult, MONTHS);
while (months.abs().geq(MathAbs(oneYearMonths))) {
months = months.subtract(oneYearMonths);
years = years.add(sign);
plainRelativeTo = newRelativeTo;
newRelativeTo = CalendarDateAdd(calendarRec.receiver, plainRelativeTo, oneYear, undefined, calendarRec.dateAdd);
const untilOptions = ObjectCreate(null);
untilOptions.largestUnit = 'month';
untilResult = CalendarDateUntil(
calendarRec.receiver,
plainRelativeTo,
newRelativeTo,
untilOptions,
calendarRec.dateUntil
);
oneYearMonths = GetSlot(untilResult, MONTHS);
}
break;
return {
years: GetSlot(untilResult, YEARS),
months: GetSlot(untilResult, MONTHS),
weeks,
days: GetSlot(untilResult, DAYS)
};
}
case 'month': {
// balance days up to months
let newRelativeTo, oneMonthDays;
({ relativeTo: newRelativeTo, days: oneMonthDays } = MoveRelativeDate(calendarRec, plainRelativeTo, oneMonth));
while (days.abs().geq(MathAbs(oneMonthDays))) {
days = days.subtract(oneMonthDays);
months = months.add(sign);
plainRelativeTo = newRelativeTo;
({ relativeTo: newRelativeTo, days: oneMonthDays } = MoveRelativeDate(calendarRec, plainRelativeTo, oneMonth));
}
break;
const later = AddDate(calendarRec, plainRelativeTo, new TemporalDuration(0, months, 0, days));
const untilResult = CalendarDateUntil(
calendarRec.receiver,
plainRelativeTo,
later,
untilOptions,
calendarRec.dateUntil
);
return {
years: 0,
months: GetSlot(untilResult, MONTHS),
weeks,
days: GetSlot(untilResult, DAYS)
};
}
case 'week': {
// balance days up to weeks
let newRelativeTo, oneWeekDays;
({ relativeTo: newRelativeTo, days: oneWeekDays } = MoveRelativeDate(calendarRec, plainRelativeTo, oneWeek));
while (days.abs().geq(MathAbs(oneWeekDays))) {
days = days.subtract(oneWeekDays);
weeks = weeks.add(sign);
plainRelativeTo = newRelativeTo;
({ relativeTo: newRelativeTo, days: oneWeekDays } = MoveRelativeDate(calendarRec, plainRelativeTo, oneWeek));
}
break;
const later = AddDate(calendarRec, plainRelativeTo, new TemporalDuration(0, 0, weeks, days));
const untilResult = CalendarDateUntil(
calendarRec.receiver,
plainRelativeTo,
later,
untilOptions,
calendarRec.dateUntil
);
return {
years: 0,
months: 0,
weeks: GetSlot(untilResult, WEEKS),
days: GetSlot(untilResult, DAYS)
};
}
default:
// no-op
break;
// not reached
}

return {
years: years.toJSNumber(),
months: months.toJSNumber(),
weeks: weeks.toJSNumber(),
days: days.toJSNumber()
};
}

export function CreateNegatedTemporalDuration(duration) {
Expand Down
94 changes: 25 additions & 69 deletions spec/duration.html
Original file line number Diff line number Diff line change
Expand Up @@ -476,9 +476,9 @@ <h1>Temporal.Duration.prototype.round ( _roundTo_ )</h1>
1. Set _calendar_ to _plainRelativeTo_.[[Calendar]].
1. If _calendar_ is not *undefined*, then
1. Set _calendarRec_ to ! CreateCalendarRecord(_calendar_, « »).
1. If _duration_.[[Years]] &ne; 0; or _duration_.[[Months]] &ne; 0; or _duration_.[[Weeks]] &ne; 0; or _largestUnit_ is any of *"year"*, *"month"*, *"week"*; or _smallestUnit_ is any of *"year"*, *"month"*, *"week"*; then
1. If _duration_.[[Years]] &ne; 0; or _duration_.[[Months]] &ne; 0; or _duration_.[[Weeks]] &ne; 0; or _duration_.[[Days]] &ne; 0 and _largestUnit_ is any of *"year"*, *"month"*, *"week"*; or _smallestUnit_ is any of *"year"*, *"month"*, *"week"*; then
1. Set _calendarRec_.[[DateAdd]] to ? GetMethod(_calendar_, *"dateAdd"*).
1. If _largestUnit_ is *"year"*, or _largestUnit_ is *"month"* and _duration_.[[Years]] &ne; 0, or _smallestUnit_ is *"year"*, then
1. If _largestUnit_ is any of *"year"*, *"month"*, *"week"*, or _smallestUnit_ is *"year"*, then
1. Set _calendarRec_.[[DateUntil]] to ? GetMethod(_calendar_, *"dateUntil"*).
1. Let _unbalanceResult_ be ? UnbalanceDateDurationRelative(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]], _largestUnit_, _plainRelativeTo_, _calendarRec_).
1. Let _roundRecord_ 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_, _plainRelativeTo_, _calendarRec_, _zonedRelativeTo_, _timeZoneRec_).
Expand Down Expand Up @@ -1463,74 +1463,30 @@ <h1>
1. Return ! CreateDateDurationRecord(_years_, _months_, _weeks_, _days_).
1. If _plainRelativeTo_ is *undefined*, then
1. Throw a *RangeError* exception.
1. Let _sign_ be ! DurationSign(_years_, _months_, _weeks_, _days_, 0, 0, 0, 0, 0, 0).
1. Assert: _sign_ &ne; 0.
1. Let _oneYear_ be ! CreateTemporalDuration(_sign_, 0, 0, 0, 0, 0, 0, 0, 0, 0).
1. Let _oneMonth_ be ! CreateTemporalDuration(0, _sign_, 0, 0, 0, 0, 0, 0, 0, 0).
1. Let _oneWeek_ be ! CreateTemporalDuration(0, 0, _sign_, 0, 0, 0, 0, 0, 0, 0).
1. Assert: _calendarRec_.[[DateUntil]] is not *undefined*.
1. Let _untilOptions_ be OrdinaryObjectCreate(*null*).
1. Perform ! CreateDataPropertyOrThrow(_untilOptions_, *"largestUnit"*, _largestUnit_).
1. If _largestUnit_ is *"year"*, then
1. Assert: _calendarRec_.[[DateAdd]] is not *undefined*.
1. Assert: _calendarRec_.[[DateUntil]] is not *undefined*.
1. Let _moveResult_ be ? MoveRelativeDate(_calendarRec_, _plainRelativeTo_, _oneYear_).
1. Let _newRelativeTo_ be _moveResult_.[[RelativeTo]].
1. Let _oneYearDays_ be _moveResult_.[[Days]].
1. Repeat, while abs(_days_) &ge; abs(_oneYearDays_),
1. Set _days_ to _days_ - _oneYearDays_.
1. Set _years_ to _years_ + _sign_.
1. Set _plainRelativeTo_ to _newRelativeTo_.
1. Set _moveResult_ to ? MoveRelativeDate(_calendarRec_, _plainRelativeTo_, _oneYear_).
1. Set _newRelativeTo_ to _moveResult_.[[RelativeTo]].
1. Set _oneYearDays_ to _moveResult_.[[Days]].
1. Set _moveResult_ to ? MoveRelativeDate(_calendarRec_, _plainRelativeTo_, _oneMonth_).
1. Set _newRelativeTo_ to _moveResult_.[[RelativeTo]].
1. Let _oneMonthDays_ be _moveResult_.[[Days]].
1. Repeat, while abs(_days_) &ge; abs(_oneMonthDays_),
1. Set _days_ to _days_ - _oneMonthDays_.
1. Set _months_ to _months_ + _sign_.
1. Set _plainRelativeTo_ to _newRelativeTo_.
1. Set _moveResult_ to ? MoveRelativeDate(_calendarRec_, _plainRelativeTo_, _oneMonth_).
1. Set _newRelativeTo_ to _moveResult_.[[RelativeTo]].
1. Set _oneMonthDays_ to _moveResult_.[[Days]].
1. Set _newRelativeTo_ to ? CalendarDateAdd(_calendarRec_.[[Receiver]], _plainRelativeTo_, _oneYear_, *undefined*, _calendarRec_.[[DateAdd]]).
1. Let _untilOptions_ be OrdinaryObjectCreate(*null*).
1. Perform ! CreateDataPropertyOrThrow(_untilOptions_, *"largestUnit"*, *"month"*).
1. Let _untilResult_ be ? CalendarDateUntil(_calendarRec_.[[Receiver]], _plainRelativeTo_, _newRelativeTo_, _untilOptions_, _calendarRec_.[[DateUntil]]).
1. Let _oneYearMonths_ be _untilResult_.[[Months]].
1. Repeat, while abs(_months_) &ge; abs(_oneYearMonths_),
1. Set _months_ to _months_ - _oneYearMonths_.
1. Set _years_ to _years_ + _sign_.
1. Set _plainRelativeTo_ to _newRelativeTo_.
1. Set _newRelativeTo_ to ? CalendarDateAdd(_calendarRec_.[[Receiver]], _plainRelativeTo_, _oneYear_, *undefined*, _calendarRec_[[DateAdd]]).
1. Set _untilOptions_ to OrdinaryObjectCreate(*null*).
1. Perform ! CreateDataPropertyOrThrow(_untilOptions_, *"largestUnit"*, *"month"*).
1. Set _untilResult_ to ? CalendarDateUntil(_calendarRec_.[[Receiver]], _plainRelativeTo_, _newRelativeTo_, _untilOptions_, _calendarRec_.[[DateUntil]]).
1. Set _oneYearMonths_ to _untilResult_.[[Months]].
1. Else if _largestUnit_ is *"month"*, then
1. Assert: _calendarRec_.[[DateAdd]] is not *undefined*.
1. Let _moveResult_ be ? MoveRelativeDate(_calendarRec_, _plainRelativeTo_, _oneMonth_).
1. Let _newRelativeTo_ be _moveResult_.[[RelativeTo]].
1. Let _oneMonthDays_ be _moveResult_.[[Days]].
1. Repeat, while abs(_days_) &ge; abs(_oneMonthDays_),
1. Set _days_ to _days_ - _oneMonthDays_.
1. Set _months_ to _months_ + _sign_.
1. Set _plainRelativeTo_ to _newRelativeTo_.
1. Set _moveResult_ to ? MoveRelativeDate(_calendarRec_, _plainRelativeTo_, _oneMonth_).
1. Set _newRelativeTo_ to _moveResult_.[[RelativeTo]].
1. Set _oneMonthDays_ to _moveResult_.[[Days]].
1. Else,
1. Assert: _largestUnit_ is *"week"*.
1. Assert: _calendarRec_.[[DateAdd]] is not *undefined*.
1. Let _moveResult_ be ? MoveRelativeDate(_calendarRec_, _plainRelativeTo_, _oneWeek_).
1. Let _newRelativeTo_ be _moveResult_.[[RelativeTo]].
1. Let _oneWeekDays_ be _moveResult_.[[Days]].
1. Repeat, while abs(_days_) &ge; abs(_oneWeekDays_),
1. Set _days_ to _days_ - _oneWeekDays_.
1. Set _weeks_ to _weeks_ + _sign_.
1. Set _plainRelativeTo_ to _newRelativeTo_.
1. Set _moveResult_ to ? MoveRelativeDate(_calendarRec_, _plainRelativeTo_, _oneWeek_).
1. Set _newRelativeTo_ to _moveResult_.[[RelativeTo]].
1. Set _oneWeekDays_ to _moveResult_.[[Days]].
1. Return ! CreateDateDurationRecord(_years_, _months_, _weeks_, _days_).
1. Assert: If _years_ &ne; 0 or _months_ &ne; 0, _calendarRec_.[[DateAdd]] is not *undefined*.
1. Let _yearsMonthsDaysDuration_ be ! CreateTemporalDuration(_years_, _months_, 0, _days_, 0, 0, 0, 0, 0, 0).
1. Let _later_ be ? AddDate(_calendarRec_, _plainRelativeTo_, _yearsMonthsDaysDuration_).
1. Let _untilResult_ be ? CalendarDateUntil(_calendarRec_.[[Receiver]], _plainRelativeTo_, _later_, _untilOptions_, _calendarRec_.[[DateUntil]]).
1. Return ? CreateDateDurationRecord(_untilResult_.[[Years]], _untilResult_.[[Months]], _weeks_, _untilResult_.[[Days]]).
1. If _largestUnit_ is *"month"*, then
1. Assert: _years_ = 0.
1. Assert: If _months_ &ne; 0, _calendarRec_.[[DateAdd]] is not *undefined*.
1. Let _monthsDaysDuration_ be ! CreateTemporalDuration(0, _months_, 0, _days_, 0, 0, 0, 0, 0, 0).
1. Let _later_ be ? AddDate(_calendarRec_, _plainRelativeTo_, _monthsDaysDuration_).
1. Let _untilResult_ be ? CalendarDateUntil(_calendarRec_.[[Receiver]], _plainRelativeTo_, _later_, _untilOptions_, _calendarRec_.[[DateUntil]]).
1. Return ? CreateDateDurationRecord(0, _untilResult_.[[Months]], _weeks_, _untilResult_.[[Days]]).
1. Assert: _largestUnit_ is *"week"*.
1. Assert: _years_ = 0.
1. Assert: _months_ = 0.
1. Assert: If _weeks_ &ne; 0, _calendarRec_.[[DateAdd]] is not *undefined*.
1. Let _weeksDaysDuration_ be ! CreateTemporalDuration(0, 0, _weeks_, _days_, 0, 0, 0, 0, 0, 0).
1. Let _later_ be ? AddDate(_calendarRec_, _plainRelativeTo_, _weeksDaysDuration_).
1. Let _untilResult_ be ? CalendarDateUntil(_calendarRec_.[[Receiver]], _plainRelativeTo_, _later_, _untilOptions_, _calendarRec_.[[DateUntil]]).
1. Return ? CreateDateDurationRecord(0, 0, _untilResult_.[[Weeks]], _untilResult_.[[Days]]).
</emu-alg>
</emu-clause>

Expand Down

0 comments on commit 014e38f

Please sign in to comment.