Skip to content

Commit

Permalink
Normative: Separate zoned and plain operations in RoundDuration
Browse files Browse the repository at this point in the history
This converts a ZonedDateTime relativeTo into a PlainDateTime relativeTo
only when necessary, only after potentially throwing other errors, and
only once. Previously, it could be converted up to a few separate times in
each operation, such as UnbalanceDurationRelative, RoundDuration, and
BalanceDurationRelative. Since the conversion is user-visible, we don't
want to perform it when not necessary or perform it more times than
necessary.

Closes: #2247
Closes: #2529
  • Loading branch information
ptomato committed Sep 13, 2023
1 parent e9b9a6f commit 742248e
Show file tree
Hide file tree
Showing 6 changed files with 467 additions and 230 deletions.
69 changes: 50 additions & 19 deletions polyfill/lib/duration.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ export class Duration {
}

let largestUnit = ES.GetTemporalUnit(roundTo, 'largestUnit', 'datetime', undefined, ['auto']);
let relativeTo = ES.ToRelativeTemporalObject(roundTo);
let { plainRelativeTo, zonedRelativeTo } = ES.ToRelativeTemporalObject(roundTo);
const roundingIncrement = ES.ToTemporalRoundingIncrement(roundTo);
const roundingMode = ES.ToTemporalRoundingMode(roundTo, 'halfExpand');
let smallestUnit = ES.GetTemporalUnit(roundTo, 'smallestUnit', 'datetime', undefined);
Expand Down Expand Up @@ -284,7 +284,7 @@ export class Duration {
const calendarUnitsPresent = years !== 0 || months !== 0 || weeks !== 0;
const timeUnitsOverflowWillOccur =
minutes >= 60 || seconds >= 60 || milliseconds >= 1000 || microseconds >= 1000 || nanoseconds >= 1000;
const hoursToDaysConversionMayOccur = (days !== 0 && ES.IsTemporalZonedDateTime(relativeTo)) || hours >= 24;
const hoursToDaysConversionMayOccur = (days !== 0 && zonedRelativeTo) || hours >= 24;
if (
roundingGranularityIsNoop &&
!balancingRequested &&
Expand All @@ -295,13 +295,27 @@ export class Duration {
return new Duration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
}

const plainRelativeToWillBeUsed =
smallestUnit === 'year' ||
smallestUnit === 'month' ||
smallestUnit === 'week' ||
years !== 0 ||
months !== 0 ||
weeks !== 0 ||
days !== 0;
if (zonedRelativeTo && plainRelativeToWillBeUsed) {
// Convert a ZonedDateTime relativeTo to PlainDate only if needed in one
// of the operations below, because the conversion is user visible
plainRelativeTo = ES.ToTemporalDate(zonedRelativeTo);
}

({ years, months, weeks, days } = ES.UnbalanceDateDurationRelative(
years,
months,
weeks,
days,
largestUnit,
relativeTo
plainRelativeTo
));
({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
ES.RoundDuration(
Expand All @@ -318,9 +332,10 @@ export class Duration {
roundingIncrement,
smallestUnit,
roundingMode,
relativeTo
plainRelativeTo,
zonedRelativeTo
));
if (ES.IsTemporalZonedDateTime(relativeTo)) {
if (zonedRelativeTo) {
({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
ES.AdjustRoundedDurationDays(
years,
Expand All @@ -336,7 +351,7 @@ export class Duration {
roundingIncrement,
smallestUnit,
roundingMode,
relativeTo
zonedRelativeTo
));
({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceTimeDurationRelative(
days,
Expand All @@ -347,7 +362,7 @@ export class Duration {
microseconds,
nanoseconds,
largestUnit,
relativeTo
zonedRelativeTo
));
} else {
({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceTimeDuration(
Expand All @@ -367,7 +382,7 @@ export class Duration {
weeks,
days,
largestUnit,
relativeTo
plainRelativeTo
));

return new Duration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
Expand All @@ -393,15 +408,30 @@ export class Duration {
} else {
totalOf = ES.GetOptionsObject(totalOf);
}
const relativeTo = ES.ToRelativeTemporalObject(totalOf);
let { plainRelativeTo, zonedRelativeTo } = ES.ToRelativeTemporalObject(totalOf);
const unit = ES.GetTemporalUnit(totalOf, 'unit', 'datetime', ES.REQUIRED);

const plainRelativeToWillBeUsed =
unit === 'year' || unit === 'month' || unit === 'week' || years !== 0 || months !== 0 || weeks !== 0;
if (zonedRelativeTo !== undefined && plainRelativeToWillBeUsed) {
// Convert a ZonedDateTime relativeTo to PlainDate only if needed in one
// of the operations below, because the conversion is user visible
plainRelativeTo = ES.ToTemporalDate(zonedRelativeTo);
}

// Convert larger units down to days
({ years, months, weeks, days } = ES.UnbalanceDateDurationRelative(years, months, weeks, days, unit, relativeTo));
({ years, months, weeks, days } = ES.UnbalanceDateDurationRelative(
years,
months,
weeks,
days,
unit,
plainRelativeTo
));
// If the unit we're totalling is smaller than `days`, convert days down to that unit.
let balanceResult;
if (ES.IsTemporalZonedDateTime(relativeTo)) {
const intermediate = ES.MoveRelativeZonedDateTime(relativeTo, years, months, weeks, 0);
if (zonedRelativeTo) {
const intermediate = ES.MoveRelativeZonedDateTime(zonedRelativeTo, years, months, weeks, 0);
balanceResult = ES.BalancePossiblyInfiniteTimeDurationRelative(
days,
hours,
Expand Down Expand Up @@ -446,7 +476,8 @@ export class Duration {
1,
unit,
'trunc',
relativeTo
plainRelativeTo,
zonedRelativeTo
);
return total;
}
Expand Down Expand Up @@ -548,7 +579,7 @@ export class Duration {
one = ES.ToTemporalDuration(one);
two = ES.ToTemporalDuration(two);
options = ES.GetOptionsObject(options);
let relativeTo = ES.ToRelativeTemporalObject(options);
let { plainRelativeTo, zonedRelativeTo } = ES.ToRelativeTemporalObject(options);
const y1 = GetSlot(one, YEARS);
const mon1 = GetSlot(one, MONTHS);
const w1 = GetSlot(one, WEEKS);
Expand All @@ -569,12 +600,12 @@ export class Duration {
const ms2 = GetSlot(two, MILLISECONDS);
const µs2 = GetSlot(two, MICROSECONDS);
let ns2 = GetSlot(two, NANOSECONDS);
const shift1 = ES.CalculateOffsetShift(relativeTo, y1, mon1, w1, d1);
const shift2 = ES.CalculateOffsetShift(relativeTo, y2, mon2, w2, d2);
const shift1 = ES.CalculateOffsetShift(zonedRelativeTo, y1, mon1, w1, d1);
const shift2 = ES.CalculateOffsetShift(zonedRelativeTo, y2, mon2, w2, d2);
if (y1 !== 0 || y2 !== 0 || mon1 !== 0 || mon2 !== 0 || w1 !== 0 || w2 !== 0) {
if (ES.IsTemporalZonedDateTime(relativeTo)) relativeTo = ES.ToTemporalDate(relativeTo);
({ days: d1 } = ES.UnbalanceDateDurationRelative(y1, mon1, w1, d1, 'day', relativeTo));
({ days: d2 } = ES.UnbalanceDateDurationRelative(y2, mon2, w2, d2, 'day', relativeTo));
if (zonedRelativeTo) plainRelativeTo = ES.ToTemporalDate(zonedRelativeTo);
({ days: d1 } = ES.UnbalanceDateDurationRelative(y1, mon1, w1, d1, 'day', plainRelativeTo));
({ days: d2 } = ES.UnbalanceDateDurationRelative(y2, mon2, w2, d2, 'day', plainRelativeTo));
}
ns1 = ES.TotalDurationNanoseconds(d1, h1, min1, s1, ms1, µs1, ns1, shift1);
ns2 = ES.TotalDurationNanoseconds(d2, h2, min2, s2, ms2, µs2, ns2, shift2);
Expand Down
Loading

0 comments on commit 742248e

Please sign in to comment.