LibJS: Use relative date in NudgeToCalendarUnit when comparing durations

This is a normative change in the Temporal proposal. See:
17e12be

The test added here would previously crash.
This commit is contained in:
Timothy Flynn 2025-11-20 07:03:09 -05:00 committed by Tim Flynn
parent 0eb28a1a54
commit 54b9ddcc80
Notes: github-actions[bot] 2025-11-20 13:30:03 +00:00
3 changed files with 133 additions and 52 deletions

View file

@ -847,8 +847,8 @@ Crypto::BigFraction total_time_duration(TimeDuration const& time_duration, Unit
return Crypto::BigFraction { time_duration } / Crypto::BigFraction { Crypto::SignedBigInteger { divisor } }; return Crypto::BigFraction { time_duration } / Crypto::BigFraction { Crypto::SignedBigInteger { divisor } };
} }
// 7.5.33 NudgeToCalendarUnit ( sign, duration, originEpochNs, destEpochNs, isoDateTime, timeZone, calendar, increment, unit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-nudgetocalendarunit // 7.5.33 ComputeNudgeWindow ( sign, duration, originEpochNs, isoDateTime, timeZone, calendar, increment, unit, additionalShift ), https://tc39.es/proposal-temporal/#sec-temporal-computenudgewindow
ThrowCompletionOr<CalendarNudgeResult> nudge_to_calendar_unit(VM& vm, i8 sign, InternalDuration const& duration, Crypto::SignedBigInteger const& origin_epoch_ns, Crypto::SignedBigInteger const& dest_epoch_ns, ISODateTime const& iso_date_time, Optional<StringView> time_zone, StringView calendar, u64 increment, Unit unit, RoundingMode rounding_mode) ThrowCompletionOr<NudgeWindow> compute_nudge_window(VM& vm, i8 sign, InternalDuration const& duration, Crypto::SignedBigInteger const& origin_epoch_ns, ISODateTime const& iso_date_time, Optional<StringView> time_zone, StringView calendar, u64 increment, Unit unit, bool additional_shift)
{ {
DateDuration start_duration; DateDuration start_duration;
DateDuration end_duration; DateDuration end_duration;
@ -856,21 +856,26 @@ ThrowCompletionOr<CalendarNudgeResult> nudge_to_calendar_unit(VM& vm, i8 sign, I
double r1 = 0; double r1 = 0;
double r2 = 0; double r2 = 0;
auto signed_increment = static_cast<double>(increment) * sign;
// 1. If unit is YEAR, then // 1. If unit is YEAR, then
if (unit == Unit::Year) { if (unit == Unit::Year) {
// a. Let years be RoundNumberToIncrement(duration.[[Date]].[[Years]], increment, TRUNC). // a. Let years be RoundNumberToIncrement(duration.[[Date]].[[Years]], increment, TRUNC).
auto years = round_number_to_increment(duration.date.years, increment, RoundingMode::Trunc); auto years = round_number_to_increment(duration.date.years, increment, RoundingMode::Trunc);
// b. Let r1 be years. // b. If additionalShift is false, then
r1 = years; // i. Let r1 be years.
// c. Else,
// i. Let r1 be years + increment × sign.
r1 = additional_shift ? years + signed_increment : years;
// c. Let r2 be years + increment × sign. // d. Let r2 be r1 + increment × sign.
r2 = years + static_cast<double>(increment) * sign; r2 = r1 + signed_increment;
// d. Let startDuration be ? CreateDateDurationRecord(r1, 0, 0, 0). // e. Let startDuration be ? CreateDateDurationRecord(r1, 0, 0, 0).
start_duration = TRY(create_date_duration_record(vm, r1, 0, 0, 0)); start_duration = TRY(create_date_duration_record(vm, r1, 0, 0, 0));
// e. Let endDuration be ? CreateDateDurationRecord(r2, 0, 0, 0). // f. Let endDuration be ? CreateDateDurationRecord(r2, 0, 0, 0).
end_duration = TRY(create_date_duration_record(vm, r2, 0, 0, 0)); end_duration = TRY(create_date_duration_record(vm, r2, 0, 0, 0));
} }
// 2. Else if unit is MONTH, then // 2. Else if unit is MONTH, then
@ -878,16 +883,19 @@ ThrowCompletionOr<CalendarNudgeResult> nudge_to_calendar_unit(VM& vm, i8 sign, I
// a. Let months be RoundNumberToIncrement(duration.[[Date]].[[Months]], increment, TRUNC). // a. Let months be RoundNumberToIncrement(duration.[[Date]].[[Months]], increment, TRUNC).
auto months = round_number_to_increment(duration.date.months, increment, RoundingMode::Trunc); auto months = round_number_to_increment(duration.date.months, increment, RoundingMode::Trunc);
// b. Let r1 be months. // b. If additionalShift is false, then
r1 = months; // i. Let r1 be months.
// c. Else,
// i. Let r1 be months + increment × sign.
r1 = additional_shift ? months + signed_increment : months;
// c. Let r2 be months + increment × sign. // d. Let r2 be r1 + increment × sign.
r2 = months + static_cast<double>(increment) * sign; r2 = r1 + signed_increment;
// d. Let startDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, 0, r1). // e. Let startDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, 0, r1).
start_duration = TRY(adjust_date_duration_record(vm, duration.date, 0, 0, r1)); start_duration = TRY(adjust_date_duration_record(vm, duration.date, 0, 0, r1));
// e. Let endDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, 0, r2). // f. Let endDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, 0, r2).
end_duration = TRY(adjust_date_duration_record(vm, duration.date, 0, 0, r2)); end_duration = TRY(adjust_date_duration_record(vm, duration.date, 0, 0, r2));
} }
// 3. Else if unit is WEEK, then // 3. Else if unit is WEEK, then
@ -911,7 +919,7 @@ ThrowCompletionOr<CalendarNudgeResult> nudge_to_calendar_unit(VM& vm, i8 sign, I
r1 = weeks; r1 = weeks;
// g. Let r2 be weeks + increment × sign. // g. Let r2 be weeks + increment × sign.
r2 = weeks + static_cast<double>(increment) * sign; r2 = weeks + signed_increment;
// h. Let startDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, r1). // h. Let startDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, r1).
start_duration = TRY(adjust_date_duration_record(vm, duration.date, 0, r1)); start_duration = TRY(adjust_date_duration_record(vm, duration.date, 0, r1));
@ -931,7 +939,7 @@ ThrowCompletionOr<CalendarNudgeResult> nudge_to_calendar_unit(VM& vm, i8 sign, I
r1 = days; r1 = days;
// d. Let r2 be days + increment × sign. // d. Let r2 be days + increment × sign.
r2 = days + static_cast<double>(increment) * sign; r2 = days + signed_increment;
// e. Let startDuration be ? AdjustDateDurationRecord(duration.[[Date]], r1). // e. Let startDuration be ? AdjustDateDurationRecord(duration.[[Date]], r1).
start_duration = TRY(adjust_date_duration_record(vm, duration.date, r1)); start_duration = TRY(adjust_date_duration_record(vm, duration.date, r1));
@ -992,28 +1000,87 @@ ThrowCompletionOr<CalendarNudgeResult> nudge_to_calendar_unit(VM& vm, i8 sign, I
end_epoch_ns = TRY(get_epoch_nanoseconds_for(vm, *time_zone, end_date_time, Disambiguation::Compatible)); end_epoch_ns = TRY(get_epoch_nanoseconds_for(vm, *time_zone, end_date_time, Disambiguation::Compatible));
} }
// 13. If sign is 1, then // 13. Return the Record { [[R1]]: r1, [[R2]]: r2, [[StartEpochNs]]: startEpochNs, [[EndEpochNs]]: endEpochNs, [[StartDuration]]: startDuration, [[EndDuration]]: endDuration }.
return NudgeWindow { r1, r2, move(start_epoch_ns), move(end_epoch_ns), start_duration, end_duration };
}
// 7.5.34 NudgeToCalendarUnit ( sign, duration, originEpochNs, destEpochNs, isoDateTime, timeZone, calendar, increment, unit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-nudgetocalendarunit
ThrowCompletionOr<CalendarNudgeResult> nudge_to_calendar_unit(VM& vm, i8 sign, InternalDuration const& duration, Crypto::SignedBigInteger const& origin_epoch_ns, Crypto::SignedBigInteger const& dest_epoch_ns, ISODateTime const& iso_date_time, Optional<StringView> time_zone, StringView calendar, u64 increment, Unit unit, RoundingMode rounding_mode)
{
// 1. Let didExpandCalendarUnit be false.
auto did_expand_calendar_unit = false;
// 2. Let nudgeWindow be ? ComputeNudgeWindow(sign, duration, originEpochNs, isoDateTime, timeZone, calendar, increment, unit, false).
auto nudge_window = TRY(compute_nudge_window(vm, sign, duration, origin_epoch_ns, iso_date_time, time_zone, calendar, increment, unit, false));
// 3. Let startEpochNs be nudgeWindow.[[StartEpochNs]].
auto start_epoch_ns = move(nudge_window.start_epoch_ns);
// 4. Let endEpochNs be nudgeWindow.[[EndEpochNs]].
auto end_epoch_ns = move(nudge_window.end_epoch_ns);
auto recompute_nudge_window = [&]() -> ThrowCompletionOr<void> {
nudge_window = TRY(compute_nudge_window(vm, sign, duration, origin_epoch_ns, iso_date_time, time_zone, calendar, increment, unit, true));
start_epoch_ns = move(nudge_window.start_epoch_ns);
end_epoch_ns = move(nudge_window.end_epoch_ns);
return {};
};
// 5. If sign is 1, then
if (sign == 1) { if (sign == 1) {
// a. Assert: startEpochNs ≤ destEpochNs ≤ endEpochNs. // a. If startEpochNs ≤ destEpochNs ≤ endEpochNs is false, then
VERIFY(start_epoch_ns <= dest_epoch_ns); if (start_epoch_ns > dest_epoch_ns || dest_epoch_ns > end_epoch_ns) {
VERIFY(dest_epoch_ns <= end_epoch_ns); // i. Set nudgeWindow to ? ComputeNudgeWindow(sign, duration, originEpochNs, isoDateTime, timeZone, calendar, increment, unit, true).
TRY(recompute_nudge_window());
// ii. Assert: nudgeWindow.[[StartEpochNs]] ≤ destEpochNs ≤ nudgeWindow.[[EndEpochNs]].
VERIFY(start_epoch_ns <= dest_epoch_ns);
VERIFY(dest_epoch_ns <= end_epoch_ns);
// iii. Set didExpandCalendarUnit to true.
did_expand_calendar_unit = true;
}
} }
// 14. Else, // 6. Else,
else { else {
// a. Assert: endEpochNs ≤ destEpochNs ≤ startEpochNs. // a. If endEpochNs ≤ destEpochNs ≤ startEpochNs is false, then
VERIFY(end_epoch_ns <= dest_epoch_ns); if (end_epoch_ns > dest_epoch_ns || dest_epoch_ns > start_epoch_ns) {
VERIFY(dest_epoch_ns <= start_epoch_ns); // i. Set nudgeWindow to ? ComputeNudgeWindow(sign, duration, originEpochNs, isoDateTime, timeZone, calendar, increment, unit, true).
TRY(recompute_nudge_window());
// ii. Assert: nudgeWindow.[[EndEpochNs]] ≤ destEpochNs ≤ nudgeWindow.[[StartEpochNs]].
VERIFY(end_epoch_ns <= dest_epoch_ns);
VERIFY(dest_epoch_ns <= start_epoch_ns);
// iii. Set didExpandCalendarUnit to true.
did_expand_calendar_unit = true;
}
} }
// 15. Assert: startEpochNs ≠ endEpochNs. // 7. Let r1 be nudgeWindow.[[R1]].
auto r1 = nudge_window.r1;
// 8. Let r2 be nudgeWindow.[[R2]].
auto r2 = nudge_window.r2;
// 9. Set startEpochNs to nudgeWindow.[[StartEpochNs]].
// 10. Set endEpochNs to nudgeWindow.[[StartEpochNs]].
// 11. Let startDuration be nudgeWindow.[[StartDuration]].
auto start_duration = nudge_window.start_duration;
// 12. Let endDuration be nudgeWindow.[[EndDuration]].
auto end_duration = nudge_window.end_duration;
// 13. Assert: startEpochNs ≠ endEpochNs.
VERIFY(start_epoch_ns != end_epoch_ns); VERIFY(start_epoch_ns != end_epoch_ns);
// 16. Let progress be (destEpochNs - startEpochNs) / (endEpochNs - startEpochNs). // 14. Let progress be (destEpochNs - startEpochNs) / (endEpochNs - startEpochNs).
auto progress_numerator = dest_epoch_ns.minus(start_epoch_ns); auto progress_numerator = dest_epoch_ns.minus(start_epoch_ns);
auto progress_denominator = end_epoch_ns.minus(start_epoch_ns); auto progress_denominator = end_epoch_ns.minus(start_epoch_ns);
auto progress_equals_one = progress_numerator == progress_denominator; auto progress_equals_one = progress_numerator == progress_denominator;
// 17. Let total be r1 + progress × increment × sign. // 15. Let total be r1 + progress × increment × sign.
auto total_numerator = progress_numerator.multiplied_by(Crypto::UnsignedBigInteger { increment }); auto total_numerator = progress_numerator.multiplied_by(Crypto::UnsignedBigInteger { increment });
if (sign == -1) if (sign == -1)
@ -1024,26 +1091,26 @@ ThrowCompletionOr<CalendarNudgeResult> nudge_to_calendar_unit(VM& vm, i8 sign, I
auto total_mv = Crypto::BigFraction { Crypto::SignedBigInteger { r1 } } + Crypto::BigFraction { move(total_numerator), progress_denominator.unsigned_value() }; auto total_mv = Crypto::BigFraction { Crypto::SignedBigInteger { r1 } } + Crypto::BigFraction { move(total_numerator), progress_denominator.unsigned_value() };
auto total = total_mv.to_double(); auto total = total_mv.to_double();
// 18. NOTE: The above two steps cannot be implemented directly using floating-point arithmetic. This division can be // 16. NOTE: The above two steps cannot be implemented directly using floating-point arithmetic. This division can be
// implemented as if expressing the denominator and numerator of total as two time durations, and performing one // implemented as if expressing the denominator and numerator of total as two time durations, and performing one
// division operation with a floating-point result. // division operation with a floating-point result.
// 19. Assert: 0 ≤ progress ≤ 1. // 17. Assert: 0 ≤ progress ≤ 1.
// 20. If sign < 0, let isNegative be NEGATIVE; else let isNegative be POSITIVE. // 18. If sign < 0, let isNegative be NEGATIVE; else let isNegative be POSITIVE.
auto is_negative = sign < 0 ? Sign::Negative : Sign::Positive; auto is_negative = sign < 0 ? Sign::Negative : Sign::Positive;
// 21. Let unsignedRoundingMode be GetUnsignedRoundingMode(roundingMode, isNegative). // 19. Let unsignedRoundingMode be GetUnsignedRoundingMode(roundingMode, isNegative).
auto unsigned_rounding_mode = get_unsigned_rounding_mode(rounding_mode, is_negative); auto unsigned_rounding_mode = get_unsigned_rounding_mode(rounding_mode, is_negative);
double rounded_unit = 0; double rounded_unit = 0;
// 22. If progress = 1, then // 20. If progress = 1, then
if (progress_equals_one) { if (progress_equals_one) {
// a. Let roundedUnit be abs(r2). // a. Let roundedUnit be abs(r2).
rounded_unit = fabs(r2); rounded_unit = fabs(r2);
} }
// 23. Else, // 21. Else,
else { else {
// a. Assert: abs(r1) ≤ abs(total) < abs(r2). // a. Assert: abs(r1) ≤ abs(total) < abs(r2).
VERIFY(fabs(r1) <= fabs(total)); VERIFY(fabs(r1) <= fabs(total));
@ -1053,13 +1120,12 @@ ThrowCompletionOr<CalendarNudgeResult> nudge_to_calendar_unit(VM& vm, i8 sign, I
rounded_unit = apply_unsigned_rounding_mode(fabs(total), fabs(r1), fabs(r2), unsigned_rounding_mode); rounded_unit = apply_unsigned_rounding_mode(fabs(total), fabs(r1), fabs(r2), unsigned_rounding_mode);
} }
auto did_expand_calendar_unit = false;
DateDuration result_duration; DateDuration result_duration;
Crypto::SignedBigInteger nudged_epoch_ns; Crypto::SignedBigInteger nudged_epoch_ns;
// 24. If roundedUnit is abs(r2), then // 22. If roundedUnit is abs(r2), then
if (rounded_unit == fabs(r2)) { if (rounded_unit == fabs(r2)) {
// a. Let didExpandCalendarUnit be true. // a. Set didExpandCalendarUnit to true.
did_expand_calendar_unit = true; did_expand_calendar_unit = true;
// b. Let resultDuration be endDuration. // b. Let resultDuration be endDuration.
@ -1068,29 +1134,26 @@ ThrowCompletionOr<CalendarNudgeResult> nudge_to_calendar_unit(VM& vm, i8 sign, I
// c. Let nudgedEpochNs be endEpochNs. // c. Let nudgedEpochNs be endEpochNs.
nudged_epoch_ns = move(end_epoch_ns); nudged_epoch_ns = move(end_epoch_ns);
} }
// 25. Else, // 23. Else,
else { else {
// a. Let didExpandCalendarUnit be false. // a. Let resultDuration be startDuration.
did_expand_calendar_unit = false;
// b. Let resultDuration be startDuration.
result_duration = start_duration; result_duration = start_duration;
// c. Let nudgedEpochNs be startEpochNs. // b. Let nudgedEpochNs be startEpochNs.
nudged_epoch_ns = move(start_epoch_ns); nudged_epoch_ns = move(start_epoch_ns);
} }
// 26. Set resultDuration to CombineDateAndTimeDuration(resultDuration, 0). // 24. Set resultDuration to CombineDateAndTimeDuration(resultDuration, 0).
auto result_date_and_time_duration = combine_date_and_time_duration(result_duration, TimeDuration { 0 }); auto result_date_and_time_duration = combine_date_and_time_duration(result_duration, TimeDuration { 0 });
// 27. Let nudgeResult be Duration Nudge Result Record { [[Duration]]: resultDuration, [[NudgedEpochNs]]: nudgedEpochNs, [[DidExpandCalendarUnit]]: didExpandCalendarUnit }. // 25. Let nudgeResult be Duration Nudge Result Record { [[Duration]]: resultDuration, [[NudgedEpochNs]]: nudgedEpochNs, [[DidExpandCalendarUnit]]: didExpandCalendarUnit }.
DurationNudgeResult nudge_result { .duration = move(result_date_and_time_duration), .nudged_epoch_ns = move(nudged_epoch_ns), .did_expand_calendar_unit = did_expand_calendar_unit }; DurationNudgeResult nudge_result { .duration = move(result_date_and_time_duration), .nudged_epoch_ns = move(nudged_epoch_ns), .did_expand_calendar_unit = did_expand_calendar_unit };
// 28. Return the Record { [[NudgeResult]]: nudgeResult, [[Total]]: total }. // 26. Return the Record { [[NudgeResult]]: nudgeResult, [[Total]]: total }.
return CalendarNudgeResult { .nudge_result = move(nudge_result), .total = move(total_mv) }; return CalendarNudgeResult { .nudge_result = move(nudge_result), .total = move(total_mv) };
} }
// 7.5.34 NudgeToZonedTime ( sign, duration, isoDateTime, timeZone, calendar, increment, unit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-nudgetozonedtime // 7.5.35 NudgeToZonedTime ( sign, duration, isoDateTime, timeZone, calendar, increment, unit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-nudgetozonedtime
ThrowCompletionOr<DurationNudgeResult> nudge_to_zoned_time(VM& vm, i8 sign, InternalDuration const& duration, ISODateTime const& iso_date_time, StringView time_zone, StringView calendar, u64 increment, Unit unit, RoundingMode rounding_mode) ThrowCompletionOr<DurationNudgeResult> nudge_to_zoned_time(VM& vm, i8 sign, InternalDuration const& duration, ISODateTime const& iso_date_time, StringView time_zone, StringView calendar, u64 increment, Unit unit, RoundingMode rounding_mode)
{ {
// 1. Let start be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], duration.[[Date]], CONSTRAIN). // 1. Let start be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], duration.[[Date]], CONSTRAIN).
@ -1168,7 +1231,7 @@ ThrowCompletionOr<DurationNudgeResult> nudge_to_zoned_time(VM& vm, i8 sign, Inte
return DurationNudgeResult { .duration = move(result_duration), .nudged_epoch_ns = move(nudged_epoch_ns), .did_expand_calendar_unit = did_round_beyond_day }; return DurationNudgeResult { .duration = move(result_duration), .nudged_epoch_ns = move(nudged_epoch_ns), .did_expand_calendar_unit = did_round_beyond_day };
} }
// 7.5.35 NudgeToDayOrTime ( duration, destEpochNs, largestUnit, increment, smallestUnit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-nudgetodayortime // 7.5.36 NudgeToDayOrTime ( duration, destEpochNs, largestUnit, increment, smallestUnit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-nudgetodayortime
ThrowCompletionOr<DurationNudgeResult> nudge_to_day_or_time(VM& vm, InternalDuration const& duration, Crypto::SignedBigInteger const& dest_epoch_ns, Unit largest_unit, u64 increment, Unit smallest_unit, RoundingMode rounding_mode) ThrowCompletionOr<DurationNudgeResult> nudge_to_day_or_time(VM& vm, InternalDuration const& duration, Crypto::SignedBigInteger const& dest_epoch_ns, Unit largest_unit, u64 increment, Unit smallest_unit, RoundingMode rounding_mode)
{ {
// 1. Let timeDuration be ! Add24HourDaysToTimeDuration(duration.[[Time]], duration.[[Date]].[[Days]]). // 1. Let timeDuration be ! Add24HourDaysToTimeDuration(duration.[[Time]], duration.[[Date]].[[Days]]).
@ -1231,7 +1294,7 @@ ThrowCompletionOr<DurationNudgeResult> nudge_to_day_or_time(VM& vm, InternalDura
return DurationNudgeResult { .duration = move(result_duration), .nudged_epoch_ns = move(nudged_epoch_ns), .did_expand_calendar_unit = did_expand_days }; return DurationNudgeResult { .duration = move(result_duration), .nudged_epoch_ns = move(nudged_epoch_ns), .did_expand_calendar_unit = did_expand_days };
} }
// 7.5.36 BubbleRelativeDuration ( sign, duration, nudgedEpochNs, isoDateTime, timeZone, calendar, largestUnit, smallestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-bubblerelativeduration // 7.5.37 BubbleRelativeDuration ( sign, duration, nudgedEpochNs, isoDateTime, timeZone, calendar, largestUnit, smallestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-bubblerelativeduration
ThrowCompletionOr<InternalDuration> bubble_relative_duration(VM& vm, i8 sign, InternalDuration duration, Crypto::SignedBigInteger const& nudged_epoch_ns, ISODateTime const& iso_date_time, Optional<StringView> time_zone, StringView calendar, Unit largest_unit, Unit smallest_unit) ThrowCompletionOr<InternalDuration> bubble_relative_duration(VM& vm, i8 sign, InternalDuration duration, Crypto::SignedBigInteger const& nudged_epoch_ns, ISODateTime const& iso_date_time, Optional<StringView> time_zone, StringView calendar, Unit largest_unit, Unit smallest_unit)
{ {
// 1. If smallestUnit is largestUnit, return duration. // 1. If smallestUnit is largestUnit, return duration.
@ -1332,7 +1395,7 @@ ThrowCompletionOr<InternalDuration> bubble_relative_duration(VM& vm, i8 sign, In
return duration; return duration;
} }
// 7.5.37 RoundRelativeDuration ( duration, originEpochNs, destEpochNs, isoDateTime, timeZone, calendar, largestUnit, increment, smallestUnit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundrelativeduration // 7.5.38 RoundRelativeDuration ( duration, originEpochNs, destEpochNs, isoDateTime, timeZone, calendar, largestUnit, increment, smallestUnit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundrelativeduration
ThrowCompletionOr<InternalDuration> round_relative_duration(VM& vm, InternalDuration duration, Crypto::SignedBigInteger const& origin_epoch_ns, Crypto::SignedBigInteger const& dest_epoch_ns, ISODateTime const& iso_date_time, Optional<StringView> time_zone, StringView calendar, Unit largest_unit, u64 increment, Unit smallest_unit, RoundingMode rounding_mode) ThrowCompletionOr<InternalDuration> round_relative_duration(VM& vm, InternalDuration duration, Crypto::SignedBigInteger const& origin_epoch_ns, Crypto::SignedBigInteger const& dest_epoch_ns, ISODateTime const& iso_date_time, Optional<StringView> time_zone, StringView calendar, Unit largest_unit, u64 increment, Unit smallest_unit, RoundingMode rounding_mode)
{ {
// 1. Let irregularLengthUnit be false. // 1. Let irregularLengthUnit be false.
@ -1386,7 +1449,7 @@ ThrowCompletionOr<InternalDuration> round_relative_duration(VM& vm, InternalDura
return duration; return duration;
} }
// 7.5.38 TotalRelativeDuration ( duration, originEpochNs, destEpochNs, isoDateTime, timeZone, calendar, unit ), https://tc39.es/proposal-temporal/#sec-temporal-totalrelativeduration // 7.5.39 TotalRelativeDuration ( duration, originEpochNs, destEpochNs, isoDateTime, timeZone, calendar, unit ), https://tc39.es/proposal-temporal/#sec-temporal-totalrelativeduration
ThrowCompletionOr<Crypto::BigFraction> total_relative_duration(VM& vm, InternalDuration const& duration, Crypto::SignedBigInteger const& origin_epoch_ns, Crypto::SignedBigInteger const& dest_epoch_ns, ISODateTime const& iso_date_time, Optional<StringView> time_zone, StringView calendar, Unit unit) ThrowCompletionOr<Crypto::BigFraction> total_relative_duration(VM& vm, InternalDuration const& duration, Crypto::SignedBigInteger const& origin_epoch_ns, Crypto::SignedBigInteger const& dest_epoch_ns, ISODateTime const& iso_date_time, Optional<StringView> time_zone, StringView calendar, Unit unit)
{ {
// 1. If IsCalendarUnit(unit) is true, or timeZone is not UNSET and unit is DAY, then // 1. If IsCalendarUnit(unit) is true, or timeZone is not UNSET and unit is DAY, then
@ -1408,7 +1471,7 @@ ThrowCompletionOr<Crypto::BigFraction> total_relative_duration(VM& vm, InternalD
return total_time_duration(time_duration, unit); return total_time_duration(time_duration, unit);
} }
// 7.5.39 TemporalDurationToString ( duration, precision ), https://tc39.es/proposal-temporal/#sec-temporal-temporaldurationtostring // 7.5.40 TemporalDurationToString ( duration, precision ), https://tc39.es/proposal-temporal/#sec-temporal-temporaldurationtostring
String temporal_duration_to_string(Duration const& duration, Precision precision) String temporal_duration_to_string(Duration const& duration, Precision precision)
{ {
// 1. Let sign be DurationSign(duration). // 1. Let sign be DurationSign(duration).
@ -1500,7 +1563,7 @@ String temporal_duration_to_string(Duration const& duration, Precision precision
return MUST(result.to_string()); return MUST(result.to_string());
} }
// 7.5.40 AddDurations ( operation, duration, other ), https://tc39.es/proposal-temporal/#sec-temporal-adddurations // 7.5.41 AddDurations ( operation, duration, other ), https://tc39.es/proposal-temporal/#sec-temporal-adddurations
ThrowCompletionOr<GC::Ref<Duration>> add_durations(VM& vm, ArithmeticOperation operation, Duration const& duration, Value other_value) ThrowCompletionOr<GC::Ref<Duration>> add_durations(VM& vm, ArithmeticOperation operation, Duration const& duration, Value other_value)
{ {
// 1. Set other to ? ToTemporalDuration(other). // 1. Set other to ? ToTemporalDuration(other).

View file

@ -104,6 +104,15 @@ struct DurationNudgeResult {
bool did_expand_calendar_unit { false }; bool did_expand_calendar_unit { false };
}; };
struct NudgeWindow {
double r1 { 0 };
double r2 { 0 };
Crypto::SignedBigInteger start_epoch_ns;
Crypto::SignedBigInteger end_epoch_ns;
DateDuration start_duration;
DateDuration end_duration;
};
struct CalendarNudgeResult { struct CalendarNudgeResult {
DurationNudgeResult nudge_result; DurationNudgeResult nudge_result;
Crypto::BigFraction total; Crypto::BigFraction total;
@ -137,6 +146,7 @@ i8 time_duration_sign(TimeDuration const&);
ThrowCompletionOr<double> date_duration_days(VM&, DateDuration const&, PlainDate const&); ThrowCompletionOr<double> date_duration_days(VM&, DateDuration const&, PlainDate const&);
ThrowCompletionOr<TimeDuration> round_time_duration(VM&, TimeDuration const&, Crypto::UnsignedBigInteger const& increment, Unit, RoundingMode); ThrowCompletionOr<TimeDuration> round_time_duration(VM&, TimeDuration const&, Crypto::UnsignedBigInteger const& increment, Unit, RoundingMode);
Crypto::BigFraction total_time_duration(TimeDuration const&, Unit); Crypto::BigFraction total_time_duration(TimeDuration const&, Unit);
ThrowCompletionOr<NudgeWindow> compute_nudge_window(VM&, i8 sign, InternalDuration const&, Crypto::SignedBigInteger const& origin_epoch_ns, ISODateTime const& iso_date_time, Optional<StringView> time_zone, StringView calendar, u64 increment, Unit, bool additional_shift);
ThrowCompletionOr<CalendarNudgeResult> nudge_to_calendar_unit(VM&, i8 sign, InternalDuration const&, Crypto::SignedBigInteger const& origin_epoch_ns, Crypto::SignedBigInteger const& dest_epoch_ns, ISODateTime const&, Optional<StringView> time_zone, StringView calendar, u64 increment, Unit, RoundingMode); ThrowCompletionOr<CalendarNudgeResult> nudge_to_calendar_unit(VM&, i8 sign, InternalDuration const&, Crypto::SignedBigInteger const& origin_epoch_ns, Crypto::SignedBigInteger const& dest_epoch_ns, ISODateTime const&, Optional<StringView> time_zone, StringView calendar, u64 increment, Unit, RoundingMode);
ThrowCompletionOr<DurationNudgeResult> nudge_to_zoned_time(VM&, i8 sign, InternalDuration const&, ISODateTime const&, StringView time_zone, StringView calendar, u64 increment, Unit, RoundingMode); ThrowCompletionOr<DurationNudgeResult> nudge_to_zoned_time(VM&, i8 sign, InternalDuration const&, ISODateTime const&, StringView time_zone, StringView calendar, u64 increment, Unit, RoundingMode);
ThrowCompletionOr<DurationNudgeResult> nudge_to_day_or_time(VM&, InternalDuration const&, Crypto::SignedBigInteger const& dest_epoch_ns, Unit largest_unit, u64 increment, Unit smallest_unit, RoundingMode); ThrowCompletionOr<DurationNudgeResult> nudge_to_day_or_time(VM&, InternalDuration const&, Crypto::SignedBigInteger const& dest_epoch_ns, Unit largest_unit, u64 increment, Unit smallest_unit, RoundingMode);

View file

@ -120,6 +120,14 @@ describe("correct behavior", () => {
expect(result.toString()).toBe("PT0S"); expect(result.toString()).toBe("PT0S");
}); });
test("leap day rounding", () => {
const duration = new Temporal.Duration(1, 0, 0, 0, 1);
const relativeTo = new Temporal.PlainDate(2020, 2, 29);
const result = duration.round({ smallestUnit: "years", relativeTo });
expect(result.toString()).toBe("P1Y");
});
}); });
describe("errors", () => { describe("errors", () => {