Skip to content

Commit

Permalink
Normative: Combine code paths for duration rounding and difference
Browse files Browse the repository at this point in the history
In order to prevent bugs due to discrepancies between two ways of
calculating the same thing such as in #2742, refactor duration rounding
with relativeTo so that

    duration.round({ smallestUnit, largestUnit, relativeTo, ...options })

goes through the same code path and gives the same result as

    const target = relativeTo.add(duration);
    relativeTo.until(target, { smallestUnit, largestUnit, ...options })

but taking into account that the until() methods have a different default
roundingMode than Duration.prototype.round(), and optimizing away as many
user-observable calls as possible.
  • Loading branch information
ptomato committed Jan 27, 2024
1 parent e6da103 commit b184abd
Show file tree
Hide file tree
Showing 5 changed files with 444 additions and 199 deletions.
176 changes: 109 additions & 67 deletions polyfill/lib/duration.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import bigInt from 'big-integer';

import * as ES from './ecmascript.mjs';
import { MakeIntrinsicClass } from './intrinsicclass.mjs';
import { GetIntrinsic, MakeIntrinsicClass } from './intrinsicclass.mjs';
import { CalendarMethodRecord } from './methodrecord.mjs';
import {
YEARS,
Expand All @@ -18,9 +18,13 @@ import {
NANOSECONDS,
CALENDAR,
INSTANT,
ISO_YEAR,
ISO_MONTH,
ISO_DAY,
CreateSlots,
GetSlot,
SetSlot
SetSlot,
EPOCHNANOSECONDS
} from './slots.mjs';

const MathAbs = Math.abs;
Expand Down Expand Up @@ -331,17 +335,12 @@ export class Duration {
'dateUntil'
]);

({ years, months, weeks, days } = ES.UnbalanceDateDurationRelative(
years,
months,
weeks,
days,
largestUnit,
plainRelativeTo,
calendarRec
));
({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
ES.RoundDuration(
if (zonedRelativeTo) {
const relativeEpochNs = GetSlot(zonedRelativeTo, EPOCHNANOSECONDS);
const targetEpochNs = ES.AddZonedDateTime(
GetSlot(zonedRelativeTo, INSTANT),
timeZoneRec,
calendarRec,
years,
months,
weeks,
Expand All @@ -352,80 +351,123 @@ export class Duration {
milliseconds,
microseconds,
nanoseconds,
roundingIncrement,
smallestUnit,
roundingMode,
plainRelativeTo,
calendarRec,
zonedRelativeTo,
timeZoneRec,
precalculatedPlainDateTime
));
if (zonedRelativeTo) {
);
({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
ES.AdjustRoundedDurationDays(
years,
months,
weeks,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
roundingIncrement,
smallestUnit,
roundingMode,
zonedRelativeTo,
ES.DifferenceZonedDateTimeWithRounding(
relativeEpochNs,
targetEpochNs,
plainRelativeTo,
calendarRec,
zonedRelativeTo,
timeZoneRec,
precalculatedPlainDateTime
precalculatedPlainDateTime,
ObjectCreate(null),
largestUnit,
roundingIncrement,
smallestUnit,
roundingMode
));
const intermediate = ES.MoveRelativeZonedDateTime(
zonedRelativeTo,
calendarRec,
timeZoneRec,
years,
months,
weeks,
} else if (plainRelativeTo) {
let targetTime = ES.BalanceTimeDuration(
0,
precalculatedPlainDateTime
);
({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceTimeDurationRelative(
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
largestUnit,
intermediate,
timeZoneRec
));
'day'
);

// Delegate the date part addition to the calendar
const TemporalDuration = GetIntrinsic('%Temporal.Duration%');
const dateDuration = new TemporalDuration(years, months, weeks, days + targetTime.days, 0, 0, 0, 0, 0, 0);
const targetDate = ES.AddDate(calendarRec, plainRelativeTo, dateDuration);

({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
ES.DifferencePlainDateTimeWithRounding(
plainRelativeTo,
0,
0,
0,
0,
0,
0,
GetSlot(targetDate, ISO_YEAR),
GetSlot(targetDate, ISO_MONTH),
GetSlot(targetDate, ISO_DAY),
targetTime.hours,
targetTime.minutes,
targetTime.seconds,
targetTime.milliseconds,
targetTime.microseconds,
targetTime.nanoseconds,
calendarRec,
largestUnit,
roundingIncrement,
smallestUnit,
roundingMode
));
} else {
({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceTimeDuration(
if (calendarUnitsPresent) {
throw new RangeError('a starting point is required for years, months, or weeks balancing');
}
if (largestUnit === 'year' || largestUnit === 'month' || largestUnit === 'week') {
throw new RangeError(`a starting point is required for ${largestUnit}s balancing`);
}
if (smallestUnit === 'year' || smallestUnit === 'month' || smallestUnit === 'week') {
throw new RangeError(`a starting point is required for ${smallestUnit}s rounding`);
}

const isoCalendarRec = new CalendarMethodRecord('iso8601', ['dateAdd', 'dateUntil']);
const target = ES.AddDateTime(
1970,
1,
1,
0,
0,
0,
0,
0,
0,
isoCalendarRec,
years,
months,
weeks,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
largestUnit
));
nanoseconds
);
({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
ES.DifferencePlainDateTimeWithRounding(
undefined,
0,
0,
0,
0,
0,
0,
target.year,
target.month,
target.day,
target.hour,
target.minute,
target.second,
target.millisecond,
target.microsecond,
target.nanosecond,
isoCalendarRec,
largestUnit,
roundingIncrement,
smallestUnit,
roundingMode
));
}
({ years, months, weeks, days } = ES.BalanceDateDurationRelative(
years,
months,
weeks,
days,
largestUnit,
smallestUnit,
plainRelativeTo,
calendarRec
));

return new Duration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
}
Expand Down
Loading

0 comments on commit b184abd

Please sign in to comment.