Skip to content

Commit

Permalink
Normative: Implement optimizations in shared duration rounding code path
Browse files Browse the repository at this point in the history
These optimizations were developed by Adam Shaw:
https://gist.github.com/arshaw/36d3152c21482bcb78ea2c69591b20e0

It does the same thing as previously, although fixes some incidental edge
cases that Adam discovered. However, the algorithm is simpler to explain
and to understand, and also makes fewer calls into user code.

It uses three variations on a bounding technique for rounding: computing
the upper and lower result, and checking which one is closer to the
original duration, and 'nudging' the duration up or down accordingly.

There is one variation for calendar units, one variation for rounding
relative to a ZonedDateTime where smallestUnit is a time unit and
largestUnit is a calendar unit, and one variation for time units.

RoundDuration becomes a lot more simplified, any part of it that was
complex is now split out into the new RoundRelativeDuration and
BubbleRelativeDuration operations, and the three 'nudging' operations.

The operations NormalizedTimeDurationToDays, BalanceTimeDurationRelative,
BalanceDateDurationRelative, MoveRelativeDate, MoveRelativeZonedDateTime,
and AdjustRoundedDurationDays are no longer needed. Their functionality is
subsumed by the new operations.

Closes: #2792
Closes: #2817
  • Loading branch information
ptomato committed May 10, 2024
1 parent b4b21b3 commit 510d53f
Show file tree
Hide file tree
Showing 10 changed files with 976 additions and 1,041 deletions.
13 changes: 6 additions & 7 deletions polyfill/lib/duration.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
import { TimeDuration } from './timeduration.mjs';

const MathAbs = Math.abs;
const NumberIsNaN = Number.isNaN;
const ObjectCreate = Object.create;

export class Duration {
Expand Down Expand Up @@ -345,9 +346,7 @@ export class Duration {
ES.DifferenceZonedDateTimeWithRounding(
relativeEpochNs,
targetEpochNs,
plainRelativeTo,
calendarRec,
zonedRelativeTo,
timeZoneRec,
precalculatedPlainDateTime,
ObjectCreate(null),
Expand Down Expand Up @@ -399,7 +398,7 @@ export class Duration {
if (ES.IsCalendarUnit(smallestUnit)) {
throw new RangeError(`a starting point is required for ${smallestUnit}s rounding`);
}
({ days, norm } = ES.RoundDuration(0, 0, 0, days, norm, roundingIncrement, smallestUnit, roundingMode));
({ days, norm } = ES.RoundTimeDuration(days, norm, roundingIncrement, smallestUnit, roundingMode));
({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceTimeDuration(
norm.add24HourDays(days),
largestUnit
Expand Down Expand Up @@ -468,9 +467,7 @@ export class Duration {
const { total } = ES.DifferenceZonedDateTimeWithRounding(
relativeEpochNs,
targetEpochNs,
plainRelativeTo,
calendarRec,
zonedRelativeTo,
timeZoneRec,
precalculatedPlainDateTime,
ObjectCreate(null),
Expand All @@ -479,6 +476,7 @@ export class Duration {
unit,
'trunc'
);
if (NumberIsNaN(total)) throw new Error('assertion failed: total hit unexpected code path');
return total;
}

Expand Down Expand Up @@ -513,6 +511,7 @@ export class Duration {
unit,
'trunc'
);
if (NumberIsNaN(total)) throw new Error('assertion failed: total hit unexpected code path');
return total;
}

Expand All @@ -524,7 +523,7 @@ export class Duration {
throw new RangeError(`a starting point is required for ${unit}s total`);
}
norm = norm.add24HourDays(days);
const { total } = ES.RoundDuration(0, 0, 0, 0, norm, 1, unit, 'trunc');
const { total } = ES.RoundTimeDuration(0, norm, 1, unit, 'trunc');
return total;
}
toString(options = undefined) {
Expand Down Expand Up @@ -563,7 +562,7 @@ export class Duration {
microseconds,
nanoseconds
);
({ norm } = ES.RoundDuration(0, 0, 0, 0, norm, increment, unit, roundingMode));
({ norm } = ES.RoundTimeDuration(0, norm, increment, unit, roundingMode));
let deltaDays;
({
days: deltaDays,
Expand Down
Loading

0 comments on commit 510d53f

Please sign in to comment.