diff options
Diffstat (limited to '')
-rw-r--r-- | js/src/builtin/temporal/ZonedDateTime.cpp | 1003 |
1 files changed, 531 insertions, 472 deletions
diff --git a/js/src/builtin/temporal/ZonedDateTime.cpp b/js/src/builtin/temporal/ZonedDateTime.cpp index 92842a9626..e75b368ba9 100644 --- a/js/src/builtin/temporal/ZonedDateTime.cpp +++ b/js/src/builtin/temporal/ZonedDateTime.cpp @@ -10,6 +10,7 @@ #include "mozilla/Maybe.h" #include <cstdlib> +#include <limits> #include <utility> #include "jspubtd.h" @@ -18,6 +19,7 @@ #include "builtin/temporal/Calendar.h" #include "builtin/temporal/Duration.h" #include "builtin/temporal/Instant.h" +#include "builtin/temporal/Int96.h" #include "builtin/temporal/PlainDate.h" #include "builtin/temporal/PlainDateTime.h" #include "builtin/temporal/PlainMonthDay.h" @@ -119,7 +121,8 @@ bool js::temporal::InterpretISODateTimeOffset( // Step 5. if (offsetBehaviour == OffsetBehaviour::Wall || - offsetOption == TemporalOffset::Ignore) { + (offsetBehaviour == OffsetBehaviour::Option && + offsetOption == TemporalOffset::Ignore)) { // Steps 5.a-b. return GetInstantFor(cx, timeZone, temporalDateTime, disambiguation, result); @@ -127,7 +130,8 @@ bool js::temporal::InterpretISODateTimeOffset( // Step 6. if (offsetBehaviour == OffsetBehaviour::Exact || - offsetOption == TemporalOffset::Use) { + (offsetBehaviour == OffsetBehaviour::Option && + offsetOption == TemporalOffset::Use)) { // Step 6.a. auto epochNanoseconds = GetUTCEpochNanoseconds( dateTime, InstantSpan::fromNanoseconds(offsetNanoseconds)); @@ -151,27 +155,21 @@ bool js::temporal::InterpretISODateTimeOffset( MOZ_ASSERT(offsetOption == TemporalOffset::Prefer || offsetOption == TemporalOffset::Reject); - // FIXME: spec issue - duplicate assertion - // Step 9. - MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp( - timeZone, TimeZoneMethod::GetPossibleInstantsFor)); - - // Step 10. Rooted<InstantVector> possibleInstants(cx, InstantVector(cx)); if (!GetPossibleInstantsFor(cx, timeZone, temporalDateTime, &possibleInstants)) { return false; } - // Step 11. + // Step 10. if (!possibleInstants.empty()) { - // Step 11.a. + // Step 10.a. Rooted<Wrapped<InstantObject*>> candidate(cx); for (size_t i = 0; i < possibleInstants.length(); i++) { candidate = possibleInstants[i]; - // Step 11.a.i. + // Step 10.a.i. int64_t candidateNanoseconds; if (!GetOffsetNanosecondsFor(cx, timeZone, candidate, &candidateNanoseconds)) { @@ -180,7 +178,7 @@ bool js::temporal::InterpretISODateTimeOffset( MOZ_ASSERT(std::abs(candidateNanoseconds) < ToNanoseconds(TemporalUnit::Day)); - // Step 11.a.ii. + // Step 10.a.ii. if (candidateNanoseconds == offsetNanoseconds) { auto* unwrapped = candidate.unwrap(cx); if (!unwrapped) { @@ -191,20 +189,20 @@ bool js::temporal::InterpretISODateTimeOffset( return true; } - // Step 11.a.iii. + // Step 10.a.iii. if (matchBehaviour == MatchBehaviour::MatchMinutes) { - // Step 11.a.iii.1. + // Step 10.a.iii.1. int64_t roundedCandidateNanoseconds = RoundNanosecondsToMinutesIncrement(candidateNanoseconds); - // Step 11.a.iii.2. + // Step 10.a.iii.2. if (roundedCandidateNanoseconds == offsetNanoseconds) { auto* unwrapped = candidate.unwrap(cx); if (!unwrapped) { return false; } - // Step 11.a.iii.2.a. + // Step 10.a.iii.2.a. *result = ToInstant(unwrapped); return true; } @@ -212,14 +210,14 @@ bool js::temporal::InterpretISODateTimeOffset( } } - // Step 12. + // Step 11. if (offsetOption == TemporalOffset::Reject) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_ZONED_DATE_TIME_NO_TIME_FOUND); return false; } - // Step 13. + // Step 12. Rooted<Wrapped<InstantObject*>> instant(cx); if (!DisambiguatePossibleInstants(cx, possibleInstants, timeZone, ToPlainDateTime(temporalDateTime), @@ -232,7 +230,7 @@ bool js::temporal::InterpretISODateTimeOffset( return false; } - // Step 14. + // Step 13. *result = ToInstant(unwrappedInstant); return true; } @@ -430,19 +428,19 @@ static bool ToTemporalZonedDateTime(JSContext* cx, Handle<Value> item, bool isUTC; bool hasOffset; int64_t timeZoneOffset; - Rooted<ParsedTimeZone> timeZoneString(cx); + Rooted<ParsedTimeZone> timeZoneAnnotation(cx); Rooted<JSString*> calendarString(cx); - if (!ParseTemporalZonedDateTimeString(cx, string, &dateTime, &isUTC, - &hasOffset, &timeZoneOffset, - &timeZoneString, &calendarString)) { + if (!ParseTemporalZonedDateTimeString( + cx, string, &dateTime, &isUTC, &hasOffset, &timeZoneOffset, + &timeZoneAnnotation, &calendarString)) { return false; } // Step 6.d. - MOZ_ASSERT(timeZoneString); + MOZ_ASSERT(timeZoneAnnotation); // Step 6.e. - if (!ToTemporalTimeZone(cx, timeZoneString, &timeZone)) { + if (!ToTemporalTimeZone(cx, timeZoneAnnotation, &timeZone)) { return false; } @@ -624,7 +622,7 @@ struct PlainDateTimeAndInstant { static bool AddDaysToZonedDateTime(JSContext* cx, const Instant& instant, const PlainDateTime& dateTime, Handle<TimeZoneRecord> timeZone, - Handle<CalendarValue> calendar, double days, + Handle<CalendarValue> calendar, int64_t days, TemporalOverflow overflow, PlainDateTimeAndInstant* result) { // Step 1. (Not applicable in our implementation.) @@ -669,7 +667,7 @@ static bool AddDaysToZonedDateTime(JSContext* cx, const Instant& instant, bool js::temporal::AddDaysToZonedDateTime( JSContext* cx, const Instant& instant, const PlainDateTime& dateTime, Handle<TimeZoneRecord> timeZone, Handle<CalendarValue> calendar, - double days, TemporalOverflow overflow, Instant* result) { + int64_t days, TemporalOverflow overflow, Instant* result) { // Steps 1-7. PlainDateTimeAndInstant dateTimeAndInstant; if (!::AddDaysToZonedDateTime(cx, instant, dateTime, timeZone, calendar, days, @@ -689,7 +687,7 @@ bool js::temporal::AddDaysToZonedDateTime(JSContext* cx, const Instant& instant, const PlainDateTime& dateTime, Handle<TimeZoneRecord> timeZone, Handle<CalendarValue> calendar, - double days, Instant* result) { + int64_t days, Instant* result) { // Step 2. auto overflow = TemporalOverflow::Constrain; @@ -700,18 +698,16 @@ bool js::temporal::AddDaysToZonedDateTime(JSContext* cx, const Instant& instant, /** * AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months, - * weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds - * [ , precalculatedPlainDateTime [ , options ] ] ) + * weeks, days, norm [ , precalculatedPlainDateTime [ , options ] ] ) */ static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds, Handle<TimeZoneRecord> timeZone, Handle<CalendarRecord> calendar, - const Duration& duration, + const NormalizedDuration& duration, mozilla::Maybe<const PlainDateTime&> dateTime, Handle<JSObject*> maybeOptions, Instant* result) { MOZ_ASSERT(IsValidEpochInstant(epochNanoseconds)); - MOZ_ASSERT(IsValidDuration(duration.date())); - MOZ_ASSERT(IsValidDuration(duration.time())); + MOZ_ASSERT(IsValidDuration(duration)); // Step 1. MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp( @@ -725,10 +721,9 @@ static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds, // Steps 4-5. (Not applicable in our implementation) // Step 6. - if (duration.years == 0 && duration.months == 0 && duration.weeks == 0 && - duration.days == 0) { + if (duration.date == DateDuration{}) { // Step 6.a. - return AddInstant(cx, epochNanoseconds, duration, result); + return AddInstant(cx, epochNanoseconds, duration.time, result); } // Step 7. (Not applicable in our implementation) @@ -748,7 +743,8 @@ static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds, auto& [date, time] = temporalDateTime; // Step 10. - if (duration.years == 0 && duration.months == 0 && duration.weeks == 0) { + if (duration.date.years == 0 && duration.date.months == 0 && + duration.date.weeks == 0) { // Step 10.a. auto overflow = TemporalOverflow::Constrain; if (maybeOptions) { @@ -760,13 +756,13 @@ static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds, // Step 10.b. Instant intermediate; if (!AddDaysToZonedDateTime(cx, epochNanoseconds, temporalDateTime, - timeZone, calendar.receiver(), duration.days, - overflow, &intermediate)) { + timeZone, calendar.receiver(), + duration.date.days, overflow, &intermediate)) { return false; } // Step 10.c. - return AddInstant(cx, intermediate, duration.time(), result); + return AddInstant(cx, intermediate, duration.time, result); } // Step 11. @@ -777,13 +773,13 @@ static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds, const auto& datePart = date; // Step 13. - auto dateDuration = duration.date(); + const auto& dateDuration = duration.date; // Step 14. PlainDate addedDate; if (maybeOptions) { - if (!CalendarDateAdd(cx, calendar, datePart, dateDuration, maybeOptions, - &addedDate)) { + if (!temporal::CalendarDateAdd(cx, calendar, datePart, dateDuration, + maybeOptions, &addedDate)) { return false; } } else { @@ -808,18 +804,17 @@ static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds, } // Step 17. - return AddInstant(cx, intermediateInstant, duration.time(), result); + return AddInstant(cx, intermediateInstant, duration.time, result); } /** * AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months, - * weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds - * [ , precalculatedPlainDateTime [ , options ] ] ) + * weeks, days, norm [ , precalculatedPlainDateTime [ , options ] ] ) */ static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds, Handle<TimeZoneRecord> timeZone, Handle<CalendarRecord> calendar, - const Duration& duration, + const NormalizedDuration& duration, Handle<JSObject*> maybeOptions, Instant* result) { return ::AddZonedDateTime(cx, epochNanoseconds, timeZone, calendar, duration, mozilla::Nothing(), maybeOptions, result); @@ -827,84 +822,94 @@ static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds, /** * AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months, - * weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds - * [ , precalculatedPlainDateTime [ , options ] ] ) + * weeks, days, norm [ , precalculatedPlainDateTime [ , options ] ] ) */ bool js::temporal::AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds, Handle<TimeZoneRecord> timeZone, Handle<CalendarRecord> calendar, - const Duration& duration, Instant* result) { + const NormalizedDuration& duration, + Instant* result) { return ::AddZonedDateTime(cx, epochNanoseconds, timeZone, calendar, duration, mozilla::Nothing(), nullptr, result); } /** * AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months, - * weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds - * [ , precalculatedPlainDateTime [ , options ] ] ) + * weeks, days, norm [ , precalculatedPlainDateTime [ , options ] ] ) */ -bool js::temporal::AddZonedDateTime( - JSContext* cx, const Instant& epochNanoseconds, - Handle<TimeZoneRecord> timeZone, Handle<CalendarRecord> calendar, - const Duration& duration, const PlainDateTime& dateTime, Instant* result) { +bool js::temporal::AddZonedDateTime(JSContext* cx, + const Instant& epochNanoseconds, + Handle<TimeZoneRecord> timeZone, + Handle<CalendarRecord> calendar, + const NormalizedDuration& duration, + const PlainDateTime& dateTime, + Instant* result) { return ::AddZonedDateTime(cx, epochNanoseconds, timeZone, calendar, duration, mozilla::SomeRef(dateTime), nullptr, result); } -double js::temporal::NanosecondsAndDays::daysNumber() const { - if (days) { - return BigInt::numberValue(days); - } - return double(daysInt); +/** + * AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months, + * weeks, days, norm [ , precalculatedPlainDateTime [ , options ] ] ) + */ +bool js::temporal::AddZonedDateTime(JSContext* cx, + const Instant& epochNanoseconds, + Handle<TimeZoneRecord> timeZone, + Handle<CalendarRecord> calendar, + const DateDuration& duration, + Instant* result) { + return ::AddZonedDateTime(cx, epochNanoseconds, timeZone, calendar, + {duration, {}}, mozilla::Nothing(), nullptr, + result); } -void js::temporal::NanosecondsAndDays::trace(JSTracer* trc) { - if (days) { - TraceRoot(trc, &days, "NanosecondsAndDays::days"); - } +/** + * AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months, + * weeks, days, norm [ , precalculatedPlainDateTime [ , options ] ] ) + */ +bool js::temporal::AddZonedDateTime(JSContext* cx, + const Instant& epochNanoseconds, + Handle<TimeZoneRecord> timeZone, + Handle<CalendarRecord> calendar, + const DateDuration& duration, + const PlainDateTime& dateTime, + Instant* result) { + return ::AddZonedDateTime(cx, epochNanoseconds, timeZone, calendar, + {duration, {}}, mozilla::SomeRef(dateTime), nullptr, + result); } /** - * NanosecondsToDays ( nanoseconds, zonedRelativeTo, timeZoneRec [ , + * NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , * precalculatedPlainDateTime ] ) */ -static bool NanosecondsToDays( - JSContext* cx, const InstantSpan& nanoseconds, +static bool NormalizedTimeDurationToDays( + JSContext* cx, const NormalizedTimeDuration& duration, Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone, mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime, - MutableHandle<NanosecondsAndDays> result) { - MOZ_ASSERT(IsValidInstantSpan(nanoseconds)); + NormalizedTimeAndDays* result) { + MOZ_ASSERT(IsValidNormalizedTimeDuration(duration)); // Step 1. - if (nanoseconds == InstantSpan{}) { - result.set(NanosecondsAndDays::from( - int64_t(0), InstantSpan{}, - InstantSpan::fromNanoseconds(ToNanoseconds(TemporalUnit::Day)))); - return true; - } + int32_t sign = NormalizedTimeDurationSign(duration); // Step 2. - int32_t sign = nanoseconds < InstantSpan{} ? -1 : 1; + if (sign == 0) { + *result = {int64_t(0), int64_t(0), ToNanoseconds(TemporalUnit::Day)}; + return true; + } // Step 3. - auto startNs = zonedRelativeTo.instant(); - auto calendar = zonedRelativeTo.calendar(); + const auto& startNs = zonedRelativeTo.instant(); // Step 5. - // - // NB: This addition can't overflow, because we've checked that |nanoseconds| - // can be represented as an InstantSpan value. - auto endNs = startNs + nanoseconds; - - // Step 6. - if (!IsValidEpochInstant(endNs)) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, - JSMSG_TEMPORAL_INSTANT_INVALID); + Instant endNs; + if (!AddInstant(cx, startNs, duration, &endNs)) { return false; } - // Steps 4 and 8. + // Steps 4 and 7. PlainDateTime startDateTime; if (!precalculatedPlainDateTime) { if (!GetPlainDateTimeFor(cx, timeZone, startNs, &startDateTime)) { @@ -914,145 +919,157 @@ static bool NanosecondsToDays( startDateTime = *precalculatedPlainDateTime; } - // Steps 7 and 9. + // Steps 6 and 8. PlainDateTime endDateTime; if (!GetPlainDateTimeFor(cx, timeZone, endNs, &endDateTime)) { return false; } - // Steps 10-11. (Not applicable in our implementation.) + // Steps 9-10. (Not applicable in our implementation.) - // Step 12. - // - // Overflows in step 21 can be safely ignored, because they take too long to - // happen for int64. - int64_t days = DaysUntil(startDateTime.date, endDateTime.date); + // Step 11. + int32_t days = DaysUntil(startDateTime.date, endDateTime.date); + MOZ_ASSERT(std::abs(days) <= MaxEpochDaysDuration); - // Step 13. + // Step 12. int32_t timeSign = CompareTemporalTime(startDateTime.time, endDateTime.time); - // Steps 14-15. + // Steps 13-14. if (days > 0 && timeSign > 0) { days -= 1; } else if (days < 0 && timeSign < 0) { days += 1; } - // Step 16. + // Step 15. PlainDateTimeAndInstant relativeResult; - if (!::AddDaysToZonedDateTime(cx, startNs, startDateTime, timeZone, calendar, - days, TemporalOverflow::Constrain, - &relativeResult)) { + if (!::AddDaysToZonedDateTime(cx, startNs, startDateTime, timeZone, + zonedRelativeTo.calendar(), days, + TemporalOverflow::Constrain, &relativeResult)) { return false; } MOZ_ASSERT(IsValidISODateTime(relativeResult.dateTime)); MOZ_ASSERT(IsValidEpochInstant(relativeResult.instant)); - // Step 17. - if (sign > 0) { - // Step 17.a. - while (days > 0 && relativeResult.instant > endNs) { - // This loop can iterate indefinitely when given a specially crafted - // time zone object, so we need to check for interrupts. - if (!CheckForInterrupt(cx)) { - return false; - } - - // Step 17.a.i. - days -= 1; + // Step 16. + if (sign > 0 && days > 0 && relativeResult.instant > endNs) { + // Step 16.a. + days -= 1; - // Step 17.a.ii. - if (!::AddDaysToZonedDateTime(cx, startNs, startDateTime, timeZone, - calendar, days, TemporalOverflow::Constrain, - &relativeResult)) { - return false; - } - MOZ_ASSERT(IsValidISODateTime(relativeResult.dateTime)); - MOZ_ASSERT(IsValidEpochInstant(relativeResult.instant)); + // Step 16.b. + if (!::AddDaysToZonedDateTime( + cx, startNs, startDateTime, timeZone, zonedRelativeTo.calendar(), + days, TemporalOverflow::Constrain, &relativeResult)) { + return false; + } + MOZ_ASSERT(IsValidISODateTime(relativeResult.dateTime)); + MOZ_ASSERT(IsValidEpochInstant(relativeResult.instant)); + + // Step 16.c. + if (days > 0 && relativeResult.instant > endNs) { + JS_ReportErrorNumberASCII( + cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_ZONED_DATE_TIME_INCONSISTENT_INSTANT); + return false; } - MOZ_ASSERT_IF(days > 0, relativeResult.instant <= endNs); } MOZ_ASSERT_IF(days == 0, relativeResult.instant == startNs); - // Step 18. + // Step 17. (Inlined NormalizedTimeDurationFromEpochNanosecondsDifference) auto ns = endNs - relativeResult.instant; MOZ_ASSERT(IsValidInstantSpan(ns)); - // Steps 19-21. - InstantSpan dayLengthNs{}; - while (true) { - // This loop can iterate indefinitely when given a specially crafted time - // zone object, so we need to check for interrupts. - if (!CheckForInterrupt(cx)) { - return false; - } + // Step 18. + PlainDateTimeAndInstant oneDayFarther; + if (!::AddDaysToZonedDateTime(cx, relativeResult.instant, + relativeResult.dateTime, timeZone, + zonedRelativeTo.calendar(), sign, + TemporalOverflow::Constrain, &oneDayFarther)) { + return false; + } + MOZ_ASSERT(IsValidISODateTime(oneDayFarther.dateTime)); + MOZ_ASSERT(IsValidEpochInstant(oneDayFarther.instant)); + + // Step 19. (Inlined NormalizedTimeDurationFromEpochNanosecondsDifference) + auto dayLengthNs = oneDayFarther.instant - relativeResult.instant; + MOZ_ASSERT(IsValidInstantSpan(dayLengthNs)); + + // clang-format off + // + // ns = endNs - relativeResult.instant + // dayLengthNs = oneDayFarther.instant - relativeResult.instant + // oneDayLess = ns - dayLengthNs + // = (endNs - relativeResult.instant) - (oneDayFarther.instant - relativeResult.instant) + // = endNs - relativeResult.instant - oneDayFarther.instant + relativeResult.instant + // = endNs - oneDayFarther.instant + // + // |endNs| and |oneDayFarther.instant| are both valid epoch instant values, + // so the difference |oneDayLess| is a valid epoch instant difference value. + // + // clang-format on + // Step 20. (Inlined SubtractNormalizedTimeDuration) + auto oneDayLess = ns - dayLengthNs; + MOZ_ASSERT(IsValidInstantSpan(oneDayLess)); + MOZ_ASSERT(oneDayLess == (endNs - oneDayFarther.instant)); + + // Step 21. + if (oneDayLess == InstantSpan{} || + ((oneDayLess < InstantSpan{}) == (sign < 0))) { // Step 21.a. + ns = oneDayLess; + + // Step 21.b. + relativeResult = oneDayFarther; + + // Step 21.c. + days += sign; + + // Step 21.d. PlainDateTimeAndInstant oneDayFarther; if (!::AddDaysToZonedDateTime( cx, relativeResult.instant, relativeResult.dateTime, timeZone, - calendar, sign, TemporalOverflow::Constrain, &oneDayFarther)) { + zonedRelativeTo.calendar(), sign, TemporalOverflow::Constrain, + &oneDayFarther)) { return false; } MOZ_ASSERT(IsValidISODateTime(oneDayFarther.dateTime)); MOZ_ASSERT(IsValidEpochInstant(oneDayFarther.instant)); - // Step 21.b. + // Step 21.e. (Inlined NormalizedTimeDurationFromEpochNanosecondsDifference) dayLengthNs = oneDayFarther.instant - relativeResult.instant; MOZ_ASSERT(IsValidInstantSpan(dayLengthNs)); // clang-format off // - // First iteration: - // - // ns = endNs - relativeResult.instant - // dayLengthNs = oneDayFarther.instant - relativeResult.instant - // diff = ns - dayLengthNs - // = (endNs - relativeResult.instant) - (oneDayFarther.instant - relativeResult.instant) - // = endNs - relativeResult.instant - oneDayFarther.instant + relativeResult.instant - // = endNs - oneDayFarther.instant - // - // Second iteration: - // - // ns = diff' + // ns = oneDayLess' // = endNs - oneDayFarther.instant' // relativeResult.instant = oneDayFarther.instant' // dayLengthNs = oneDayFarther.instant - relativeResult.instant // = oneDayFarther.instant - oneDayFarther.instant' - // diff = ns - dayLengthNs - // = (endNs - oneDayFarther.instant') - (oneDayFarther.instant - oneDayFarther.instant') - // = endNs - oneDayFarther.instant' - oneDayFarther.instant + oneDayFarther.instant' - // = endNs - oneDayFarther.instant - // - // Where |diff'| and |oneDayFarther.instant'| denote the variables from the - // previous iteration. + // oneDayLess = ns - dayLengthNs + // = (endNs - oneDayFarther.instant') - (oneDayFarther.instant - oneDayFarther.instant') + // = endNs - oneDayFarther.instant' - oneDayFarther.instant + oneDayFarther.instant' + // = endNs - oneDayFarther.instant // - // This repeats for all following iterations. + // Where |oneDayLess'| and |oneDayFarther.instant'| denote the variables + // from before this if-statement block. // // |endNs| and |oneDayFarther.instant| are both valid epoch instant values, - // so the difference is a valid epoch instant difference value, too. + // so the difference |oneDayLess| is a valid epoch instant difference value. // // clang-format on - // Step 21.c. - auto diff = ns - dayLengthNs; - MOZ_ASSERT(IsValidInstantSpan(diff)); - MOZ_ASSERT(diff == (endNs - oneDayFarther.instant)); - - if (diff == InstantSpan{} || ((diff < InstantSpan{}) == (sign < 0))) { - // Step 21.c.i. - ns = diff; - - // Step 21.c.ii. - relativeResult = oneDayFarther; - - // Step 21.c.iii. - days += sign; - } else { - // Step 21.d. - break; + // Step 21.f. + auto oneDayLess = ns - dayLengthNs; + if (oneDayLess == InstantSpan{} || + ((oneDayLess < InstantSpan{}) == (sign < 0))) { + JS_ReportErrorNumberASCII( + cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_ZONED_DATE_TIME_INCONSISTENT_INSTANT); + return false; } } @@ -1075,15 +1092,6 @@ static bool NanosecondsToDays( MOZ_ASSERT(IsValidInstantSpan(dayLengthNs)); MOZ_ASSERT(IsValidInstantSpan(ns)); - // FIXME: spec issue - rewrite steps 24-25 as: - // - // If sign = -1, then - // If nanoseconds > 0, throw a RangeError. - // Else, - // Assert: nanoseconds ≥ 0. - // - // https://github.com/tc39/proposal-temporal/issues/2530 - // Steps 24-25. if (sign < 0) { if (ns > InstantSpan{}) { @@ -1096,39 +1104,57 @@ static bool NanosecondsToDays( MOZ_ASSERT(ns >= InstantSpan{}); } - // Step 26. - MOZ_ASSERT(ns.abs() < dayLengthNs.abs()); + // Steps 26-27. + dayLengthNs = dayLengthNs.abs(); + MOZ_ASSERT(ns.abs() < dayLengthNs); - // Step 27. - result.set(NanosecondsAndDays::from(days, ns, dayLengthNs.abs())); + // Step 28. + constexpr auto maxDayLength = Int128{1} << 53; + auto dayLengthNanos = dayLengthNs.toNanoseconds(); + if (dayLengthNanos >= maxDayLength) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_ZONED_DATE_TIME_INCORRECT_SIGN, + "days"); + return false; + } + + auto timeNanos = ns.toNanoseconds(); + MOZ_ASSERT(timeNanos == Int128{int64_t(timeNanos)}, + "abs(ns) < dayLengthNs < 2**53 implies that |ns| fits in int64"); + + // Step 29. + static_assert(std::numeric_limits<decltype(days)>::max() <= + ((int64_t(1) << 53) / (24 * 60 * 60))); + + // Step 30. + *result = {int64_t(days), int64_t(timeNanos), int64_t(dayLengthNanos)}; return true; } /** - * NanosecondsToDays ( nanoseconds, zonedRelativeTo, timeZoneRec [ , + * NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , * precalculatedPlainDateTime ] ) */ -bool js::temporal::NanosecondsToDays(JSContext* cx, - const InstantSpan& nanoseconds, - Handle<ZonedDateTime> zonedRelativeTo, - Handle<TimeZoneRecord> timeZone, - MutableHandle<NanosecondsAndDays> result) { - return ::NanosecondsToDays(cx, nanoseconds, zonedRelativeTo, timeZone, - mozilla::Nothing(), result); +bool js::temporal::NormalizedTimeDurationToDays( + JSContext* cx, const NormalizedTimeDuration& duration, + Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone, + NormalizedTimeAndDays* result) { + return ::NormalizedTimeDurationToDays(cx, duration, zonedRelativeTo, timeZone, + mozilla::Nothing(), result); } /** - * NanosecondsToDays ( nanoseconds, zonedRelativeTo, timeZoneRec [ , + * NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ , * precalculatedPlainDateTime ] ) */ -bool js::temporal::NanosecondsToDays( - JSContext* cx, const InstantSpan& nanoseconds, +bool js::temporal::NormalizedTimeDurationToDays( + JSContext* cx, const NormalizedTimeDuration& duration, Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone, const PlainDateTime& precalculatedPlainDateTime, - MutableHandle<NanosecondsAndDays> result) { - return ::NanosecondsToDays(cx, nanoseconds, zonedRelativeTo, timeZone, - mozilla::SomeRef(precalculatedPlainDateTime), - result); + NormalizedTimeAndDays* result) { + return ::NormalizedTimeDurationToDays( + cx, duration, zonedRelativeTo, timeZone, + mozilla::SomeRef(precalculatedPlainDateTime), result); } /** @@ -1139,27 +1165,22 @@ static bool DifferenceZonedDateTime( JSContext* cx, const Instant& ns1, const Instant& ns2, Handle<TimeZoneRecord> timeZone, Handle<CalendarRecord> calendar, TemporalUnit largestUnit, Handle<PlainObject*> maybeOptions, - mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime, - Duration* result) { + const PlainDateTime& precalculatedPlainDateTime, + NormalizedDuration* result) { MOZ_ASSERT(IsValidEpochInstant(ns1)); MOZ_ASSERT(IsValidEpochInstant(ns2)); // Steps 1. if (ns1 == ns2) { - *result = {}; + *result = CreateNormalizedDurationRecord({}, {}); return true; } + // FIXME: spec issue - precalculatedPlainDateTime is never undefined + // https://github.com/tc39/proposal-temporal/issues/2822 + // Steps 2-3. - PlainDateTime startDateTime; - if (!precalculatedPlainDateTime) { - // Steps 2.a-b. - if (!GetPlainDateTimeFor(cx, timeZone, ns1, &startDateTime)) { - return false; - } - } else { - startDateTime = *precalculatedPlainDateTime; - } + const auto& startDateTime = precalculatedPlainDateTime; // Steps 4-5. PlainDateTime endDateTime; @@ -1168,65 +1189,100 @@ static bool DifferenceZonedDateTime( } // Step 6. - Duration dateDifference; - if (maybeOptions) { - if (!DifferenceISODateTime(cx, startDateTime, endDateTime, calendar, - largestUnit, maybeOptions, &dateDifference)) { - return false; - } - } else { - if (!DifferenceISODateTime(cx, startDateTime, endDateTime, calendar, - largestUnit, &dateDifference)) { - return false; - } - } + int32_t sign = (ns2 - ns1 < InstantSpan{}) ? -1 : 1; // Step 7. - Instant intermediateNs; - if (!AddZonedDateTime(cx, ns1, timeZone, calendar, - { - dateDifference.years, - dateDifference.months, - dateDifference.weeks, - }, - startDateTime, &intermediateNs)) { - return false; - } - MOZ_ASSERT(IsValidEpochInstant(intermediateNs)); + int32_t maxDayCorrection = 1 + (sign > 0); // Step 8. - auto timeRemainder = ns2 - intermediateNs; - MOZ_ASSERT(IsValidInstantSpan(timeRemainder)); + int32_t dayCorrection = 0; // Step 9. - Rooted<ZonedDateTime> intermediate( - cx, - ZonedDateTime{intermediateNs, timeZone.receiver(), calendar.receiver()}); + auto timeDuration = DifferenceTime(startDateTime.time, endDateTime.time); // Step 10. - Rooted<NanosecondsAndDays> nanosAndDays(cx); - if (!NanosecondsToDays(cx, timeRemainder, intermediate, timeZone, - &nanosAndDays)) { - return false; + if (NormalizedTimeDurationSign(timeDuration) == -sign) { + dayCorrection += 1; } - // Step 11. - TimeDuration timeDifference; - if (!BalanceTimeDuration(cx, nanosAndDays.nanoseconds(), TemporalUnit::Hour, - &timeDifference)) { - return false; + // Steps 11-12. + Rooted<PlainDateTimeWithCalendar> intermediateDateTime(cx); + while (dayCorrection <= maxDayCorrection) { + // Step 12.a. + auto intermediateDate = + BalanceISODate(endDateTime.date.year, endDateTime.date.month, + endDateTime.date.day - dayCorrection * sign); + + // FIXME: spec issue - CreateTemporalDateTime is fallible + // https://github.com/tc39/proposal-temporal/issues/2824 + + // Step 12.b. + if (!CreateTemporalDateTime(cx, {intermediateDate, startDateTime.time}, + calendar.receiver(), &intermediateDateTime)) { + return false; + } + + // Steps 12.c-d. + Instant intermediateInstant; + if (!GetInstantFor(cx, timeZone, intermediateDateTime, + TemporalDisambiguation::Compatible, + &intermediateInstant)) { + return false; + } + + // Step 12.e. + auto norm = NormalizedTimeDurationFromEpochNanosecondsDifference( + ns2, intermediateInstant); + + // Step 12.f. + int32_t timeSign = NormalizedTimeDurationSign(norm); + + // Step 12.g. + if (sign != -timeSign) { + // Step 13.a. + const auto& date1 = startDateTime.date; + MOZ_ASSERT(ISODateTimeWithinLimits(date1)); + + // Step 13.b. + const auto& date2 = intermediateDate; + MOZ_ASSERT(ISODateTimeWithinLimits(date2)); + + // Step 13.c. + auto dateLargestUnit = std::min(largestUnit, TemporalUnit::Day); + + // Steps 13.d-e. + // + // The spec performs an unnecessary copy operation. As an optimization, we + // omit this copy. + auto untilOptions = maybeOptions; + + // Step 13.f. + DateDuration dateDifference; + if (untilOptions) { + if (!DifferenceDate(cx, calendar, date1, date2, dateLargestUnit, + untilOptions, &dateDifference)) { + return false; + } + } else { + if (!DifferenceDate(cx, calendar, date1, date2, dateLargestUnit, + &dateDifference)) { + return false; + } + } + + // Step 13.g. + return CreateNormalizedDurationRecord(cx, dateDifference, norm, result); + } + + // Step 12.h. + dayCorrection += 1; } - // Step 12. - *result = { - dateDifference.years, dateDifference.months, - dateDifference.weeks, nanosAndDays.daysNumber(), - timeDifference.hours, timeDifference.minutes, - timeDifference.seconds, timeDifference.milliseconds, - timeDifference.microseconds, timeDifference.nanoseconds, - }; - MOZ_ASSERT(IsValidDuration(*result)); - return true; + // Steps 14-15. + JS_ReportErrorNumberASCII( + cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_ZONED_DATE_TIME_INCONSISTENT_INSTANT); + return false; } /** @@ -1237,10 +1293,10 @@ bool js::temporal::DifferenceZonedDateTime( JSContext* cx, const Instant& ns1, const Instant& ns2, Handle<TimeZoneRecord> timeZone, Handle<CalendarRecord> calendar, TemporalUnit largestUnit, const PlainDateTime& precalculatedPlainDateTime, - Duration* result) { - return ::DifferenceZonedDateTime( - cx, ns1, ns2, timeZone, calendar, largestUnit, nullptr, - mozilla::SomeRef(precalculatedPlainDateTime), result); + NormalizedDuration* result) { + return ::DifferenceZonedDateTime(cx, ns1, ns2, timeZone, calendar, + largestUnit, nullptr, + precalculatedPlainDateTime, result); } /** @@ -1287,46 +1343,6 @@ static bool TimeZoneEqualsOrThrow(JSContext* cx, Handle<TimeZoneValue> one, } /** - * RoundISODateTime ( year, month, day, hour, minute, second, millisecond, - * microsecond, nanosecond, increment, unit, roundingMode [ , dayLength ] ) - */ -static bool RoundISODateTime(JSContext* cx, const PlainDateTime& dateTime, - Increment increment, TemporalUnit unit, - TemporalRoundingMode roundingMode, - const InstantSpan& dayLength, - PlainDateTime* result) { - MOZ_ASSERT(IsValidInstantSpan(dayLength)); - MOZ_ASSERT(dayLength > (InstantSpan{})); - - const auto& [date, time] = dateTime; - - // Step 1. - MOZ_ASSERT(IsValidISODateTime(dateTime)); - MOZ_ASSERT(ISODateTimeWithinLimits(dateTime)); - - // Step 2. (Not applicable in our implementation.) - - // Step 3. - auto roundedTime = RoundTime(time, increment, unit, roundingMode, dayLength); - - // |dayLength| can be as small as 1, so the number of rounded days can be as - // large as the number of nanoseconds in |time|. - MOZ_ASSERT(0 <= roundedTime.days && - roundedTime.days < ToNanoseconds(TemporalUnit::Day)); - - // Step 4. - PlainDate balanceResult; - if (!BalanceISODate(cx, date.year, date.month, - int64_t(date.day) + roundedTime.days, &balanceResult)) { - return false; - } - - // Step 5. - *result = {balanceResult, roundedTime.time}; - return true; -} - -/** * DifferenceTemporalZonedDateTime ( operation, zonedDateTime, other, options ) */ static bool DifferenceTemporalZonedDateTime(JSContext* cx, @@ -1385,20 +1401,24 @@ static bool DifferenceTemporalZonedDateTime(JSContext* cx, MOZ_ASSERT(settings.smallestUnit >= settings.largestUnit); // Step 6.a. - Duration difference; - if (!DifferenceInstant(cx, zonedDateTime.instant(), other.instant(), - settings.roundingIncrement, settings.smallestUnit, - settings.largestUnit, settings.roundingMode, - &difference)) { + auto difference = DifferenceInstant( + zonedDateTime.instant(), other.instant(), settings.roundingIncrement, + settings.smallestUnit, settings.roundingMode); + + // Step 6.b. + TimeDuration balancedTime; + if (!BalanceTimeDuration(cx, difference, settings.largestUnit, + &balancedTime)) { return false; } - // Step 6.b. + // Step 6.c. + auto duration = balancedTime.toDuration(); if (operation == TemporalDifference::Since) { - difference = difference.negate(); + duration = duration.negate(); } - auto* result = CreateTemporalDuration(cx, difference); + auto* result = CreateTemporalDuration(cx, duration); if (!result) { return false; } @@ -1407,15 +1427,12 @@ static bool DifferenceTemporalZonedDateTime(JSContext* cx, return true; } - // FIXME: spec issue - move this step next to the calendar validation? - // https://github.com/tc39/proposal-temporal/issues/2533 - - // Step 7. + // Steps 7-8. if (!TimeZoneEqualsOrThrow(cx, zonedDateTime.timeZone(), other.timeZone())) { return false; } - // Step 8. + // Step 9. if (zonedDateTime.instant() == other.instant()) { auto* obj = CreateTemporalDuration(cx, {}); if (!obj) { @@ -1426,7 +1443,7 @@ static bool DifferenceTemporalZonedDateTime(JSContext* cx, return true; } - // Step 9. + // Step 10. Rooted<TimeZoneRecord> timeZone(cx); if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(), { @@ -1437,7 +1454,7 @@ static bool DifferenceTemporalZonedDateTime(JSContext* cx, return false; } - // Step 10. + // Step 11. Rooted<CalendarRecord> calendar(cx); if (!CreateCalendarMethodsRecord(cx, zonedDateTime.calendar(), { @@ -1448,14 +1465,14 @@ static bool DifferenceTemporalZonedDateTime(JSContext* cx, return false; } - // Steps 11-12. + // Steps 12-13. PlainDateTime precalculatedPlainDateTime; if (!GetPlainDateTimeFor(cx, timeZone, zonedDateTime.instant(), &precalculatedPlainDateTime)) { return false; } - // Step 13. + // Step 14. Rooted<PlainDateObject*> plainRelativeTo( cx, CreateTemporalDate(cx, precalculatedPlainDateTime.date, calendar.receiver())); @@ -1463,23 +1480,12 @@ static bool DifferenceTemporalZonedDateTime(JSContext* cx, return false; } - // Step 14. - if (resolvedOptions) { - Rooted<Value> largestUnitValue( - cx, StringValue(TemporalUnitToString(cx, settings.largestUnit))); - if (!DefineDataProperty(cx, resolvedOptions, cx->names().largestUnit, - largestUnitValue)) { - return false; - } - } - // Step 15. - Duration difference; - if (!::DifferenceZonedDateTime( - cx, zonedDateTime.instant(), other.instant(), timeZone, calendar, - settings.largestUnit, resolvedOptions, - mozilla::SomeRef<const PlainDateTime>(precalculatedPlainDateTime), - &difference)) { + NormalizedDuration difference; + if (!::DifferenceZonedDateTime(cx, zonedDateTime.instant(), other.instant(), + timeZone, calendar, settings.largestUnit, + resolvedOptions, precalculatedPlainDateTime, + &difference)) { return false; } @@ -1489,59 +1495,76 @@ static bool DifferenceTemporalZonedDateTime(JSContext* cx, settings.roundingIncrement == Increment{1}; // Step 17. - if (roundingGranularityIsNoop) { - if (operation == TemporalDifference::Since) { - difference = difference.negate(); + if (!roundingGranularityIsNoop) { + // Steps 17.a-b. + NormalizedDuration roundResult; + if (!RoundDuration(cx, difference, settings.roundingIncrement, + settings.smallestUnit, settings.roundingMode, + plainRelativeTo, calendar, zonedDateTime, timeZone, + precalculatedPlainDateTime, &roundResult)) { + return false; } - auto* obj = CreateTemporalDuration(cx, difference); - if (!obj) { + // Step 17.c. + NormalizedTimeAndDays timeAndDays; + if (!NormalizedTimeDurationToDays(cx, roundResult.time, zonedDateTime, + timeZone, &timeAndDays)) { return false; } - args.rval().setObject(*obj); - return true; - } + // Step 17.d. + int64_t days = roundResult.date.days + timeAndDays.days; + + // Step 17.e. + auto toAdjust = NormalizedDuration{ + { + roundResult.date.years, + roundResult.date.months, + roundResult.date.weeks, + days, + }, + NormalizedTimeDuration::fromNanoseconds(timeAndDays.time), + }; + NormalizedDuration adjustResult; + if (!AdjustRoundedDurationDays(cx, toAdjust, settings.roundingIncrement, + settings.smallestUnit, settings.roundingMode, + zonedDateTime, calendar, timeZone, + precalculatedPlainDateTime, &adjustResult)) { + return false; + } - // Steps 18-19. - Duration roundResult; - if (!RoundDuration(cx, difference, settings.roundingIncrement, - settings.smallestUnit, settings.roundingMode, - plainRelativeTo, calendar, zonedDateTime, timeZone, - precalculatedPlainDateTime, &roundResult)) { - return false; - } + // Step 17.f. + DateDuration balanceResult; + if (!temporal::BalanceDateDurationRelative( + cx, adjustResult.date, settings.largestUnit, settings.smallestUnit, + plainRelativeTo, calendar, &balanceResult)) { + return false; + } - // Step 20. - Duration adjustResult; - if (!AdjustRoundedDurationDays(cx, roundResult, settings.roundingIncrement, - settings.smallestUnit, settings.roundingMode, - zonedDateTime, calendar, timeZone, - precalculatedPlainDateTime, &adjustResult)) { - return false; + // Step 17.g. + if (!CombineDateAndNormalizedTimeDuration(cx, balanceResult, + adjustResult.time, &difference)) { + return false; + } } - // Step 21. - DateDuration balanceResult; - if (!temporal::BalanceDateDurationRelative( - cx, adjustResult.date(), settings.largestUnit, settings.smallestUnit, - plainRelativeTo, calendar, &balanceResult)) { - return false; - } + // Step 18. + auto timeDuration = BalanceTimeDuration(difference.time, TemporalUnit::Hour); - // Step 22. - auto result = Duration{ - balanceResult.years, balanceResult.months, - balanceResult.weeks, balanceResult.days, - adjustResult.hours, adjustResult.minutes, - adjustResult.seconds, adjustResult.milliseconds, - adjustResult.microseconds, adjustResult.nanoseconds, + // Step 19. + auto duration = Duration{ + double(difference.date.years), double(difference.date.months), + double(difference.date.weeks), double(difference.date.days), + double(timeDuration.hours), double(timeDuration.minutes), + double(timeDuration.seconds), double(timeDuration.milliseconds), + timeDuration.microseconds, timeDuration.nanoseconds, }; if (operation == TemporalDifference::Since) { - result = result.negate(); + duration = duration.negate(); } + MOZ_ASSERT(IsValidDuration(duration)); - auto* obj = CreateTemporalDuration(cx, result); + auto* obj = CreateTemporalDuration(cx, duration); if (!obj) { return false; } @@ -1607,15 +1630,17 @@ static bool AddDurationToOrSubtractDurationFromZonedDateTime( if (operation == ZonedDateTimeDuration::Subtract) { duration = duration.negate(); } + auto normalized = CreateNormalizedDurationRecord(duration); + // Step 7. Instant resultInstant; if (!::AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar, - duration, options, &resultInstant)) { + normalized, options, &resultInstant)) { return false; } MOZ_ASSERT(IsValidEpochInstant(resultInstant)); - // Step 7. + // Step 8. auto* result = CreateTemporalZonedDateTime( cx, resultInstant, timeZone.receiver(), calendar.receiver()); if (!result) { @@ -1764,8 +1789,8 @@ static bool ZonedDateTime_compare(JSContext* cx, unsigned argc, Value* vp) { } // Step 3. - auto oneNs = one.instant(); - auto twoNs = two.instant(); + const auto& oneNs = one.instant(); + const auto& twoNs = two.instant(); args.rval().setInt32(oneNs > twoNs ? 1 : oneNs < twoNs ? -1 : 0); return true; } @@ -2359,7 +2384,7 @@ static bool ZonedDateTime_hoursInDay(JSContext* cx, const CallArgs& args) { } // Step 4. - auto instant = zonedDateTime.instant(); + const auto& instant = zonedDateTime.instant(); // Step 5. PlainDateTime temporalDateTime; @@ -2402,20 +2427,12 @@ static bool ZonedDateTime_hoursInDay(JSContext* cx, const CallArgs& args) { } // Step 14. - auto diffNs = tomorrowInstant - todayInstant; - MOZ_ASSERT(IsValidInstantSpan(diffNs)); + auto diff = tomorrowInstant - todayInstant; + MOZ_ASSERT(IsValidInstantSpan(diff)); // Step 15. - constexpr int32_t secPerHour = 60 * 60; - constexpr int64_t nsPerSec = ToNanoseconds(TemporalUnit::Second); - constexpr double nsPerHour = ToNanoseconds(TemporalUnit::Hour); - - int64_t hours = diffNs.seconds / secPerHour; - int64_t seconds = diffNs.seconds % secPerHour; - int64_t nanoseconds = seconds * nsPerSec + diffNs.nanoseconds; - - double result = double(hours) + double(nanoseconds) / nsPerHour; - args.rval().setNumber(result); + constexpr auto nsPerHour = Int128{ToNanoseconds(TemporalUnit::Hour)}; + args.rval().setNumber(FractionToDouble(diff.toNanoseconds(), nsPerHour)); return true; } @@ -2587,7 +2604,7 @@ static bool ZonedDateTime_offsetNanoseconds(JSContext* cx, auto timeZone = zonedDateTime.timeZone(); // Step 4. - auto instant = zonedDateTime.instant(); + const auto& instant = zonedDateTime.instant(); // Step 5. int64_t offsetNanoseconds; @@ -2622,7 +2639,7 @@ static bool ZonedDateTime_offset(JSContext* cx, const CallArgs& args) { auto timeZone = zonedDateTime.timeZone(); // Step 4. - auto instant = zonedDateTime.instant(); + const auto& instant = zonedDateTime.instant(); // Step 5. JSString* str = GetOffsetStringFor(cx, timeZone, instant); @@ -2658,13 +2675,11 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) { if (!temporalZonedDateTimeLike) { return false; } - - // Step 4. - if (!RejectTemporalLikeObject(cx, temporalZonedDateTimeLike)) { + if (!ThrowIfTemporalLikeObject(cx, temporalZonedDateTimeLike)) { return false; } - // Step 5. + // Step 4. Rooted<PlainObject*> resolvedOptions(cx); if (args.hasDefined(1)) { Rooted<JSObject*> options(cx, @@ -2680,7 +2695,7 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) { return false; } - // Step 6. + // Step 5. Rooted<CalendarRecord> calendar(cx); if (!CreateCalendarMethodsRecord(cx, zonedDateTime.calendar(), { @@ -2692,7 +2707,7 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) { return false; } - // Step 7. + // Step 6. Rooted<TimeZoneRecord> timeZone(cx); if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(), { @@ -2703,16 +2718,16 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) { return false; } - // Step 8. - auto instant = zonedDateTime.instant(); + // Step 7. + const auto& instant = zonedDateTime.instant(); - // Step 9. + // Step 8. int64_t offsetNanoseconds; if (!GetOffsetNanosecondsFor(cx, timeZone, instant, &offsetNanoseconds)) { return false; } - // Step 10. + // Step 9. Rooted<PlainDateTimeObject*> dateTime( cx, GetPlainDateTimeFor(cx, instant, calendar.receiver(), offsetNanoseconds)); @@ -2720,7 +2735,7 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) { return false; } - // Step 11. + // Step 10. JS::RootedVector<PropertyKey> fieldNames(cx); if (!CalendarFields(cx, calendar, {CalendarField::Day, CalendarField::Month, @@ -2729,14 +2744,14 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) { return false; } - // Step 12. + // Step 11. Rooted<PlainObject*> fields(cx, PrepareTemporalFields(cx, dateTime, fieldNames)); if (!fields) { return false; } - // Steps 13-18. + // Steps 12-17. struct TimeField { using FieldName = ImmutableTenuredPtr<PropertyName*> JSAtomState::*; @@ -2761,7 +2776,7 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) { } } - // Step 19. + // Step 18. JSString* fieldsOffset = FormatUTCOffsetNanoseconds(cx, offsetNanoseconds); if (!fieldsOffset) { return false; @@ -2772,7 +2787,7 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) { return false; } - // Step 20. + // Step 19. if (!AppendSorted(cx, fieldNames.get(), { TemporalField::Hour, @@ -2786,7 +2801,7 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) { return false; } - // Step 21. + // Step 20. Rooted<PlainObject*> partialZonedDateTime( cx, PreparePartialTemporalFields(cx, temporalZonedDateTimeLike, fieldNames)); @@ -2794,56 +2809,56 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) { return false; } - // Step 22. + // Step 21. Rooted<JSObject*> mergedFields( cx, CalendarMergeFields(cx, calendar, fields, partialZonedDateTime)); if (!mergedFields) { return false; } - // Step 23. + // Step 22. fields = PrepareTemporalFields(cx, mergedFields, fieldNames, {TemporalField::Offset}); if (!fields) { return false; } - // Step 24-25. + // Step 23-24. auto disambiguation = TemporalDisambiguation::Compatible; if (!ToTemporalDisambiguation(cx, resolvedOptions, &disambiguation)) { return false; } - // Step 26. + // Step 25. auto offset = TemporalOffset::Prefer; if (!ToTemporalOffset(cx, resolvedOptions, &offset)) { return false; } - // Step 27. + // Step 26. PlainDateTime dateTimeResult; if (!InterpretTemporalDateTimeFields(cx, calendar, fields, resolvedOptions, &dateTimeResult)) { return false; } - // Step 28. + // Step 27. Rooted<Value> offsetString(cx); if (!GetProperty(cx, fields, fields, cx->names().offset, &offsetString)) { return false; } - // Step 29. + // Step 28. MOZ_ASSERT(offsetString.isString()); - // Step 30. + // Step 29. Rooted<JSString*> offsetStr(cx, offsetString.toString()); int64_t newOffsetNanoseconds; if (!ParseDateTimeUTCOffset(cx, offsetStr, &newOffsetNanoseconds)) { return false; } - // Step 31. + // Step 30. Instant epochNanoseconds; if (!InterpretISODateTimeOffset( cx, dateTimeResult, OffsetBehaviour::Option, newOffsetNanoseconds, @@ -2852,7 +2867,7 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) { return false; } - // Step 32. + // Step 31. auto* result = CreateTemporalZonedDateTime( cx, epochNanoseconds, timeZone.receiver(), calendar.receiver()); if (!result) { @@ -2880,7 +2895,7 @@ static bool ZonedDateTime_withPlainTime(JSContext* cx, const CallArgs& args) { Rooted<ZonedDateTime> zonedDateTime( cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); - // Steps 3-4. + // Step 3. (Inlined ToTemporalTimeOrMidnight) PlainTime time = {}; if (args.hasDefined(0)) { if (!ToTemporalTime(cx, args[0], &time)) { @@ -2888,7 +2903,7 @@ static bool ZonedDateTime_withPlainTime(JSContext* cx, const CallArgs& args) { } } - // Step 5. + // Step 4. Rooted<TimeZoneRecord> timeZone(cx); if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(), { @@ -2899,31 +2914,31 @@ static bool ZonedDateTime_withPlainTime(JSContext* cx, const CallArgs& args) { return false; } - // Steps 6 and 8. + // Steps 5 and 7. PlainDateTime plainDateTime; if (!GetPlainDateTimeFor(cx, timeZone, zonedDateTime.instant(), &plainDateTime)) { return false; } - // Step 7. + // Step 6. auto calendar = zonedDateTime.calendar(); - // Step 9. + // Step 8. Rooted<PlainDateTimeWithCalendar> resultPlainDateTime(cx); if (!CreateTemporalDateTime(cx, {plainDateTime.date, time}, calendar, &resultPlainDateTime)) { return false; } - // Step 10. + // Step 9. Instant instant; if (!GetInstantFor(cx, timeZone, resultPlainDateTime, TemporalDisambiguation::Compatible, &instant)) { return false; } - // Step 11. + // Step 10. auto* result = CreateTemporalZonedDateTime(cx, instant, timeZone.receiver(), calendar); if (!result) { @@ -3273,57 +3288,101 @@ static bool ZonedDateTime_round(JSContext* cx, const CallArgs& args) { GetPlainDateTimeFor(zonedDateTime.instant(), offsetNanoseconds); // Step 19. - Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601)); - Rooted<PlainDateTimeWithCalendar> dtStart(cx); - if (!CreateTemporalDateTime(cx, {temporalDateTime.date, {}}, isoCalendar, - &dtStart)) { - return false; - } + Instant epochNanoseconds; + if (smallestUnit == TemporalUnit::Day) { + // Step 19.a. + Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601)); + Rooted<PlainDateTimeWithCalendar> dtStart(cx); + if (!CreateTemporalDateTime(cx, {temporalDateTime.date, {}}, isoCalendar, + &dtStart)) { + return false; + } - // Steps 20-21. - Instant startNs; - if (!GetInstantFor(cx, timeZone, dtStart, TemporalDisambiguation::Compatible, - &startNs)) { - return false; - } + // Step 19.b. + auto dateEnd = + BalanceISODate(temporalDateTime.date.year, temporalDateTime.date.month, + temporalDateTime.date.day + 1); - // Step 22. - Instant endNs; - if (!AddDaysToZonedDateTime(cx, startNs, ToPlainDateTime(dtStart), timeZone, - calendar, 1, &endNs)) { - return false; - } - MOZ_ASSERT(IsValidEpochInstant(endNs)); + // Step 19.c. + Rooted<PlainDateTimeWithCalendar> dtEnd(cx); + if (!CreateTemporalDateTime(cx, {dateEnd, {}}, isoCalendar, &dtEnd)) { + return false; + } - // Step 23. - auto dayLengthNs = endNs - startNs; - MOZ_ASSERT(IsValidInstantSpan(dayLengthNs)); + // Step 19.d. + const auto& thisNs = zonedDateTime.instant(); - // Step 24. - if (dayLengthNs <= InstantSpan{}) { - JS_ReportErrorNumberASCII( - cx, GetErrorMessage, nullptr, - JSMSG_TEMPORAL_ZONED_DATE_TIME_NON_POSITIVE_DAY_LENGTH); - return false; - } + // Steps 19.e-f. + Instant startNs; + if (!GetInstantFor(cx, timeZone, dtStart, + TemporalDisambiguation::Compatible, &startNs)) { + return false; + } - // Step 25. - PlainDateTime roundResult; - if (!RoundISODateTime(cx, temporalDateTime, roundingIncrement, smallestUnit, - roundingMode, dayLengthNs, &roundResult)) { - return false; - } + // Step 19.g. + if (thisNs < startNs) { + JS_ReportErrorNumberASCII( + cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_ZONED_DATE_TIME_INCONSISTENT_INSTANT); + return false; + } - // Step 26. - Instant epochNanoseconds; - if (!InterpretISODateTimeOffset( - cx, roundResult, OffsetBehaviour::Option, offsetNanoseconds, timeZone, - TemporalDisambiguation::Compatible, TemporalOffset::Prefer, - MatchBehaviour::MatchExactly, &epochNanoseconds)) { - return false; + // Steps 19.h-i. + Instant endNs; + if (!GetInstantFor(cx, timeZone, dtEnd, TemporalDisambiguation::Compatible, + &endNs)) { + return false; + } + + // Step 19.j. + if (thisNs >= endNs) { + JS_ReportErrorNumberASCII( + cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_ZONED_DATE_TIME_INCONSISTENT_INSTANT); + return false; + } + + // Step 19.k. + auto dayLengthNs = endNs - startNs; + MOZ_ASSERT(IsValidInstantSpan(dayLengthNs)); + MOZ_ASSERT(dayLengthNs > InstantSpan{}, "dayLengthNs is positive"); + + // Step 19.l. (Inlined NormalizedTimeDurationFromEpochNanosecondsDifference) + auto dayProgressNs = thisNs - startNs; + MOZ_ASSERT(IsValidInstantSpan(dayProgressNs)); + MOZ_ASSERT(dayProgressNs >= InstantSpan{}, "dayProgressNs is non-negative"); + + MOZ_ASSERT(startNs <= thisNs && thisNs < endNs); + MOZ_ASSERT(dayProgressNs < dayLengthNs); + + // Step 19.m. (Inlined RoundNormalizedTimeDurationToIncrement) + auto rounded = + RoundNumberToIncrement(dayProgressNs.toNanoseconds(), + dayLengthNs.toNanoseconds(), roundingMode); + auto roundedDaysNs = InstantSpan::fromNanoseconds(rounded); + MOZ_ASSERT(roundedDaysNs == InstantSpan{} || roundedDaysNs == dayLengthNs); + MOZ_ASSERT(IsValidInstantSpan(roundedDaysNs)); + + // Step 19.n. + epochNanoseconds = startNs + roundedDaysNs; + MOZ_ASSERT(epochNanoseconds == startNs || epochNanoseconds == endNs); + } else { + // Step 20.a. + auto roundResult = RoundISODateTime(temporalDateTime, roundingIncrement, + smallestUnit, roundingMode); + + // Step 20.b. + if (!InterpretISODateTimeOffset( + cx, roundResult, OffsetBehaviour::Option, offsetNanoseconds, + timeZone, TemporalDisambiguation::Compatible, + TemporalOffset::Prefer, MatchBehaviour::MatchExactly, + &epochNanoseconds)) { + return false; + } } + MOZ_ASSERT(IsValidEpochInstant(epochNanoseconds)); - // Step 27. + // Step 22. auto* result = CreateTemporalZonedDateTime(cx, epochNanoseconds, timeZone.receiver(), calendar); if (!result) { @@ -3557,7 +3616,7 @@ static bool ZonedDateTime_startOfDay(JSContext* cx, const CallArgs& args) { auto calendar = zonedDateTime.calendar(); // Step 5. - auto instant = zonedDateTime.instant(); + const auto& instant = zonedDateTime.instant(); // Steps 5-6. PlainDateTime temporalDateTime; @@ -3864,7 +3923,7 @@ static bool ZonedDateTime_getISOFields(JSContext* cx, const CallArgs& args) { Rooted<IdValueVector> fields(cx, IdValueVector(cx)); // Step 4. - auto instant = zonedDateTime.instant(); + const auto& instant = zonedDateTime.instant(); // Step 5. auto calendar = zonedDateTime.calendar(); |