From 853a2122a45425e9bb9262e4e7d99ec332dfc5af Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Tue, 7 Mar 2023 20:25:07 -0800 Subject: [PATCH] Normative: Avoid reconverting Zoned- to PlainDateTime in AddZonedDateTime There are several places where a ZonedDateTime is converted to a PlainDateTime, which is a user-visible time zone operation, and then the same operation is performed again right afterwards in AddZonedDateTime. Avoid this by making AddZonedDateTime take an optional precalculated PlainDateTime. If we have it, we can pass it in, and avoid the second conversion. --- polyfill/lib/ecmascript.mjs | 27 ++++++++++++++++++++++----- polyfill/lib/zoneddatetime.mjs | 15 ++------------- spec/zoneddatetime.html | 19 ++++++++++++------- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index 33d358615f..77b298d009 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -3338,7 +3338,7 @@ export function NanosecondsToDays(nanoseconds, zonedRelativeTo) { 'day', ObjectCreate(null) ); - let intermediateNs = AddZonedDateTime(start, timeZone, calendar, 0, 0, 0, days, 0, 0, 0, 0, 0, 0); + let intermediateNs = AddZonedDateTime(start, timeZone, calendar, 0, 0, 0, days, 0, 0, 0, 0, 0, 0, dtStart); // may disambiguate // If clock time after addition was in the middle of a skipped period, the @@ -3353,7 +3353,22 @@ export function NanosecondsToDays(nanoseconds, zonedRelativeTo) { if (sign === 1) { while (days.greater(0) && intermediateNs.greater(endNs)) { days = days.prev(); - intermediateNs = AddZonedDateTime(start, timeZone, calendar, 0, 0, 0, days.toJSNumber(), 0, 0, 0, 0, 0, 0); + intermediateNs = AddZonedDateTime( + start, + timeZone, + calendar, + 0, + 0, + 0, + days.toJSNumber(), + 0, + 0, + 0, + 0, + 0, + 0, + dtStart + ); // may do disambiguation } } @@ -4253,7 +4268,7 @@ export function DifferenceZonedDateTime(ns1, ns2, timeZone, calendar, largestUni largestUnit, options ); - let intermediateNs = AddZonedDateTime(start, timeZone, calendar, years, months, weeks, 0, 0, 0, 0, 0, 0, 0); + let intermediateNs = AddZonedDateTime(start, timeZone, calendar, years, months, weeks, 0, 0, 0, 0, 0, 0, 0, dtStart); // may disambiguate let timeRemainderNs = ns2.subtract(intermediateNs); const intermediate = CreateTemporalZonedDateTime(intermediateNs, timeZone, calendar); @@ -4947,7 +4962,8 @@ export function AddZonedDateTime( ms, µs, ns, - options + precalculatedPlainDateTime = undefined, + options = undefined ) { // If only time is to be added, then use Instant math. It's not OK to fall // through to the date/time code below because compatible disambiguation in @@ -4964,7 +4980,7 @@ export function AddZonedDateTime( // RFC 5545 requires the date portion to be added in calendar days and the // time portion to be added in exact time. - let dt = GetPlainDateTimeFor(timeZone, instant, calendar); + const dt = precalculatedPlainDateTime ?? GetPlainDateTimeFor(timeZone, instant, calendar); const datePart = CreateTemporalDate(GetSlot(dt, ISO_YEAR), GetSlot(dt, ISO_MONTH), GetSlot(dt, ISO_DAY), calendar); const dateDuration = new TemporalDuration(years, months, weeks, days, 0, 0, 0, 0, 0, 0); const addedDate = CalendarDateAdd(calendar, datePart, dateDuration, options); @@ -5170,6 +5186,7 @@ export function AddDurationToOrSubtractDurationFromZonedDateTime(operation, zone sign * milliseconds, sign * microseconds, sign * nanoseconds, + undefined, options ); return CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar); diff --git a/polyfill/lib/zoneddatetime.mjs b/polyfill/lib/zoneddatetime.mjs index 498f6114bc..60a6110d95 100644 --- a/polyfill/lib/zoneddatetime.mjs +++ b/polyfill/lib/zoneddatetime.mjs @@ -387,20 +387,9 @@ export class ZonedDateTime { let nanosecond = GetSlot(dt, ISO_NANOSECOND); const calendar = GetSlot(this, CALENDAR); - const dtStart = ES.CreateTemporalDateTime( - GetSlot(dt, ISO_YEAR), - GetSlot(dt, ISO_MONTH), - GetSlot(dt, ISO_DAY), - 0, - 0, - 0, - 0, - 0, - 0, - 'iso8601' - ); + const dtStart = ES.CreateTemporalDateTime(year, month, day, 0, 0, 0, 0, 0, 0, 'iso8601'); const instantStart = ES.GetInstantFor(timeZone, dtStart, 'compatible'); - const endNs = ES.AddZonedDateTime(instantStart, timeZone, calendar, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0); + const endNs = ES.AddZonedDateTime(instantStart, timeZone, calendar, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, dtStart); const dayLengthNs = endNs.subtract(GetSlot(instantStart, EPOCHNANOSECONDS)); if (dayLengthNs.leq(0)) { throw new RangeError('cannot round a ZonedDateTime in a calendar with zero- or negative-length days'); diff --git a/spec/zoneddatetime.html b/spec/zoneddatetime.html index db120eedf4..364538fd68 100644 --- a/spec/zoneddatetime.html +++ b/spec/zoneddatetime.html @@ -768,7 +768,7 @@

