Skip to content

Commit

Permalink
Normative: Precalculate PlainDateTime from ZonedDateTime in more places
Browse files Browse the repository at this point in the history
There are a few more places where we can avoid doing an additional lookup
and call of getOffsetNanosecondsFor on the same ZonedDateTime, to convert
it into a PlainDateTime.

This affects

- Temporal.Duration.prototype.add (with relativeTo ZonedDateTime)
- Temporal.Duration.prototype.subtract (ditto)
- Temporal.Duration.prototype.round (ditto)
- Temporal.Duration.prototype.total (ditto)
- Temporal.ZonedDateTime.prototype.since
- Temporal.ZonedDateTime.prototype.until

(also fixes "and" vs "or" prose mistakes)
Closes: #2680
Closes: #2681
  • Loading branch information
ptomato committed Sep 27, 2023
1 parent 66d8089 commit 6eeee90
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 77 deletions.
65 changes: 48 additions & 17 deletions polyfill/lib/duration.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -300,18 +300,25 @@ export class Duration {
return new Duration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
}

const plainRelativeToWillBeUsed =
let precalculatedPlainDateTime;
const plainDateTimeOrRelativeToWillBeUsed =
!roundingGranularityIsNoop ||
smallestUnit === 'year' ||
smallestUnit === 'month' ||
smallestUnit === 'week' ||
years !== 0 ||
months !== 0 ||
weeks !== 0 ||
smallestUnit === 'day' ||
calendarUnitsPresent ||
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);
if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) {
// Convert a ZonedDateTime relativeTo to PlainDateTime and PlainDate only
// if either is needed in one of the operations below, because the
// conversion is user visible
precalculatedPlainDateTime = ES.GetPlainDateTimeFor(
GetSlot(zonedRelativeTo, TIME_ZONE),
GetSlot(zonedRelativeTo, INSTANT),
GetSlot(zonedRelativeTo, CALENDAR)
);
plainRelativeTo = ES.TemporalDateTimeToDate(precalculatedPlainDateTime);
}

