Skip to content

Commit

Permalink
Editorial: Make BalanceTimeDuration fallible
Browse files Browse the repository at this point in the history
Commit 1643cc6 only changed IsValidDuration to correctly reject too-large
duration values, but the other half was that BalanceTimeDuration needs to
be able to throw when that happens.

BalanceTimeDuration can throw due to floating-point rounding error if the
normalized time duration could be nearby the limit and largestUnit is
milliseconds, microseconds, or nanoseconds.

Closes: #2785
  • Loading branch information
ptomato authored and Ms2ger committed May 15, 2024
1 parent f1b611f commit a113bda
Show file tree
Hide file tree
Showing 8 changed files with 25 additions and 18 deletions.
2 changes: 1 addition & 1 deletion spec/calendar.html
Original file line number Diff line number Diff line change
Expand Up @@ -1918,7 +1918,7 @@ <h1>Temporal.Calendar.prototype.dateAdd ( _date_, _duration_ [ , _options_ ] )</
1. Set _options_ to ? GetOptionsObject(_options_).
1. Let _overflow_ be ? GetTemporalOverflowOption(_options_).
1. Let _norm_ be NormalizeTimeDuration(_duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]]).
1. Let _balanceResult_ be BalanceTimeDuration(_norm_, *"day"*).
1. Let _balanceResult_ be ! BalanceTimeDuration(_norm_, *"day"*).
1. If _calendar_.[[Identifier]] is *"iso8601"*, then
1. Let _result_ be ? AddISODate(_date_.[[ISOYear]], _date_.[[ISOMonth]], _date_.[[ISODay]], _duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]] + _balanceResult_.[[Days]], _overflow_).
1. Else,
Expand Down
24 changes: 15 additions & 9 deletions spec/duration.html
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ <h1>Temporal.Duration.prototype.round ( _roundTo_ )</h1>
1. If _calendarUnitsPresent_ is *true*, or IsCalendarUnit(_largestUnit_) is *true*, or IsCalendarUnit(_smallestUnit_) is *true*, throw a *RangeError* exception.
1. Let _roundRecord_ be ? RoundTimeDuration(_duration_.[[Days]], _norm_, _roundingIncrement_, _smallestUnit_, _roundingMode_).
1. Let _normWithDays_ be ? Add24HourDaysToNormalizedTimeDuration(_roundRecord_.[[NormalizedDuration]].[[Days]], _roundRecord_.[[NormalizedDuration]].[[NormalizedTime]]).
1. Let _balanceResult_ be BalanceTimeDuration(_normWithDays_, _largestUnit_).
1. Let _balanceResult_ be ? BalanceTimeDuration(_normWithDays_, _largestUnit_).
1. Let _roundResult_ be CreateDurationRecord(0, 0, 0, _balanceResult_.[[Days]], _balanceResult_.[[Hours]], _balanceResult_.[[Minutes]], _balanceResult_.[[Seconds]], _balanceResult_.[[Milliseconds]], _balanceResult_.[[Microseconds]], _balanceResult_.[[Nanoseconds]]).
1. Return ? CreateTemporalDuration(_roundResult_.[[Years]], _roundResult_.[[Months]], _roundResult_.[[Weeks]], _roundResult_.[[Days]], _roundResult_.[[Hours]], _roundResult_.[[Minutes]], _roundResult_.[[Seconds]], _roundResult_.[[Milliseconds]], _roundResult_.[[Microseconds]], _roundResult_.[[Nanoseconds]]).
</emu-alg>
Expand Down Expand Up @@ -561,7 +561,7 @@ <h1>Temporal.Duration.prototype.toString ( [ _options_ ] )</h1>
1. Let _largestUnit_ be DefaultTemporalLargestUnit(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]], _duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]]).
1. Let _roundRecord_ be ? RoundTimeDuration(0, _norm_, _precision_.[[Increment]], _precision_.[[Unit]], _roundingMode_).
1. Set _norm_ to _roundRecord_.[[NormalizedDuration]].[[NormalizedTime]].
1. Let _result_ be BalanceTimeDuration(_norm_, LargerOfTwoTemporalUnits(_largestUnit_, *"second"*)).
1. Let _result_ be ! BalanceTimeDuration(_norm_, LargerOfTwoTemporalUnits(_largestUnit_, *"second"*)).
1. Set _result_ to CreateDurationRecord(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]] + _result_.[[Days]], _result_.[[Hours]], _result_.[[Minutes]], _result_.[[Seconds]], _result_.[[Milliseconds]], _result_.[[Microseconds]], _result_.[[Nanoseconds]]).
1. Else,
1. Let _result_ be _duration_.
Expand Down Expand Up @@ -1609,11 +1609,16 @@ <h1>
BalanceTimeDuration (
_norm_: a Normalized Time Duration Record,
_largestUnit_: a String,
): a Time Duration Record
): either a normal completion containing a Time Duration Record or a throw completion
</h1>
<dl class="header">
<dt>description</dt>
<dd>It converts a normalized time duration into a time duration with separated units, up to _largestUnit_.</dd>
<dd>
It converts a normalized time duration into a time duration with separated units, up to _largestUnit_.
The conversion may be lossy if _largestUnit_ is *"milliseconds"*, *"microseconds"*, or *"nanoseconds"*.
In that case, the fields of the returned Time Duration Record may be unsafe (but float64-representable) integers.
The result of a lossy conversion may be outside the allowed range for Durations, even if the input was not.
</dd>
</dl>
<emu-alg>
1. Let _days_, _hours_, _minutes_, _seconds_, _milliseconds_, and _microseconds_ be 0.
Expand Down Expand Up @@ -1670,6 +1675,7 @@ <h1>
1. Else,
1. Assert: _largestUnit_ is *"nanosecond"*.
1. NOTE: When _largestUnit_ is *"millisecond"*, *"microsecond"*, or *"nanosecond"*, _milliseconds_, _microseconds_, or _nanoseconds_ may be an unsafe integer. In this case, care must be taken when implementing the calculation using floating point arithmetic. It can be implemented in C++ using `std::fma()`. String manipulation will also give an exact result, since the multiplication is by a power of 10.
1. If IsValidDuration(0, 0, 0, _days_, _hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_) is *false*, throw a *RangeError* exception.
1. Return CreateTimeDurationRecord(_days_ &times; _sign_, _hours_ &times; _sign_, _minutes_ &times; _sign_, _seconds_ &times; _sign_, _milliseconds_ &times; _sign_, _microseconds_ &times; _sign_, _nanoseconds_ &times; _sign_).
</emu-alg>
</emu-clause>
Expand Down Expand Up @@ -2074,7 +2080,7 @@ <h1>
1. Set _duration_ to ? BubbleRelativeDuration(_sign_, _duration_, _nudgeResult_.[[NudgedEpochNs]], _dateTime_, _calendarRec_, _timeZoneRec_, _largestUnit_, _startUnit_).
1. If IsCalendarUnit(_largestUnit_) is *true* or _largestUnit_ is *"day"*, then
1. Set _largestUnit_ to *"hour"*.
1. Let _balanceResult_ be BalanceTimeDuration(_duration_.[[NormalizedTime]], _largestUnit_).
1. Let _balanceResult_ be ? BalanceTimeDuration(_duration_.[[NormalizedTime]], _largestUnit_).
1. Return the Record {
[[Duration]]: CreateDurationRecord(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]], _balanceResult_.[[Hours]], _balanceResult_.[[Minutes]], _balanceResult_.[[Seconds]], _balanceResult_.[[Milliseconds]], _balanceResult_.[[Microseconds]], _balanceResult_.[[Nanoseconds]]),
[[Total]]: _nudgeResult_.[[Total]]
Expand Down Expand Up @@ -2183,7 +2189,7 @@ <h1>
1. If IsCalendarUnit(_largestUnit_), throw a *RangeError* exception.
1. Let _normResult_ be ? AddNormalizedTimeDuration(_norm1_, _norm2_).
1. Set _normResult_ to ? Add24HourDaysToNormalizedTimeDuration(_normResult_, _d1_ + _d2_).
1. Let _result_ be BalanceTimeDuration(_normResult_, _largestUnit_).
1. Let _result_ be ? BalanceTimeDuration(_normResult_, _largestUnit_).
1. Return ! CreateTemporalDuration(0, 0, 0, _result_.[[Days]], _result_.[[Hours]], _result_.[[Minutes]], _result_.[[Seconds]], _result_.[[Milliseconds]], _result_.[[Microseconds]], _result_.[[Nanoseconds]]).
1. If _plainRelativeTo_ is not *undefined*, then
1. Let _dateDuration1_ be ! CreateTemporalDuration(_y1_, _mon1_, _w1_, _d1_, 0, 0, 0, 0, 0, 0).
Expand All @@ -2196,7 +2202,7 @@ <h1>
1. Let _dateDifference_ be ? DifferenceDate(_calendarRec_, _plainRelativeTo_, _end_, _differenceOptions_).
1. Let _norm1WithDays_ be ? Add24HourDaysToNormalizedTimeDuration(_norm1_, _dateDifference_.[[Days]]).
1. Let _normResult_ be ? AddNormalizedTimeDuration(_norm1WithDays_, _norm2_).
1. Let _result_ be BalanceTimeDuration(_normResult_, _largestUnit_).
1. Let _result_ be ? BalanceTimeDuration(_normResult_, _largestUnit_).
1. Return ! CreateTemporalDuration(_dateDifference_.[[Years]], _dateDifference_.[[Months]], _dateDifference_.[[Weeks]], _result_.[[Days]], _result_.[[Hours]], _result_.[[Minutes]], _result_.[[Seconds]], _result_.[[Milliseconds]], _result_.[[Microseconds]], _result_.[[Nanoseconds]]).
1. Assert: _zonedRelativeTo_ is not *undefined*.
1. Let _largestUnitCategory_ be the value in the "Category" column of the row of <emu-xref href="#table-temporal-units"></emu-xref> whose "Singular" column contains _largestUnit_.
Expand All @@ -2208,10 +2214,10 @@ <h1>
1. Let _endNs_ be ? AddZonedDateTime(_intermediateNs_, _timeZoneRec_, _calendarRec_, _y2_, _mon2_, _w2_, _d2_, _norm2_).
1. If _largestUnitCategory_ is ~time~, then
1. Let _norm_ be NormalizedTimeDurationFromEpochNanosecondsDifference(_endNs_, _zonedRelativeTo_.[[Nanoseconds]]).
1. Let _result_ be BalanceTimeDuration(_norm_, _largestUnit_).
1. Let _result_ be ? BalanceTimeDuration(_norm_, _largestUnit_).
1. Return ! CreateTemporalDuration(0, 0, 0, 0, _result_.[[Hours]], _result_.[[Minutes]], _result_.[[Seconds]], _result_.[[Milliseconds]], _result_.[[Microseconds]], _result_.[[Nanoseconds]]).
1. Let _diffResult_ be ? DifferenceZonedDateTime(_zonedRelativeTo_.[[Nanoseconds]], _endNs_, _timeZoneRec_, _calendarRec_, _largestUnit_, OrdinaryObjectCreate(*null*), _startDateTime_).
1. Let _timeResult_ be BalanceTimeDuration(_diffResult_.[[NormalizedTime]], *"hour"*).
1. Let _timeResult_ be ! BalanceTimeDuration(_diffResult_.[[NormalizedTime]], *"hour"*).
1. Return ! CreateTemporalDuration(_diffResult_.[[Years]], _diffResult_.[[Months]], _diffResult_.[[Weeks]], _diffResult_.[[Days]], _timeResult_.[[Hours]], _timeResult_.[[Minutes]], _timeResult_.[[Seconds]], _timeResult_.[[Milliseconds]], _timeResult_.[[Microseconds]], _timeResult_.[[Nanoseconds]]).
</emu-alg>
</emu-clause>
Expand Down
2 changes: 1 addition & 1 deletion spec/instant.html
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,7 @@ <h1>
1. Let _settings_ be ? GetDifferenceSettings(_operation_, _resolvedOptions_, ~time~, &laquo; &raquo;, *"nanosecond"*, *"second"*).
1. Let _diffRecord_ be DifferenceInstant(_instant_.[[Nanoseconds]], _other_.[[Nanoseconds]], _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[RoundingMode]]).
1. Let _norm_ be _diffRecord_.[[NormalizedTimeDuration]].
1. Let _result_ be BalanceTimeDuration(_norm_, _settings_.[[LargestUnit]]).
1. Let _result_ be ! BalanceTimeDuration(_norm_, _settings_.[[LargestUnit]]).
1. Return ! CreateTemporalDuration(0, 0, 0, 0, _sign_ &times; _result_.[[Hours]], _sign_ &times; _result_.[[Minutes]], _sign_ &times; _result_.[[Seconds]], _sign_ &times; _result_.[[Milliseconds]], _sign_ &times; _result_.[[Microseconds]], _sign_ &times; _result_.[[Nanoseconds]]).
</emu-alg>
</emu-clause>
Expand Down
3 changes: 2 additions & 1 deletion spec/plaindate.html
Original file line number Diff line number Diff line change
Expand Up @@ -1092,7 +1092,8 @@ <h1>
1. Return ? CalendarDateAdd(_calendarRec_, _plainDate_, _duration_, _options_).
1. Let _overflow_ be ? GetTemporalOverflowOption(_options_).
1. Let _norm_ be NormalizeTimeDuration(_duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]]).
1. Let _days_ be _duration_.[[Days]] + BalanceTimeDuration(_norm_, *"day"*).[[Days]].
1. Let _balancedDuration_ be ! BalanceTimeDuration(_norm_, *"day"*).
1. Let _days_ be _duration_.[[Days]] + _balancedDuration_.[[Days]].
1. Let _result_ be ? AddISODate(_plainDate_.[[ISOYear]], _plainDate_.[[ISOMonth]], _plainDate_.[[ISODay]], 0, 0, 0, _days_, _overflow_).
1. Return ! CreateTemporalDate(_result_.[[Year]], _result_.[[Month]], _result_.[[Day]], _calendarRec_.[[Receiver]]).
</emu-alg>
Expand Down
2 changes: 1 addition & 1 deletion spec/plaindatetime.html
Original file line number Diff line number Diff line change
Expand Up @@ -1398,7 +1398,7 @@ <h1>
1. Let _diff_ be ? DifferenceISODateTime(_y1_, _mon1_, _d1_, _h1_, _min1_, _s1_, _ms1_, _mus1_, _ns1_, _y2_, _mon2_, _d2_, _h2_, _min2_, _s2_, _ms2_, _mus2_, _ns2_, _calendarRec_, _largestUnit_, _resolvedOptions_).
1. If _smallestUnit_ is *"nanosecond"* and _roundingIncrement_ = 1, then
1. Let _normWithDays_ be ? Add24HourDaysToNormalizedTimeDuration(_diff_.[[NormalizedTime]], _diff_.[[Days]]).
1. Let _timeResult_ be BalanceTimeDuration(_normWithDays_, _largestUnit_).
1. Let _timeResult_ be ! BalanceTimeDuration(_normWithDays_, _largestUnit_).
1. Let _total_ be NormalizedTimeDurationSeconds(_normWithDays_) × 10<sup>9</sup> + NormalizedTimeDurationSubseconds(_normWithDays_).
1. Let _durationRecord_ be CreateDurationRecord(_diff_.[[Years]], _diff_.[[Months]], _diff_.[[Weeks]], _timeResult_.[[Days]], _timeResult_.[[Hours]], _timeResult_.[[Minutes]], _timeResult_.[[Seconds]], _timeResult_.[[Milliseconds]], _timeResult_.[[Microseconds]], _timeResult_.[[Nanoseconds]]).
1. Return the Record { [[DurationRecord]]: _durationRecord_, [[Total]]: _total_ }.
Expand Down
2 changes: 1 addition & 1 deletion spec/plaintime.html
Original file line number Diff line number Diff line change
Expand Up @@ -1097,7 +1097,7 @@ <h1>
1. If _settings_.[[SmallestUnit]] is not *"nanosecond"* or _settings_.[[RoundingIncrement]] &ne; 1, then
1. Let _roundRecord_ be ! RoundTimeDuration(0, _norm_, _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[RoundingMode]]).
1. Set _norm_ to _roundRecord_.[[NormalizedDuration]].[[NormalizedTime]].
1. Let _result_ be BalanceTimeDuration(_norm_, _settings_.[[LargestUnit]]).
1. Let _result_ be ! BalanceTimeDuration(_norm_, _settings_.[[LargestUnit]]).
1. Return ! CreateTemporalDuration(0, 0, 0, 0, _sign_ &times; _result_.[[Hours]], _sign_ &times; _result_.[[Minutes]], _sign_ &times; _result_.[[Seconds]], _sign_ &times; _result_.[[Milliseconds]], _sign_ &times; _result_.[[Microseconds]], _sign_ &times; _result_.[[Nanoseconds]]).
</emu-alg>
</emu-clause>
Expand Down
2 changes: 1 addition & 1 deletion spec/plainyearmonth.html
Original file line number Diff line number Diff line change
Expand Up @@ -771,7 +771,7 @@ <h1>
1. Set _duration_ to CreateNegatedTemporalDuration(_duration_).
1. Set _options_ to ? GetOptionsObject(_options_).
1. Let _norm_ be NormalizeTimeDuration(_duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]]).
1. Let _balanceResult_ be BalanceTimeDuration(_norm_, *"day"*).
1. Let _balanceResult_ be ! BalanceTimeDuration(_norm_, *"day"*).
1. Let _days_ be _duration_.[[Days]] + _balanceResult_.[[Days]].
1. Let _sign_ be DurationSign(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _days_, 0, 0, 0, 0, 0, 0).
1. Let _calendarRec_ be ? CreateCalendarMethodsRecord(_yearMonth_.[[Calendar]], « ~date-add~, ~date-from-fields~, ~day~, ~fields~, ~year-month-from-fields~ »).
Expand Down
6 changes: 3 additions & 3 deletions spec/zoneddatetime.html
Original file line number Diff line number Diff line change
Expand Up @@ -1489,13 +1489,13 @@ <h1>
1. If IsCalendarUnit(_largestUnit_) is *false* and _largestUnit_ is not *"day"*, then
1. Let _diffRecord_ be DifferenceInstant(_ns1_, _ns2_, _roundingIncrement_, _smallestUnit_, _roundingMode_).
1. Let _norm_ be _diffRecord_.[[NormalizedTimeDuration]].
1. Let _result_ be BalanceTimeDuration(_norm_, _largestUnit_).
1. Let _result_ be ! BalanceTimeDuration(_norm_, _largestUnit_).
1. Let _durationRecord_ be CreateDurationRecord(0, 0, 0, 0, _result_.[[Hours]], _result_.[[Minutes]], _result_.[[Seconds]], _result_.[[Milliseconds]], _result_.[[Microseconds]], _result_.[[Nanoseconds]]).
1. Return the Record { [[DurationRecord]]: _durationRecord_, [[Total]]: _diffRecord_.[[Total]] }.
1. Let _difference_ be ? DifferenceZonedDateTime(_ns1_, _ns2_, _timeZoneRec_, _calendarRec_, _largestUnit_, _resolvedOptions_, _precalculatedPlainDateTime_).
1. If _smallestUnit_ is *"nanosecond"* and _roundingIncrement_ = 1, let _roundingGranularityIsNoop_ be *true*; else let _roundingGranularityIsNoop_ be *false*.
1. If _roundingGranularityIsNoop_ is *true*, then
1. Let _timeResult_ be BalanceTimeDuration(_difference_.[[NormalizedTime]], *"hour"*).
1. Let _timeResult_ be ! BalanceTimeDuration(_difference_.[[NormalizedTime]], *"hour"*).
1. Let _total_ be NormalizedTimeDurationSeconds(_difference_.[[NormalizedTime]]) × 10<sup>9</sup> + NormalizedTimeDurationSubseconds(_difference_.[[NormalizedTime]]).
1. Let _durationRecord_ be CreateDurationRecord(_difference_.[[Years]], _difference_.[[Months]], _difference_.[[Weeks]], _difference_.[[Days]], _timeResult_.[[Hours]], _timeResult_.[[Minutes]], _timeResult_.[[Seconds]], _timeResult_.[[Milliseconds]], _timeResult_.[[Microseconds]], _timeResult_.[[Nanoseconds]]).
1. Return the Record { [[DurationRecord]]: _durationRecord_, [[Total]]: _total_ }.
Expand Down Expand Up @@ -1527,7 +1527,7 @@ <h1>
1. If _settings_.[[LargestUnit]] is not one of *"year"*, *"month"*, *"week"*, or *"day"*, then
1. Let _diffRecord_ be DifferenceInstant(_zonedDateTime_.[[Nanoseconds]], _other_.[[Nanoseconds]], _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[RoundingMode]]).
1. Let _norm_ be _diffRecord_.[[NormalizedTimeDuration]].
1. Let _result_ be BalanceTimeDuration(_norm_, _settings_.[[LargestUnit]]).
1. Let _result_ be ! BalanceTimeDuration(_norm_, _settings_.[[LargestUnit]]).
1. Return ! CreateTemporalDuration(0, 0, 0, 0, _sign_ &times; _result_.[[Hours]], _sign_ &times; _result_.[[Minutes]], _sign_ &times; _result_.[[Seconds]], _sign_ &times; _result_.[[Milliseconds]], _sign_ &times; _result_.[[Microseconds]], _sign_ &times; _result_.[[Nanoseconds]]).
1. NOTE: To calculate differences in two different time zones, _settings_.[[LargestUnit]] must be *"hour"* or smaller, because day lengths can vary between time zones due to DST and other UTC offset shifts.
1. If ? TimeZoneEquals(_zonedDateTime_.[[TimeZone]], _other_.[[TimeZone]]) is *false*, then
Expand Down

0 comments on commit a113bda

Please sign in to comment.