Temporal.ZonedDateTime.prototype.round ( _roundTo_ )

1. Let _dtStart_ be ? CreateTemporalDateTime(_temporalDateTime_.[[ISOYear]], _temporalDateTime_.[[ISOMonth]], _temporalDateTime_.[[ISODay]], 0, 0, 0, 0, 0, 0, *"iso8601"*). 1. Let _instantStart_ be ? GetInstantFor(_timeZone_, _dtStart_, *"compatible"*). 1. Let _startNs_ be _instantStart_.[[Nanoseconds]]. - 1. Let _endNs_ be ? AddZonedDateTime(_startNs_, _timeZone_, _calendar_, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0). + 1. Let _endNs_ be ? AddZonedDateTime(_startNs_, _timeZone_, _calendar_, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, _dtStart_). 1. Let _dayLengthNs_ be ℝ(_endNs_ - _startNs_). 1. If _dayLengthNs_ ≤ 0, then 1. Throw a *RangeError* exception. @@ -1290,6 +1290,7 @@

_milliseconds_: an integer, _microseconds_: an integer, _nanoseconds_: an integer, + optional _precalculatedPlainDateTime_: a Temporal.PlainDateTime or *undefined*, optional _options_: an Object, ): either a normal completion containing a BigInt or an abrupt completion

@@ -1300,13 +1301,17 @@

As specified in RFC 5545, the date portion of the duration is added in calendar days, and the time portion is added in exact time. +

Unless _precalculatedPlainDateTime_ is supplied, the given _timeZone_'s `getOffsetNanosecondsFor` method will be called to convert _epochNanoseconds_ to a wall-clock time.

1. If _options_ is not present, set _options_ to *undefined*. 1. Assert: Type(_options_) is Object or Undefined. 1. If _years_ = 0, _months_ = 0, _weeks_ = 0, and _days_ = 0, then 1. Return ? AddInstant(_epochNanoseconds_, _hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_). - 1. Let _instant_ be ! CreateTemporalInstant(_epochNanoseconds_). - 1. Let _temporalDateTime_ be ? GetPlainDateTimeFor(_timeZone_, _instant_, _calendar_). + 1. If _precalculatedPlainDateTime_ is not *undefined*, then + 1. Let _temporalDateTime_ be _precalculatedPlainDateTime_. + 1. Else, + 1. Let _instant_ be ! CreateTemporalInstant(_epochNanoseconds_). + 1. Let _temporalDateTime_ be ? GetPlainDateTimeFor(_timeZone_, _instant_, _calendar_). 1. Let _datePart_ be ! CreateTemporalDate(_temporalDateTime_.[[ISOYear]], _temporalDateTime_.[[ISOMonth]], _temporalDateTime_.[[ISODay]], _calendar_). 1. Let _dateDuration_ be ! CreateTemporalDuration(_years_, _months_, _weeks_, _days_, 0, 0, 0, 0, 0, 0). 1. Let _addedDate_ be ? CalendarDateAdd(_calendar_, _datePart_, _dateDuration_, _options_). @@ -1341,7 +1346,7 @@

1. Let _endInstant_ be ! CreateTemporalInstant(_ns2_). 1. Let _endDateTime_ be ? GetPlainDateTimeFor(_timeZone_, _endInstant_, _calendar_). 1. Let _dateDifference_ be ? DifferenceISODateTime(_startDateTime_.[[ISOYear]], _startDateTime_.[[ISOMonth]], _startDateTime_.[[ISODay]], _startDateTime_.[[ISOHour]], _startDateTime_.[[ISOMinute]], _startDateTime_.[[ISOSecond]], _startDateTime_.[[ISOMillisecond]], _startDateTime_.[[ISOMicrosecond]], _startDateTime_.[[ISONanosecond]], _endDateTime_.[[ISOYear]], _endDateTime_.[[ISOMonth]], _endDateTime_.[[ISODay]], _endDateTime_.[[ISOHour]], _endDateTime_.[[ISOMinute]], _endDateTime_.[[ISOSecond]], _endDateTime_.[[ISOMillisecond]], _endDateTime_.[[ISOMicrosecond]], _endDateTime_.[[ISONanosecond]], _calendar_, _largestUnit_, _options_). - 1. Let _intermediateNs_ be ? AddZonedDateTime(_ns1_, _timeZone_, _calendar_, _dateDifference_.[[Years]], _dateDifference_.[[Months]], _dateDifference_.[[Weeks]], 0, 0, 0, 0, 0, 0, 0). + 1. Let _intermediateNs_ be ? AddZonedDateTime(_ns1_, _timeZone_, _calendar_, _dateDifference_.[[Years]], _dateDifference_.[[Months]], _dateDifference_.[[Weeks]], 0, 0, 0, 0, 0, 0, 0, _startDateTime_). 1. Let _timeRemainderNs_ be _ns2_ - _intermediateNs_. 1. Let _intermediate_ be ! CreateTemporalZonedDateTime(_intermediateNs_, _timeZone_, _calendar_). 1. Let _result_ be ? NanosecondsToDays(ℝ(_timeRemainderNs_), _intermediate_). @@ -1377,11 +1382,11 @@

