tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 38a0c9534a4888903ecc02eb9f11aad7909438b9
parent c13b7064a7eaf502c4baeffea8d1e566eb88b285
Author: André Bargull <andre.bargull@gmail.com>
Date:   Thu,  6 Nov 2025 07:44:06 +0000

Bug 1997196: Fix ZonedDateTime differences where earlier wall-clock time is later exact time, or vice versa. r=dminor

Applies the changes from <https://github.com/tc39/proposal-temporal/pull/3147>.

Differential Revision: https://phabricator.services.mozilla.com/D270616

Diffstat:
Mjs/src/builtin/temporal/Duration.cpp | 112++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Mjs/src/builtin/temporal/Duration.h | 16+++++++++-------
Mjs/src/builtin/temporal/PlainDate.cpp | 13++++++++-----
Mjs/src/builtin/temporal/PlainDateTime.cpp | 16+++++++++++-----
Mjs/src/builtin/temporal/PlainYearMonth.cpp | 16+++++++++++-----
Mjs/src/builtin/temporal/ZonedDateTime.cpp | 50++++++++++++++++++++++++++++++--------------------
6 files changed, 131 insertions(+), 92 deletions(-)

diff --git a/js/src/builtin/temporal/Duration.cpp b/js/src/builtin/temporal/Duration.cpp @@ -2031,18 +2031,18 @@ struct DurationNudge { }; /** - * NudgeToCalendarUnit ( sign, duration, destEpochNs, isoDateTime, timeZone, - * calendar, increment, unit, roundingMode ) + * NudgeToCalendarUnit ( sign, duration, originEpochNs, destEpochNs, + * isoDateTime, timeZone, calendar, increment, unit, roundingMode ) */ -static bool NudgeToCalendarUnit(JSContext* cx, const InternalDuration& duration, - const EpochNanoseconds& destEpochNs, - const ISODateTime& isoDateTime, - Handle<TimeZoneValue> timeZone, - Handle<CalendarValue> calendar, - Increment increment, TemporalUnit unit, - TemporalRoundingMode roundingMode, - DurationNudge* result) { +static bool NudgeToCalendarUnit( + JSContext* cx, const InternalDuration& duration, + const EpochNanoseconds& originEpochNs, const EpochNanoseconds& destEpochNs, + const ISODateTime& isoDateTime, Handle<TimeZoneValue> timeZone, + Handle<CalendarValue> calendar, Increment increment, TemporalUnit unit, + TemporalRoundingMode roundingMode, DurationNudge* result) { MOZ_ASSERT(IsValidDuration(duration)); + MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(originEpochNs)); + MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(originEpochNs)); MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(destEpochNs)); MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(destEpochNs)); MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime)); @@ -2160,46 +2160,55 @@ static bool NudgeToCalendarUnit(JSContext* cx, const InternalDuration& duration, // Step 6. MOZ_ASSERT_IF(sign < 0, r1 <= 0 && r1 > r2); - // Step 7. - ISODate start; - if (!CalendarDateAdd(cx, calendar, isoDateTime.date, startDuration, - TemporalOverflow::Constrain, &start)) { - return false; + // Steps 7-8. + EpochNanoseconds startEpochNs; + if (r1 == 0) { + // Step 7.a. + startEpochNs = originEpochNs; + } else { + // Step 8.a. + ISODate start; + if (!CalendarDateAdd(cx, calendar, isoDateTime.date, startDuration, + TemporalOverflow::Constrain, &start)) { + return false; + } + + // Step 8.b. + auto startDateTime = ISODateTime{start, isoDateTime.time}; + MOZ_ASSERT(ISODateTimeWithinLimits(startDateTime)); + + // Steps 8.c-d. + if (!timeZone) { + // Step 8.c. + startEpochNs = GetUTCEpochNanoseconds(startDateTime); + } else { + // Step 8.d. + if (!GetEpochNanosecondsFor(cx, timeZone, startDateTime, + TemporalDisambiguation::Compatible, + &startEpochNs)) { + return false; + } + } } - // Step 8. + // Step 9. ISODate end; if (!CalendarDateAdd(cx, calendar, isoDateTime.date, endDuration, TemporalOverflow::Constrain, &end)) { return false; } - // Step 9. - auto startDateTime = ISODateTime{start, isoDateTime.time}; - MOZ_ASSERT(ISODateTimeWithinLimits(startDateTime)); - // Step 10. auto endDateTime = ISODateTime{end, isoDateTime.time}; MOZ_ASSERT(ISODateTimeWithinLimits(endDateTime)); // Steps 11-12. - EpochNanoseconds startEpochNs; EpochNanoseconds endEpochNs; if (!timeZone) { // Step 11.a. - startEpochNs = GetUTCEpochNanoseconds(startDateTime); - - // Step 11.b. endEpochNs = GetUTCEpochNanoseconds(endDateTime); } else { // Step 12.a. - if (!GetEpochNanosecondsFor(cx, timeZone, startDateTime, - TemporalDisambiguation::Compatible, - &startEpochNs)) { - return false; - } - - // Step 12.b. if (!GetEpochNanosecondsFor(cx, timeZone, endDateTime, TemporalDisambiguation::Compatible, &endEpochNs)) { @@ -2667,16 +2676,19 @@ static bool BubbleRelativeDuration( } /** - * RoundRelativeDuration ( duration, destEpochNs, isoDateTime, timeZone, - * calendar, largestUnit, increment, smallestUnit, roundingMode ) + * RoundRelativeDuration ( duration, originEpochNs, destEpochNs, isoDateTime, + * timeZone, calendar, largestUnit, increment, smallestUnit, roundingMode ) */ bool js::temporal::RoundRelativeDuration( JSContext* cx, const InternalDuration& duration, - const EpochNanoseconds& destEpochNs, const ISODateTime& isoDateTime, - Handle<TimeZoneValue> timeZone, Handle<CalendarValue> calendar, - TemporalUnit largestUnit, Increment increment, TemporalUnit smallestUnit, + const EpochNanoseconds& originEpochNs, const EpochNanoseconds& destEpochNs, + const ISODateTime& isoDateTime, Handle<TimeZoneValue> timeZone, + Handle<CalendarValue> calendar, TemporalUnit largestUnit, + Increment increment, TemporalUnit smallestUnit, TemporalRoundingMode roundingMode, InternalDuration* result) { MOZ_ASSERT(IsValidDuration(duration)); + MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(originEpochNs)); + MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(originEpochNs)); MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(destEpochNs)); MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(destEpochNs)); MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime)); @@ -2692,9 +2704,9 @@ bool js::temporal::RoundRelativeDuration( DurationNudge nudge; if (irregularLengthUnit) { // Step 5.a. - if (!NudgeToCalendarUnit(cx, duration, destEpochNs, isoDateTime, timeZone, - calendar, increment, smallestUnit, roundingMode, - &nudge)) { + if (!NudgeToCalendarUnit(cx, duration, originEpochNs, destEpochNs, + isoDateTime, timeZone, calendar, increment, + smallestUnit, roundingMode, &nudge)) { return false; } } else if (timeZone) { @@ -2731,17 +2743,17 @@ bool js::temporal::RoundRelativeDuration( } /** - * TotalRelativeDuration ( duration, destEpochNs, isoDateTime, timeZone, - * calendar, unit ) + * TotalRelativeDuration ( duration, originEpochNs, destEpochNs, isoDateTime, + * timeZone, calendar, unit ) */ -bool js::temporal::TotalRelativeDuration(JSContext* cx, - const InternalDuration& duration, - const EpochNanoseconds& destEpochNs, - const ISODateTime& isoDateTime, - JS::Handle<TimeZoneValue> timeZone, - JS::Handle<CalendarValue> calendar, - TemporalUnit unit, double* result) { +bool js::temporal::TotalRelativeDuration( + JSContext* cx, const InternalDuration& duration, + const EpochNanoseconds& originEpochNs, const EpochNanoseconds& destEpochNs, + const ISODateTime& isoDateTime, JS::Handle<TimeZoneValue> timeZone, + JS::Handle<CalendarValue> calendar, TemporalUnit unit, double* result) { MOZ_ASSERT(IsValidDuration(duration)); + MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(originEpochNs)); + MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(originEpochNs)); MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(destEpochNs)); MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(destEpochNs)); MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime)); @@ -2750,8 +2762,8 @@ bool js::temporal::TotalRelativeDuration(JSContext* cx, // Steps 1.a-b. DurationNudge nudge; - if (!NudgeToCalendarUnit(cx, duration, destEpochNs, isoDateTime, timeZone, - calendar, Increment{1}, unit, + if (!NudgeToCalendarUnit(cx, duration, originEpochNs, destEpochNs, + isoDateTime, timeZone, calendar, Increment{1}, unit, TemporalRoundingMode::Trunc, &nudge)) { return false; } diff --git a/js/src/builtin/temporal/Duration.h b/js/src/builtin/temporal/Duration.h @@ -239,21 +239,23 @@ TimeDuration RoundTimeDuration(const TimeDuration& duration, TemporalRoundingMode roundingMode); /** - * RoundRelativeDuration ( duration, destEpochNs, isoDateTime, timeZone, - * calendar, largestUnit, increment, smallestUnit, roundingMode ) + * RoundRelativeDuration ( duration, originEpochNs, destEpochNs, isoDateTime, + * timeZone, calendar, largestUnit, increment, smallestUnit, roundingMode ) */ bool RoundRelativeDuration( JSContext* cx, const InternalDuration& duration, - const EpochNanoseconds& destEpochNs, const ISODateTime& isoDateTime, - JS::Handle<TimeZoneValue> timeZone, JS::Handle<CalendarValue> calendar, - TemporalUnit largestUnit, Increment increment, TemporalUnit smallestUnit, + const EpochNanoseconds& originEpochNs, const EpochNanoseconds& destEpochNs, + const ISODateTime& isoDateTime, JS::Handle<TimeZoneValue> timeZone, + JS::Handle<CalendarValue> calendar, TemporalUnit largestUnit, + Increment increment, TemporalUnit smallestUnit, TemporalRoundingMode roundingMode, InternalDuration* result); /** - * TotalRelativeDuration ( duration, destEpochNs, isoDateTime, timeZone, - * calendar, unit ) + * TotalRelativeDuration ( duration, originEpochNs, destEpochNs, isoDateTime, + * timeZone, calendar, unit ) */ bool TotalRelativeDuration(JSContext* cx, const InternalDuration& duration, + const EpochNanoseconds& originEpochNs, const EpochNanoseconds& destEpochNs, const ISODateTime& isoDateTime, JS::Handle<TimeZoneValue> timeZone, diff --git a/js/src/builtin/temporal/PlainDate.cpp b/js/src/builtin/temporal/PlainDate.cpp @@ -644,16 +644,19 @@ static bool DifferenceTemporalPlainDate(JSContext* cx, auto isoDateTime = ISODateTime{temporalDate.date(), {}}; // Step 8.b. - auto isoDateTimeOther = ISODateTime{other.date(), {}}; + auto originEpochNs = GetUTCEpochNanoseconds(isoDateTime); // Step 8.c. - auto destEpochNs = GetUTCEpochNanoseconds(isoDateTimeOther); + auto isoDateTimeOther = ISODateTime{other.date(), {}}; // Step 8.d. + auto destEpochNs = GetUTCEpochNanoseconds(isoDateTimeOther); + + // Step 8.e. Rooted<TimeZoneValue> timeZone(cx, TimeZoneValue{}); - if (!RoundRelativeDuration(cx, duration, destEpochNs, isoDateTime, timeZone, - temporalDate.calendar(), settings.largestUnit, - settings.roundingIncrement, + if (!RoundRelativeDuration(cx, duration, originEpochNs, destEpochNs, + isoDateTime, timeZone, temporalDate.calendar(), + settings.largestUnit, settings.roundingIncrement, settings.smallestUnit, settings.roundingMode, &duration)) { return false; diff --git a/js/src/builtin/temporal/PlainDateTime.cpp b/js/src/builtin/temporal/PlainDateTime.cpp @@ -602,12 +602,15 @@ bool js::temporal::DifferencePlainDateTimeWithRounding( } // Step 5. - auto destEpochNs = GetUTCEpochNanoseconds(isoDateTime2); + auto originEpochNs = GetUTCEpochNanoseconds(isoDateTime1); // Step 6. + auto destEpochNs = GetUTCEpochNanoseconds(isoDateTime2); + + // Step 7. Rooted<TimeZoneValue> timeZone(cx, TimeZoneValue{}); return RoundRelativeDuration( - cx, diff, destEpochNs, isoDateTime1, timeZone, calendar, + cx, diff, originEpochNs, destEpochNs, isoDateTime1, timeZone, calendar, settings.largestUnit, settings.roundingIncrement, settings.smallestUnit, settings.roundingMode, result); } @@ -661,12 +664,15 @@ bool js::temporal::DifferencePlainDateTimeWithTotal( } // Step 5. - auto destEpochNs = GetUTCEpochNanoseconds(isoDateTime2); + auto originEpochNs = GetUTCEpochNanoseconds(isoDateTime1); // Step 6. + auto destEpochNs = GetUTCEpochNanoseconds(isoDateTime2); + + // Step 7. Rooted<TimeZoneValue> timeZone(cx, TimeZoneValue{}); - return TotalRelativeDuration(cx, diff, destEpochNs, isoDateTime1, timeZone, - calendar, unit, result); + return TotalRelativeDuration(cx, diff, originEpochNs, destEpochNs, + isoDateTime1, timeZone, calendar, unit, result); } /** diff --git a/js/src/builtin/temporal/PlainYearMonth.cpp b/js/src/builtin/temporal/PlainYearMonth.cpp @@ -442,16 +442,22 @@ static bool DifferenceTemporalPlainYearMonth(JSContext* cx, if (settings.smallestUnit != TemporalUnit::Month || settings.roundingIncrement != Increment{1}) { // Step 16.a. - auto destEpochNs = GetUTCEpochNanoseconds(ISODateTime{otherDate, {}}); + auto isoDateTime = ISODateTime{thisDate, {}}; - // Steps 16.b-c. - auto dateTime = ISODateTime{thisDate, {}}; + // Step 16.b. + auto originEpochNs = GetUTCEpochNanoseconds(isoDateTime); + + // Step 16.c. + auto isoDateTimeOther = ISODateTime{otherDate, {}}; // Step 16.d. + auto destEpochNs = GetUTCEpochNanoseconds(isoDateTimeOther); + + // Step 16.e. Rooted<TimeZoneValue> timeZone(cx, TimeZoneValue{}); if (!RoundRelativeDuration( - cx, duration, destEpochNs, dateTime, timeZone, calendar, - settings.largestUnit, settings.roundingIncrement, + cx, duration, originEpochNs, destEpochNs, isoDateTime, timeZone, + calendar, settings.largestUnit, settings.roundingIncrement, settings.smallestUnit, settings.roundingMode, &duration)) { return false; } diff --git a/js/src/builtin/temporal/ZonedDateTime.cpp b/js/src/builtin/temporal/ZonedDateTime.cpp @@ -688,29 +688,39 @@ static bool DifferenceZonedDateTime(JSContext* cx, const EpochNanoseconds& ns1, } // Step 4. - int32_t sign = (ns2 - ns1 < EpochDuration{}) ? -1 : 1; + if (CompareISODate(startDateTime.date, endDateTime.date) == 0) { + // Step 4.a. + auto timeDuration = TimeDurationFromEpochNanosecondsDifference(ns2, ns1); + + // Step 4.b. + *result = {{}, timeDuration}; + return true; + } // Step 5. - int32_t maxDayCorrection = 1 + (sign > 0); + int32_t sign = (ns2 - ns1 < EpochDuration{}) ? -1 : 1; // Step 6. - int32_t dayCorrection = 0; + int32_t maxDayCorrection = 1 + (sign > 0); // Step 7. - auto timeDuration = DifferenceTime(startDateTime.time, endDateTime.time); + int32_t dayCorrection = 0; // Step 8. + auto timeDuration = DifferenceTime(startDateTime.time, endDateTime.time); + + // Step 9. if (TimeDurationSign(timeDuration) == -sign) { dayCorrection += 1; } - // Steps 9-10. + // Steps 10-11. while (dayCorrection <= maxDayCorrection) { - // Step 10.a. + // Step 11.a. auto intermediateDate = BalanceISODate(endDateTime.date, -dayCorrection * sign); - // Step 10.b. + // Step 11.b. auto intermediateDateTime = ISODateTime{intermediateDate, startDateTime.time}; if (!ISODateTimeWithinLimits(intermediateDateTime)) { @@ -719,7 +729,7 @@ static bool DifferenceZonedDateTime(JSContext* cx, const EpochNanoseconds& ns1, return false; } - // Step 10.c. + // Step 11.c. EpochNanoseconds intermediateNs; if (!GetEpochNanosecondsFor(cx, timeZone, intermediateDateTime, TemporalDisambiguation::Compatible, @@ -727,26 +737,26 @@ static bool DifferenceZonedDateTime(JSContext* cx, const EpochNanoseconds& ns1, return false; } - // Step 10.d. + // Step 11.d. auto timeDuration = TimeDurationFromEpochNanosecondsDifference(ns2, intermediateNs); - // Step 10.e. + // Step 11.e. int32_t timeSign = TimeDurationSign(timeDuration); - // Step 10.f. + // Step 11.f. if (sign != -timeSign) { - // Step 12. + // Step 13. auto dateLargestUnit = std::min(largestUnit, TemporalUnit::Day); - // Step 13. + // Step 14. DateDuration dateDifference; if (!CalendarDateUntil(cx, calendar, startDateTime.date, intermediateDate, dateLargestUnit, &dateDifference)) { return false; } - // Step 14. + // Step 15. MOZ_ASSERT(DateDurationSign(dateDifference) * TimeDurationSign(timeDuration) >= 0); @@ -754,11 +764,11 @@ static bool DifferenceZonedDateTime(JSContext* cx, const EpochNanoseconds& ns1, return true; } - // Step 10.g. + // Step 11.g. dayCorrection += 1; } - // Step 11. + // Step 12. JS_ReportErrorNumberASCII( cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_ZONED_DATE_TIME_INCONSISTENT_INSTANT); @@ -813,9 +823,9 @@ bool js::temporal::DifferenceZonedDateTimeWithRounding( // Step 5. return RoundRelativeDuration( - cx, difference, ns2, dateTime, timeZone, calendar, settings.largestUnit, - settings.roundingIncrement, settings.smallestUnit, settings.roundingMode, - result); + cx, difference, ns1, ns2, dateTime, timeZone, calendar, + settings.largestUnit, settings.roundingIncrement, settings.smallestUnit, + settings.roundingMode, result); } /** @@ -855,7 +865,7 @@ bool js::temporal::DifferenceZonedDateTimeWithTotal( } // Step 5. - return TotalRelativeDuration(cx, difference, ns2, dateTime, timeZone, + return TotalRelativeDuration(cx, difference, ns1, ns2, dateTime, timeZone, calendar, unit, result); }