LibJS: Handle relativeTo ZDTs that fall within second DST wallclock time

This is a normative change in the Temporal proposal. See:
1a089eb
This commit is contained in:
Timothy Flynn 2025-09-25 16:53:47 -04:00 committed by Tim Flynn
parent 1ecb78897f
commit cb6ca85564
Notes: github-actions[bot] 2025-09-28 15:34:41 +00:00
9 changed files with 116 additions and 60 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 } };
}
// 7.5.33 NudgeToCalendarUnit ( sign, duration, 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& dest_epoch_ns, ISODateTime const& iso_date_time, Optional<StringView> time_zone, StringView calendar, u64 increment, Unit unit, RoundingMode rounding_mode)
// 7.5.33 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)
{
DateDuration start_duration;
DateDuration end_duration;
@ -947,35 +947,48 @@ ThrowCompletionOr<CalendarNudgeResult> nudge_to_calendar_unit(VM& vm, i8 sign, I
else if (sign == -1)
VERIFY(r1 <= 0 && r1 > r2);
// 7. Let start be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], startDuration, CONSTRAIN).
auto start = TRY(calendar_date_add(vm, calendar, iso_date_time.iso_date, start_duration, Overflow::Constrain));
Crypto::SignedBigInteger start_epoch_ns;
Crypto::SignedBigInteger end_epoch_ns;
// 8. Let end be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], endDuration, CONSTRAIN).
// 7. If r1 = 0, then
if (r1 == 0) {
// a. Let startEpochNs be originEpochNs.
start_epoch_ns = origin_epoch_ns;
}
// 8. Else,
else {
// a. Let start be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], startDuration, CONSTRAIN).
auto start = TRY(calendar_date_add(vm, calendar, iso_date_time.iso_date, start_duration, Overflow::Constrain));
// b. Let startDateTime be CombineISODateAndTimeRecord(start, isoDateTime.[[Time]]).
auto start_date_time = combine_iso_date_and_time_record(start, iso_date_time.time);
// c. If timeZone is UNSET, then
if (!time_zone.has_value()) {
// i. Let startEpochNs be GetUTCEpochNanoseconds(startDateTime).
start_epoch_ns = get_utc_epoch_nanoseconds(start_date_time);
}
// d. Else,
else {
// i. Let startEpochNs be ? GetEpochNanosecondsFor(timeZone, startDateTime, COMPATIBLE).
start_epoch_ns = TRY(get_epoch_nanoseconds_for(vm, *time_zone, start_date_time, Disambiguation::Compatible));
}
}
// 9. Let end be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], endDuration, CONSTRAIN).
auto end = TRY(calendar_date_add(vm, calendar, iso_date_time.iso_date, end_duration, Overflow::Constrain));
// 9. Let startDateTime be CombineISODateAndTimeRecord(start, isoDateTime.[[Time]]).
auto start_date_time = combine_iso_date_and_time_record(start, iso_date_time.time);
// 10. Let endDateTime be CombineISODateAndTimeRecord(end, isoDateTime.[[Time]]).
auto end_date_time = combine_iso_date_and_time_record(end, iso_date_time.time);
Crypto::SignedBigInteger start_epoch_ns;
Crypto::SignedBigInteger end_epoch_ns;
// 11. If timeZone is UNSET, then
if (!time_zone.has_value()) {
// a. Let startEpochNs be GetUTCEpochNanoseconds(startDateTime).
start_epoch_ns = get_utc_epoch_nanoseconds(start_date_time);
// b. Let endEpochNs be GetUTCEpochNanoseconds(endDateTime).
// a. Let endEpochNs be GetUTCEpochNanoseconds(endDateTime).
end_epoch_ns = get_utc_epoch_nanoseconds(end_date_time);
}
// 12. Else,
else {
// a. Let startEpochNs be ? GetEpochNanosecondsFor(timeZone, startDateTime, COMPATIBLE).
start_epoch_ns = TRY(get_epoch_nanoseconds_for(vm, *time_zone, start_date_time, Disambiguation::Compatible));
// b. Let endEpochNs be ? GetEpochNanosecondsFor(timeZone, endDateTime, COMPATIBLE).
// a. Let endEpochNs be ? GetEpochNanosecondsFor(timeZone, endDateTime, COMPATIBLE).
end_epoch_ns = TRY(get_epoch_nanoseconds_for(vm, *time_zone, end_date_time, Disambiguation::Compatible));
}
@ -1319,8 +1332,8 @@ ThrowCompletionOr<InternalDuration> bubble_relative_duration(VM& vm, i8 sign, In
return duration;
}
// 7.5.37 RoundRelativeDuration ( duration, 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& 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)
// 7.5.37 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)
{
// 1. Let irregularLengthUnit be false.
auto irregular_length_unit = false;
@ -1340,8 +1353,8 @@ ThrowCompletionOr<InternalDuration> round_relative_duration(VM& vm, InternalDura
// 5. If irregularLengthUnit is true, then
if (irregular_length_unit) {
// a. Let record be ? NudgeToCalendarUnit(sign, duration, destEpochNs, isoDateTime, timeZone, calendar, increment, smallestUnit, roundingMode).
auto record = TRY(nudge_to_calendar_unit(vm, sign, duration, dest_epoch_ns, iso_date_time, time_zone, calendar, increment, smallest_unit, rounding_mode));
// a. Let record be ? NudgeToCalendarUnit(sign, duration, originEpochNs, destEpochNs, isoDateTime, timeZone, calendar, increment, smallestUnit, roundingMode).
auto record = TRY(nudge_to_calendar_unit(vm, sign, duration, origin_epoch_ns, dest_epoch_ns, iso_date_time, time_zone, calendar, increment, smallest_unit, rounding_mode));
// b. Let nudgeResult be record.[[NudgeResult]].
nudge_result = move(record.nudge_result);
@ -1373,16 +1386,16 @@ ThrowCompletionOr<InternalDuration> round_relative_duration(VM& vm, InternalDura
return duration;
}
// 7.5.38 TotalRelativeDuration ( duration, 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& dest_epoch_ns, ISODateTime const& iso_date_time, Optional<StringView> time_zone, StringView calendar, Unit unit)
// 7.5.38 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)
{
// 1. If IsCalendarUnit(unit) is true, or timeZone is not UNSET and unit is DAY, then
if (is_calendar_unit(unit) || (time_zone.has_value() && unit == Unit::Day)) {
// a. If InternalDurationSign(duration) < 0, let sign be -1; else let sign be 1.
auto sign = internal_duration_sign(duration) < 0 ? -1 : 1;
// b. Let record be ? NudgeToCalendarUnit(sign, duration, destEpochNs, isoDateTime, timeZone, calendar, 1, unit, TRUNC).
auto record = TRY(nudge_to_calendar_unit(vm, sign, duration, dest_epoch_ns, iso_date_time, time_zone, calendar, 1, unit, RoundingMode::Trunc));
// b. Let record be ? NudgeToCalendarUnit(sign, duration, originEpochNs, destEpochNs, isoDateTime, timeZone, calendar, 1, unit, TRUNC).
auto record = TRY(nudge_to_calendar_unit(vm, sign, duration, origin_epoch_ns, dest_epoch_ns, iso_date_time, time_zone, calendar, 1, unit, RoundingMode::Trunc));
// c. Return record.[[Total]].
return record.total;

View file

@ -137,12 +137,12 @@ i8 time_duration_sign(TimeDuration 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);
Crypto::BigFraction total_time_duration(TimeDuration const&, Unit);
ThrowCompletionOr<CalendarNudgeResult> nudge_to_calendar_unit(VM&, i8 sign, InternalDuration const&, 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_day_or_time(VM&, InternalDuration const&, Crypto::SignedBigInteger const& dest_epoch_ns, Unit largest_unit, u64 increment, Unit smallest_unit, RoundingMode);
ThrowCompletionOr<InternalDuration> bubble_relative_duration(VM&, i8 sign, InternalDuration, Crypto::SignedBigInteger const& nudged_epoch_ns, ISODateTime const&, Optional<StringView> time_zone, StringView calendar, Unit largest_unit, Unit smallest_unit);
ThrowCompletionOr<InternalDuration> round_relative_duration(VM&, InternalDuration, Crypto::SignedBigInteger const& dest_epoch_ns, ISODateTime const&, Optional<StringView> time_zone, StringView calendar, Unit largest_unit, u64 increment, Unit smallest_unit, RoundingMode);
ThrowCompletionOr<Crypto::BigFraction> total_relative_duration(VM&, InternalDuration const&, TimeDuration const&, ISODateTime const&, Optional<StringView> time_zone, StringView calendar, Unit);
ThrowCompletionOr<InternalDuration> round_relative_duration(VM&, InternalDuration, Crypto::SignedBigInteger const& origin_epoch_ns, Crypto::SignedBigInteger const& dest_epoch_ns, ISODateTime const&, Optional<StringView> time_zone, StringView calendar, Unit largest_unit, u64 increment, Unit smallest_unit, RoundingMode);
ThrowCompletionOr<Crypto::BigFraction> total_relative_duration(VM&, InternalDuration const&, Crypto::SignedBigInteger const& origin_epoch_ns, Crypto::SignedBigInteger const& dest_epoch_ns, ISODateTime const&, Optional<StringView> time_zone, StringView calendar, Unit);
String temporal_duration_to_string(Duration const&, Precision);
ThrowCompletionOr<GC::Ref<Duration>> add_durations(VM&, ArithmeticOperation, Duration const&, Value);

View file

@ -369,7 +369,7 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::round)
// f. Set internalDuration to ? DifferenceZonedDateTimeWithRounding(relativeEpochNs, targetEpochNs, timeZone, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode).
internal_duration = TRY(difference_zoned_date_time_with_rounding(vm, relative_epoch_nanoseconds, target_epoch_nanoseconds, time_zone, calendar, largest_unit_value, rounding_increment, smallest_unit_value, rounding_mode));
// g. If TemporalUnitCategory(largestUnit) is date, set largestUnit to hour.
// g. If TemporalUnitCategory(largestUnit) is DATE, set largestUnit to HOUR.
if (temporal_unit_category(largest_unit_value) == UnitCategory::Date)
largest_unit_value = Unit::Hour;

View file

@ -413,14 +413,17 @@ ThrowCompletionOr<GC::Ref<Duration>> difference_temporal_plain_date(VM& vm, Dura
// a. Let isoDateTime be CombineISODateAndTimeRecord(temporalDate.[[ISODate]], MidnightTimeRecord()).
auto iso_date_time = combine_iso_date_and_time_record(temporal_date.iso_date(), midnight_time_record());
// b. Let isoDateTimeOther be CombineISODateAndTimeRecord(other.[[ISODate]], MidnightTimeRecord()).
// b. Let originEpochNs be GetUTCEpochNanoseconds(isoDateTime).
auto origin_epoch_ns = get_utc_epoch_nanoseconds(iso_date_time);
// c. Let isoDateTimeOther be CombineISODateAndTimeRecord(other.[[ISODate]], MidnightTimeRecord()).
auto iso_date_time_other = combine_iso_date_and_time_record(other->iso_date(), midnight_time_record());
// c. Let destEpochNs be GetUTCEpochNanoseconds(isoDateTimeOther).
// d. Let destEpochNs be GetUTCEpochNanoseconds(isoDateTimeOther).
auto dest_epoch_ns = get_utc_epoch_nanoseconds(iso_date_time_other);
// d. Set duration to ? RoundRelativeDuration(duration, destEpochNs, isoDateTime, UNSET, temporalDate.[[Calendar]], settings.[[LargestUnit]], settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]).
duration = TRY(round_relative_duration(vm, move(duration), dest_epoch_ns, iso_date_time, {}, temporal_date.calendar(), settings.largest_unit, settings.rounding_increment, settings.smallest_unit, settings.rounding_mode));
// e. Set duration to ? RoundRelativeDuration(duration, originEpochNs, destEpochNs, isoDateTime, UNSET, temporalDate.[[Calendar]], settings.[[LargestUnit]], settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]).
duration = TRY(round_relative_duration(vm, move(duration), origin_epoch_ns, dest_epoch_ns, iso_date_time, {}, temporal_date.calendar(), settings.largest_unit, settings.rounding_increment, settings.smallest_unit, settings.rounding_mode));
}
// 9. Let result be ! TemporalDurationFromInternal(duration, DAY).

View file

@ -369,11 +369,14 @@ ThrowCompletionOr<InternalDuration> difference_plain_date_time_with_rounding(VM&
if (smallest_unit == Unit::Nanosecond && rounding_increment == 1)
return diff;
// 5. Let destEpochNs be GetUTCEpochNanoseconds(isoDateTime2).
// 5. Let originEpochNs be GetUTCEpochNanoseconds(isoDateTime1).
auto origin_epoch_ns = get_utc_epoch_nanoseconds(iso_date_time1);
// 6. Let destEpochNs be GetUTCEpochNanoseconds(isoDateTime2).
auto dest_epoch_ns = get_utc_epoch_nanoseconds(iso_date_time2);
// 6. Return ? RoundRelativeDuration(diff, destEpochNs, isoDateTime1, UNSET, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode).
return TRY(round_relative_duration(vm, diff, dest_epoch_ns, iso_date_time1, {}, calendar, largest_unit, rounding_increment, smallest_unit, rounding_mode));
// 7. Return ? RoundRelativeDuration(diff, originEpochNs, destEpochNs, isoDateTime1, UNSET, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode).
return TRY(round_relative_duration(vm, diff, origin_epoch_ns, dest_epoch_ns, iso_date_time1, {}, calendar, largest_unit, rounding_increment, smallest_unit, rounding_mode));
}
// 5.5.14 DifferencePlainDateTimeWithTotal ( isoDateTime1, isoDateTime2, calendar, unit ), https://tc39.es/proposal-temporal/#sec-temporal-differenceplaindatetimewithtotal
@ -397,11 +400,14 @@ ThrowCompletionOr<Crypto::BigFraction> difference_plain_date_time_with_total(VM&
if (unit == Unit::Nanosecond)
return move(diff.time);
// 5. Let destEpochNs be GetUTCEpochNanoseconds(isoDateTime2).
// 5. Let originEpochNs be GetUTCEpochNanoseconds(isoDateTime1).
auto origin_epoch_ns = get_utc_epoch_nanoseconds(iso_date_time1);
// 6. Let destEpochNs be GetUTCEpochNanoseconds(isoDateTime2).
auto dest_epoch_ns = get_utc_epoch_nanoseconds(iso_date_time2);
// 6. Return ? TotalRelativeDuration(diff, destEpochNs, isoDateTime1, UNSET, calendar, unit).
return TRY(total_relative_duration(vm, diff, dest_epoch_ns, iso_date_time1, {}, calendar, unit));
// 7. Return ? TotalRelativeDuration(diff, originEpochNs, destEpochNs, isoDateTime1, UNSET, calendar, unit).
return TRY(total_relative_duration(vm, diff, origin_epoch_ns, dest_epoch_ns, iso_date_time1, {}, calendar, unit));
}
// 5.5.15 DifferenceTemporalPlainDateTime ( operation, dateTime, other, options ), https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalplaindatetime

View file

@ -254,14 +254,17 @@ ThrowCompletionOr<GC::Ref<Duration>> difference_temporal_plain_year_month(VM& vm
// a. Let isoDateTime be CombineISODateAndTimeRecord(thisDate, MidnightTimeRecord()).
auto iso_date_time = combine_iso_date_and_time_record(this_date, midnight_time_record());
// b. Let isoDateTimeOther be CombineISODateAndTimeRecord(otherDate, MidnightTimeRecord()).
// b. Let originEpochNs be GetUTCEpochNanoseconds(isoDateTime).
auto origin_epoch_ns = get_utc_epoch_nanoseconds(iso_date_time);
// c. Let isoDateTimeOther be CombineISODateAndTimeRecord(otherDate, MidnightTimeRecord()).
auto iso_date_time_other = combine_iso_date_and_time_record(other_date, midnight_time_record());
// c. Let destEpochNs be GetUTCEpochNanoseconds(isoDateTimeOther).
// d. Let destEpochNs be GetUTCEpochNanoseconds(isoDateTimeOther).
auto dest_epoch_ns = get_utc_epoch_nanoseconds(iso_date_time_other);
// d. Set duration to ? RoundRelativeDuration(duration, destEpochNs, isoDateTime, UNSET, calendar, settings.[[LargestUnit]], settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]).
duration = TRY(round_relative_duration(vm, move(duration), dest_epoch_ns, iso_date_time, {}, calendar, settings.largest_unit, settings.rounding_increment, settings.smallest_unit, settings.rounding_mode));
// e. Set duration to ? RoundRelativeDuration(duration, originEpochNs, destEpochNs, isoDateTime, UNSET, calendar, settings.[[LargestUnit]], settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]).
duration = TRY(round_relative_duration(vm, move(duration), origin_epoch_ns, dest_epoch_ns, iso_date_time, {}, calendar, settings.largest_unit, settings.rounding_increment, settings.smallest_unit, settings.rounding_mode));
}
// 17. Let result be ! TemporalDurationFromInternal(duration, DAY).

View file

@ -438,28 +438,37 @@ ThrowCompletionOr<InternalDuration> difference_zoned_date_time(VM& vm, Crypto::S
// 3. Let endDateTime be GetISODateTimeFor(timeZone, ns2).
auto end_date_time = get_iso_date_time_for(time_zone, nanoseconds2);
// 4. If ns2 - ns1 < 0, let sign be -1; else let sign be 1.
// 4. If CompareISODate(startDateTime.[[ISODate]], endDateTime.[[ISODate]]) = 0, then
if (compare_iso_date(start_date_time.iso_date, end_date_time.iso_date) == 0) {
// a. Let timeDuration be TimeDurationFromEpochNanosecondsDifference(ns2, ns1).
auto time_duration = time_duration_from_epoch_nanoseconds_difference(nanoseconds2, nanoseconds1);
// b. Return CombineDateAndTimeDuration(ZeroDateDuration(), timeDuration).
return combine_date_and_time_duration(zero_date_duration(vm), move(time_duration));
}
// 5. If ns2 - ns1 < 0, let sign be -1; else let sign be 1.
double sign = nanoseconds2 < nanoseconds1 ? -1 : 1;
// 5. If sign = 1, let maxDayCorrection be 2; else let maxDayCorrection be 1.
// 6. If sign = 1, let maxDayCorrection be 2; else let maxDayCorrection be 1.
double max_day_correction = sign == 1 ? 2 : 1;
// 6. Let dayCorrection be 0.
// 7. Let dayCorrection be 0.
double day_correction = 0;
// 7. Let timeDuration be DifferenceTime(startDateTime.[[Time]], endDateTime.[[Time]]).
// 8. Let timeDuration be DifferenceTime(startDateTime.[[Time]], endDateTime.[[Time]]).
auto time_duration = difference_time(start_date_time.time, end_date_time.time);
// 8. If TimeDurationSign(timeDuration) = -sign, set dayCorrection to dayCorrection + 1.
// 9. If TimeDurationSign(timeDuration) = -sign, set dayCorrection to dayCorrection + 1.
if (time_duration_sign(time_duration) == -sign)
++day_correction;
// 9. Let success be false.
// 10. Let success be false.
auto success = false;
ISODateTime intermediate_date_time;
// 10. Repeat, while dayCorrection ≤ maxDayCorrection and success is false,
// 11. Repeat, while dayCorrection ≤ maxDayCorrection and success is false,
while (day_correction <= max_day_correction && !success) {
// a. Let intermediateDate be BalanceISODate(endDateTime.[[ISODate]].[[Year]], endDateTime.[[ISODate]].[[Month]], endDateTime.[[ISODate]].[[Day]] - dayCorrection × sign).
auto intermediate_date = balance_iso_date(end_date_time.iso_date.year, end_date_time.iso_date.month, static_cast<double>(end_date_time.iso_date.day) - (day_correction * sign));
@ -486,16 +495,16 @@ ThrowCompletionOr<InternalDuration> difference_zoned_date_time(VM& vm, Crypto::S
++day_correction;
}
// 11. Assert: success is true.
// 12. Assert: success is true.
VERIFY(success);
// 12. Let dateLargestUnit be LargerOfTwoTemporalUnits(largestUnit, DAY).
// 13. Let dateLargestUnit be LargerOfTwoTemporalUnits(largestUnit, DAY).
auto date_largest_unit = larger_of_two_temporal_units(largest_unit, Unit::Day);
// 13. Let dateDifference be CalendarDateUntil(calendar, startDateTime.[[ISODate]], intermediateDateTime.[[ISODate]], dateLargestUnit).
// 14. Let dateDifference be CalendarDateUntil(calendar, startDateTime.[[ISODate]], intermediateDateTime.[[ISODate]], dateLargestUnit).
auto date_difference = calendar_date_until(vm, calendar, start_date_time.iso_date, intermediate_date_time.iso_date, date_largest_unit);
// 14. Return CombineDateAndTimeDuration(dateDifference, timeDuration).
// 15. Return CombineDateAndTimeDuration(dateDifference, timeDuration).
return combine_date_and_time_duration(date_difference, move(time_duration));
}
@ -518,8 +527,8 @@ ThrowCompletionOr<InternalDuration> difference_zoned_date_time_with_rounding(VM&
// 4. Let dateTime be GetISODateTimeFor(timeZone, ns1).
auto date_time = get_iso_date_time_for(time_zone, nanoseconds1);
// 5. Return ? RoundRelativeDuration(difference, ns2, dateTime, timeZone, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode).
return TRY(round_relative_duration(vm, difference, nanoseconds2, date_time, time_zone, calendar, largest_unit, rounding_increment, smallest_unit, rounding_mode));
// 5. Return ? RoundRelativeDuration(difference, ns1, ns2, dateTime, timeZone, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode).
return TRY(round_relative_duration(vm, difference, nanoseconds1, nanoseconds2, date_time, time_zone, calendar, largest_unit, rounding_increment, smallest_unit, rounding_mode));
}
// 6.5.8 DifferenceZonedDateTimeWithTotal ( ns1, ns2, timeZone, calendar, unit ), https://tc39.es/proposal-temporal/#sec-temporal-differencezoneddatetimewithtotal
@ -540,8 +549,8 @@ ThrowCompletionOr<Crypto::BigFraction> difference_zoned_date_time_with_total(VM&
// 3. Let dateTime be GetISODateTimeFor(timeZone, ns1).
auto date_time = get_iso_date_time_for(time_zone, nanoseconds1);
// 4. Return ? TotalRelativeDuration(difference, ns2, dateTime, timeZone, calendar, unit).
return TRY(total_relative_duration(vm, difference, nanoseconds2, date_time, time_zone, calendar, unit));
// 4. Return ? TotalRelativeDuration(difference, ns1, ns2, dateTime, timeZone, calendar, unit).
return TRY(total_relative_duration(vm, difference, nanoseconds1, nanoseconds2, date_time, time_zone, calendar, unit));
}
// 6.5.9 DifferenceTemporalZonedDateTime ( operation, zonedDateTime, other, options ), https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalzoneddatetime

View file

@ -112,6 +112,17 @@ describe("correct behavior", () => {
expect(result.months).toBe(1);
});
});
test("relativeTo falls within second wallclock occurence of DST transition", () => {
const duration = Temporal.Duration.from({ minutes: -59 });
const result = duration.round({
smallestUnit: "days",
relativeTo: "2025-11-02T01:00:00-08:00[America/Vancouver]",
});
expect(result.toString()).toBe("PT0S");
});
});
describe("errors", () => {

View file

@ -57,6 +57,17 @@ describe("correct behavior", () => {
});
expect(result).toBe(366);
});
test("relativeTo falls within second wallclock occurence of DST transition", () => {
const duration = Temporal.Duration.from({ minutes: -59 });
const result = duration.total({
unit: "days",
relativeTo: "2025-11-02T01:00:00-08:00[America/Vancouver]",
});
expect(result).toBeCloseTo(-59 / (60 * 25));
});
});
describe("errors", () => {