summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/temporal/ZonedDateTime.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/src/builtin/temporal/ZonedDateTime.cpp1003
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();