1. Let _endDateTime_ be ? GetPlainDateTimeFor(_zonedRelativeTo_.[[TimeZone]], _endInstant_, _zonedRelativeTo_.[[Calendar]]). 1. Let _dateDifference_ be ? DifferenceISODateTime(_startDateTime_.[[ISOYear]], _startDateTime_.[[ISOMonth]], _startDateTime_.[[ISODay]], _startDateTime_.[[ISOHour]], _startDateTime_.[[ISOMinute]], _startDateTime_.[[ISOSecond]], _startDateTime_.[[ISOMillisecond]], _startDateTime_.[[ISOMicrosecond]], _startDateTime_.[[ISONanosecond]], _endDateTime_.[[ISOYear]], _endDateTime_.[[ISOMonth]], _endDateTime_.[[ISODay]], _endDateTime_.[[ISOHour]], _endDateTime_.[[ISOMinute]], _endDateTime_.[[ISOSecond]], _endDateTime_.[[ISOMillisecond]], _endDateTime_.[[ISOMicrosecond]], _endDateTime_.[[ISONanosecond]], _zonedRelativeTo_.[[Calendar]], *"day"*, OrdinaryObjectCreate(*null*)). 1. Let _days_ be _dateDifference_.[[Days]]. - 1. Let _intermediateNs_ be ℝ(? AddZonedDateTime(ℤ(_startNs_), _zonedRelativeTo_.[[TimeZone]], _zonedRelativeTo_.[[Calendar]], 0, 0, 0, _days_, 0, 0, 0, 0, 0, 0)). + 1. Let _intermediateNs_ be ℝ(? AddZonedDateTime(ℤ(_startNs_), _zonedRelativeTo_.[[TimeZone]], _zonedRelativeTo_.[[Calendar]], 0, 0, 0, _days_, 0, 0, 0, 0, 0, 0, _startDateTime_)). 1. If _sign_ is 1, then 1. Repeat, while _days_ > 0 and _intermediateNs_ > _endNs_, 1. Set _days_ to _days_ - 1. - 1. Set _intermediateNs_ to ℝ(? AddZonedDateTime(ℤ(_startNs_), _zonedRelativeTo_.[[TimeZone]], _zonedRelativeTo_.[[Calendar]], 0, 0, 0, _days_, 0, 0, 0, 0, 0, 0)). + 1. Set _intermediateNs_ to ℝ(? AddZonedDateTime(ℤ(_startNs_), _zonedRelativeTo_.[[TimeZone]], _zonedRelativeTo_.[[Calendar]], 0, 0, 0, _days_, 0, 0, 0, 0, 0, 0, _startDateTime_)). 1. Set _nanoseconds_ to _endNs_ - _intermediateNs_. 1. Let _done_ be *false*. 1. Let _dayLengthNs_ be ~unset~. @@ -1465,7 +1470,7 @@

1. Set _options_ to ? GetOptionsObject(_options_). 1. Let _timeZone_ be _zonedDateTime_.[[TimeZone]]. 1. Let _calendar_ be _zonedDateTime_.[[Calendar]]. - 1. Let _epochNanoseconds_ be ? AddZonedDateTime(_zonedDateTime_.[[Nanoseconds]], _timeZone_, _calendar_, _sign_ × _duration_.[[Years]], _sign_ × _duration_.[[Months]], _sign_ × _duration_.[[Weeks]], _sign_ × _duration_.[[Days]], _sign_ × _duration_.[[Hours]], _sign_ × _duration_.[[Minutes]], _sign_ × _duration_.[[Seconds]], _sign_ × _duration_.[[Milliseconds]], _sign_ × _duration_.[[Microseconds]], _sign_ × _duration_.[[Nanoseconds]], _options_). + 1. Let _epochNanoseconds_ be ? AddZonedDateTime(_zonedDateTime_.[[Nanoseconds]], _timeZone_, _calendar_, _sign_ × _duration_.[[Years]], _sign_ × _duration_.[[Months]], _sign_ × _duration_.[[Weeks]], _sign_ × _duration_.[[Days]], _sign_ × _duration_.[[Hours]], _sign_ × _duration_.[[Minutes]], _sign_ × _duration_.[[Seconds]], _sign_ × _duration_.[[Milliseconds]], _sign_ × _duration_.[[Microseconds]], _sign_ × _duration_.[[Nanoseconds]], *undefined*, _options_). 1. Return ! CreateTemporalZonedDateTime(_epochNanoseconds_, _timeZone_, _calendar_).