({ years, months, weeks, days } = ES.UnbalanceDateDurationRelative(
Expand All @@ -338,7 +345,8 @@ export class Duration {
smallestUnit,
roundingMode,
plainRelativeTo,
zonedRelativeTo
zonedRelativeTo,
precalculatedPlainDateTime
));
if (zonedRelativeTo) {
({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
Expand All @@ -356,7 +364,8 @@ export class Duration {
roundingIncrement,
smallestUnit,
roundingMode,
zonedRelativeTo
zonedRelativeTo,
precalculatedPlainDateTime
));
({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceTimeDurationRelative(
days,
Expand All @@ -367,7 +376,8 @@ export class Duration {
microseconds,
nanoseconds,
largestUnit,
zonedRelativeTo
zonedRelativeTo,
precalculatedPlainDateTime
));
} else {
({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceTimeDuration(
Expand Down Expand Up @@ -416,12 +426,25 @@ export class Duration {
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) {
let precalculatedPlainDateTime;
const plainDateTimeOrRelativeToWillBeUsed =
unit === 'year' ||
unit === 'month' ||
unit === 'week' ||
unit === 'day' ||
years !== 0 ||
months !== 0 ||
weeks !== 0 ||
days !== 0;
if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) {
// 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);
precalculatedPlainDateTime = ES.GetPlainDateTimeFor(
GetSlot(zonedRelativeTo, TIME_ZONE),
GetSlot(zonedRelativeTo, INSTANT),
GetSlot(zonedRelativeTo, CALENDAR)
);
plainRelativeTo = ES.TemporalDateTimeToDate(precalculatedPlainDateTime);
}

// Convert larger units down to days
Expand All @@ -436,7 +459,14 @@ export class Duration {
// If the unit we're totalling is smaller than `days`, convert days down to that unit.
let balanceResult;
if (zonedRelativeTo) {
const intermediate = ES.MoveRelativeZonedDateTime(zonedRelativeTo, years, months, weeks, 0);
const intermediate = ES.MoveRelativeZonedDateTime(
zonedRelativeTo,
years,
months,
weeks,
0,
precalculatedPlainDateTime
);
balanceResult = ES.BalancePossiblyInfiniteTimeDurationRelative(
days,
hours,
Expand Down Expand Up @@ -482,7 +512,8 @@ export class Duration {
unit,
'trunc',
plainRelativeTo,
zonedRelativeTo
zonedRelativeTo,
precalculatedPlainDateTime
);
return total;
}
Expand Down
106 changes: 79 additions & 27 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3261,7 +3261,7 @@ export function TotalDurationNanoseconds(hours, minutes, seconds, milliseconds,
return bigInt(nanoseconds).add(microseconds.multiply(1000));
}

export function NanosecondsToDays(nanoseconds, zonedRelativeTo) {
export function NanosecondsToDays(nanoseconds, zonedRelativeTo, precalculatedPlainDateTime) {
const TemporalInstant = GetIntrinsic('%Temporal.Instant%');
const sign = MathSign(nanoseconds);
nanoseconds = bigInt(nanoseconds);
Expand All @@ -3276,7 +3276,7 @@ export function NanosecondsToDays(nanoseconds, zonedRelativeTo) {

// Find the difference in days only. Inline DifferenceISODateTime because we
// don't need the path that potentially calls calendar methods.
const dtStart = GetPlainDateTimeFor(timeZone, start, 'iso8601');
const dtStart = precalculatedPlainDateTime ?? GetPlainDateTimeFor(timeZone, start, 'iso8601');
const dtEnd = GetPlainDateTimeFor(timeZone, end, 'iso8601');
let hours, minutes, seconds, milliseconds, microseconds;
({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = DifferenceTime(
Expand Down Expand Up @@ -3501,7 +3501,8 @@ export function BalanceTimeDurationRelative(
microseconds,
nanoseconds,
largestUnit,
zonedRelativeTo
zonedRelativeTo,
precalculatedPlainDateTime
) {
let result = BalancePossiblyInfiniteTimeDurationRelative(
days,
Expand All @@ -3512,7 +3513,8 @@ export function BalanceTimeDurationRelative(
microseconds,
nanoseconds,
largestUnit,
zonedRelativeTo
zonedRelativeTo,
precalculatedPlainDateTime
);
if (result === 'positive overflow' || result === 'negative overflow') {
throw new RangeError('Duration out of range');
Expand All @@ -3529,23 +3531,24 @@ export function BalancePossiblyInfiniteTimeDurationRelative(
microseconds,
nanoseconds,
largestUnit,
zonedRelativeTo
zonedRelativeTo,
precalculatedPlainDateTime
) {
const startNs = GetSlot(zonedRelativeTo, EPOCHNANOSECONDS);

let intermediateNs = startNs;
if (days !== 0) {
const startInstant = GetSlot(zonedRelativeTo, INSTANT);
const timeZone = GetSlot(zonedRelativeTo, TIME_ZONE);
const startDt = GetPlainDateTimeFor(timeZone, startInstant, 'iso8601');
const startDt = precalculatedPlainDateTime ?? GetPlainDateTimeFor(timeZone, startInstant, 'iso8601');
intermediateNs = AddDaysToZonedDateTime(startInstant, startDt, timeZone, 'iso8601', days, 'constrain').epochNs;
}

const endNs = AddInstant(intermediateNs, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
nanoseconds = endNs.subtract(startNs);

if (largestUnit === 'year' || largestUnit === 'month' || largestUnit === 'week' || largestUnit === 'day') {
({ days, nanoseconds } = NanosecondsToDays(nanoseconds, zonedRelativeTo));
({ days, nanoseconds } = NanosecondsToDays(nanoseconds, zonedRelativeTo, precalculatedPlainDateTime));
largestUnit = 'hour';
} else {
days = 0;
Expand Down Expand Up @@ -4193,7 +4196,15 @@ export function DifferenceISODateTime(
return { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds };
}

export function DifferenceZonedDateTime(ns1, ns2, timeZone, calendar, largestUnit, options) {
export function DifferenceZonedDateTime(
ns1,
ns2,
timeZone,
calendar,
largestUnit,
options,
precalculatedDtStart = undefined
) {
const nsDiff = ns2.subtract(ns1);
if (nsDiff.isZero()) {
return {
Expand All @@ -4214,7 +4225,7 @@ export function DifferenceZonedDateTime(ns1, ns2, timeZone, calendar, largestUni
const TemporalInstant = GetIntrinsic('%Temporal.Instant%');
const start = new TemporalInstant(ns1);
const end = new TemporalInstant(ns2);
const dtStart = GetPlainDateTimeFor(timeZone, start, calendar);
const dtStart = precalculatedDtStart ?? GetPlainDateTimeFor(timeZone, start, calendar);
const dtEnd = GetPlainDateTimeFor(timeZone, end, calendar);
let { years, months, weeks, days } = DifferenceISODateTime(
GetSlot(dtStart, ISO_YEAR),
Expand Down Expand Up @@ -4641,13 +4652,35 @@ export function DifferenceTemporalZonedDateTime(operation, zonedDateTime, other,

if (ns1.equals(ns2)) return new Duration();

let precalculatedPlainDateTime;
let plainRelativeTo;
const roundingIsNoop = settings.smallestUnit === 'nanosecond' && settings.roundingIncrement === 1;
const plainDateTimeOrRelativeToWillBeUsed =
!roundingIsNoop ||
settings.smallestUnit === 'year' ||
settings.smallestUnit === 'month' ||
settings.smallestUnit === 'week';
if (plainDateTimeOrRelativeToWillBeUsed) {
precalculatedPlainDateTime = GetPlainDateTimeFor(
timeZone,
GetSlot(zonedDateTime, INSTANT),
GetSlot(zonedDateTime, CALENDAR)
);
plainRelativeTo = TemporalDateTimeToDate(precalculatedPlainDateTime);
}

({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
DifferenceZonedDateTime(ns1, ns2, timeZone, calendar, settings.largestUnit, resolvedOptions));
DifferenceZonedDateTime(
ns1,
ns2,
timeZone,
calendar,
settings.largestUnit,
resolvedOptions,
precalculatedPlainDateTime
));

if (settings.smallestUnit !== 'nanosecond' || settings.roundingIncrement !== 1) {
const plainRelativeToWillBeUsed =
settings.smallestUnit === 'year' || settings.smallestUnit === 'month' || settings.smallestUnit === 'week';
const plainRelativeTo = plainRelativeToWillBeUsed ? ToTemporalDate(zonedDateTime) : undefined;
if (!roundingIsNoop) {
({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = RoundDuration(
years,
months,
Expand All @@ -4663,7 +4696,8 @@ export function DifferenceTemporalZonedDateTime(operation, zonedDateTime, other,
settings.smallestUnit,
settings.roundingMode,
plainRelativeTo,
zonedDateTime
zonedDateTime,
precalculatedPlainDateTime
));
({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
AdjustRoundedDurationDays(
Expand All @@ -4680,7 +4714,8 @@ export function DifferenceTemporalZonedDateTime(operation, zonedDateTime, other,
settings.roundingIncrement,
settings.smallestUnit,
settings.roundingMode,
zonedDateTime
zonedDateTime,
precalculatedPlainDateTime
));
}
}
Expand Down Expand Up @@ -4791,7 +4826,8 @@ export function AddDuration(
µs2,
ns2,
plainRelativeTo,
zonedRelativeTo
zonedRelativeTo,
precalculatedPlainDateTime
) {
const largestUnit1 = DefaultTemporalLargestUnit(y1, mon1, w1, d1, h1, min1, s1, ms1, µs1, ns1);
const largestUnit2 = DefaultTemporalLargestUnit(y2, mon2, w2, d2, h2, min2, s2, ms2, µs2, ns2);
Expand Down Expand Up @@ -4850,8 +4886,10 @@ export function AddDuration(
const TemporalInstant = GetIntrinsic('%Temporal.Instant%');
const timeZone = GetSlot(zonedRelativeTo, TIME_ZONE);
const calendar = GetSlot(zonedRelativeTo, CALENDAR);
const startInstant = GetSlot(zonedRelativeTo, INSTANT);
const startDateTime = precalculatedPlainDateTime ?? GetPlainDateTimeFor(timeZone, startInstant, calendar);
const intermediateNs = AddZonedDateTime(
GetSlot(zonedRelativeTo, INSTANT),
startInstant,
timeZone,
calendar,
y1,
Expand All @@ -4863,7 +4901,8 @@ export function AddDuration(
s1,
ms1,
µs1,
ns1
ns1,
startDateTime
);
const endNs = AddZonedDateTime(
new TemporalInstant(intermediateNs),
Expand Down Expand Up @@ -4902,7 +4941,8 @@ export function AddDuration(
timeZone,
calendar,
largestUnit,
ObjectCreate(null)
ObjectCreate(null),
startDateTime
));
}
}
Expand Down Expand Up @@ -5438,7 +5478,7 @@ export function MoveRelativeDate(calendar, relativeTo, duration, dateAdd) {
return { relativeTo: later, days };
}

export function MoveRelativeZonedDateTime(relativeTo, years, months, weeks, days) {
export function MoveRelativeZonedDateTime(relativeTo, years, months, weeks, days, precalculatedPlainDateTime) {
const timeZone = GetSlot(relativeTo, TIME_ZONE);
const calendar = GetSlot(relativeTo, CALENDAR);
const intermediateNs = AddZonedDateTime(
Expand All @@ -5454,7 +5494,8 @@ export function MoveRelativeZonedDateTime(relativeTo, years, months, weeks, days
0,
0,
0,
0
0,
precalculatedPlainDateTime
);
return CreateTemporalZonedDateTime(intermediateNs, timeZone, calendar);
}
Expand All @@ -5473,7 +5514,8 @@ export function AdjustRoundedDurationDays(
increment,
unit,
roundingMode,
zonedRelativeTo
zonedRelativeTo,
precalculatedPlainDateTime
) {
if (
unit === 'year' ||
Expand Down Expand Up @@ -5511,7 +5553,8 @@ export function AdjustRoundedDurationDays(
0,
0,
0,
0
0,
precalculatedPlainDateTime
);
const TemporalInstant = GetIntrinsic('%Temporal.Instant%');
const dayStartInstant = new TemporalInstant(dayStart);
Expand Down Expand Up @@ -5543,7 +5586,8 @@ export function AdjustRoundedDurationDays(
0,
0,
/* plainRelativeTo = */ undefined,
zonedRelativeTo
zonedRelativeTo,
precalculatedPlainDateTime
));
({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = RoundDuration(
years,
Expand Down Expand Up @@ -5589,7 +5633,8 @@ export function RoundDuration(
unit,
roundingMode,
plainRelativeTo = undefined,
zonedRelativeTo = undefined
zonedRelativeTo = undefined,
precalculatedPlainDateTime = undefined
) {
const TemporalDuration = GetIntrinsic('%Temporal.Duration%');

Expand All @@ -5604,7 +5649,14 @@ export function RoundDuration(
nanoseconds = TotalDurationNanoseconds(hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
let deltaDays;
if (zonedRelativeTo) {
const intermediate = MoveRelativeZonedDateTime(zonedRelativeTo, years, months, weeks, days);
const intermediate = MoveRelativeZonedDateTime(
zonedRelativeTo,
years,
months,
weeks,
days,
precalculatedPlainDateTime
);
({ days: deltaDays, nanoseconds, dayLengthNs } = NanosecondsToDays(nanoseconds, intermediate));
} else {
({ quotient: deltaDays, remainder: nanoseconds } = nanoseconds.divmod(DAY_NANOS));
Expand Down
Loading

0 comments on commit 6eeee90

Please sign in to comment.