summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/temporal/Duration.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/builtin/temporal/Duration.cpp')
-rw-r--r--js/src/builtin/temporal/Duration.cpp5034
1 files changed, 2001 insertions, 3033 deletions
diff --git a/js/src/builtin/temporal/Duration.cpp b/js/src/builtin/temporal/Duration.cpp
index 7e922aa68b..8f336a9a14 100644
--- a/js/src/builtin/temporal/Duration.cpp
+++ b/js/src/builtin/temporal/Duration.cpp
@@ -7,6 +7,7 @@
#include "builtin/temporal/Duration.h"
#include "mozilla/Assertions.h"
+#include "mozilla/Casting.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/EnumSet.h"
#include "mozilla/FloatingPoint.h"
@@ -26,6 +27,8 @@
#include "builtin/temporal/Calendar.h"
#include "builtin/temporal/Instant.h"
+#include "builtin/temporal/Int128.h"
+#include "builtin/temporal/Int96.h"
#include "builtin/temporal/PlainDate.h"
#include "builtin/temporal/PlainDateTime.h"
#include "builtin/temporal/Temporal.h"
@@ -54,7 +57,6 @@
#include "js/RootingAPI.h"
#include "js/Value.h"
#include "util/StringBuffer.h"
-#include "vm/BigIntType.h"
#include "vm/BytecodeUtil.h"
#include "vm/GlobalObject.h"
#include "vm/JSAtomState.h"
@@ -81,8 +83,8 @@ static bool IsIntegerOrInfinity(double d) {
}
static bool IsIntegerOrInfinityDuration(const Duration& duration) {
- auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
- microseconds, nanoseconds] = duration;
+ const auto& [years, months, weeks, days, hours, minutes, seconds,
+ milliseconds, microseconds, nanoseconds] = duration;
// Integers exceeding the Number range are represented as infinity.
@@ -94,8 +96,8 @@ static bool IsIntegerOrInfinityDuration(const Duration& duration) {
}
static bool IsIntegerDuration(const Duration& duration) {
- auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
- microseconds, nanoseconds] = duration;
+ const auto& [years, months, weeks, days, hours, minutes, seconds,
+ milliseconds, microseconds, nanoseconds] = duration;
return IsInteger(years) && IsInteger(months) && IsInteger(weeks) &&
IsInteger(days) && IsInteger(hours) && IsInteger(minutes) &&
@@ -104,6 +106,12 @@ static bool IsIntegerDuration(const Duration& duration) {
}
#endif
+static constexpr bool IsSafeInteger(int64_t x) {
+ constexpr int64_t MaxSafeInteger = int64_t(1) << 53;
+ constexpr int64_t MinSafeInteger = -MaxSafeInteger;
+ return MinSafeInteger < x && x < MaxSafeInteger;
+}
+
/**
* DurationSign ( years, months, weeks, days, hours, minutes, seconds,
* milliseconds, microseconds, nanoseconds )
@@ -111,8 +119,8 @@ static bool IsIntegerDuration(const Duration& duration) {
int32_t js::temporal::DurationSign(const Duration& duration) {
MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
- auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
- microseconds, nanoseconds] = duration;
+ const auto& [years, months, weeks, days, hours, minutes, seconds,
+ milliseconds, microseconds, nanoseconds] = duration;
// Step 1.
for (auto v : {years, months, weeks, days, hours, minutes, seconds,
@@ -133,14 +141,418 @@ int32_t js::temporal::DurationSign(const Duration& duration) {
}
/**
+ * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds )
+ */
+int32_t js::temporal::DurationSign(const DateDuration& duration) {
+ const auto& [years, months, weeks, days] = duration;
+
+ // Step 1.
+ for (auto v : {years, months, weeks, days}) {
+ // Step 1.a.
+ if (v < 0) {
+ return -1;
+ }
+
+ // Step 1.b.
+ if (v > 0) {
+ return 1;
+ }
+ }
+
+ // Step 2.
+ return 0;
+}
+
+/**
+ * Normalize a nanoseconds amount into a time duration.
+ */
+static NormalizedTimeDuration NormalizeNanoseconds(const Int96& nanoseconds) {
+ // Split into seconds and nanoseconds.
+ auto [seconds, nanos] = nanoseconds / ToNanoseconds(TemporalUnit::Second);
+
+ return {seconds, nanos};
+}
+
+/**
+ * Normalize a nanoseconds amount into a time duration. Return Nothing if the
+ * value is too large.
+ */
+static mozilla::Maybe<NormalizedTimeDuration> NormalizeNanoseconds(
+ double nanoseconds) {
+ MOZ_ASSERT(IsInteger(nanoseconds));
+
+ if (auto int96 = Int96::fromInteger(nanoseconds)) {
+ // The number of normalized seconds must not exceed `2**53 - 1`.
+ constexpr auto limit =
+ Int96{uint64_t(1) << 53} * ToNanoseconds(TemporalUnit::Second);
+
+ if (int96->abs() < limit) {
+ return mozilla::Some(NormalizeNanoseconds(*int96));
+ }
+ }
+ return mozilla::Nothing();
+}
+
+/**
+ * Normalize a microseconds amount into a time duration.
+ */
+static NormalizedTimeDuration NormalizeMicroseconds(const Int96& microseconds) {
+ // Split into seconds and microseconds.
+ auto [seconds, micros] = microseconds / ToMicroseconds(TemporalUnit::Second);
+
+ // Scale microseconds to nanoseconds.
+ int32_t nanos = micros * int32_t(ToNanoseconds(TemporalUnit::Microsecond));
+
+ return {seconds, nanos};
+}
+
+/**
+ * Normalize a microseconds amount into a time duration. Return Nothing if the
+ * value is too large.
+ */
+static mozilla::Maybe<NormalizedTimeDuration> NormalizeMicroseconds(
+ double microseconds) {
+ MOZ_ASSERT(IsInteger(microseconds));
+
+ if (auto int96 = Int96::fromInteger(microseconds)) {
+ // The number of normalized seconds must not exceed `2**53 - 1`.
+ constexpr auto limit =
+ Int96{uint64_t(1) << 53} * ToMicroseconds(TemporalUnit::Second);
+
+ if (int96->abs() < limit) {
+ return mozilla::Some(NormalizeMicroseconds(*int96));
+ }
+ }
+ return mozilla::Nothing();
+}
+
+/**
+ * Normalize a duration into a time duration. Return Nothing if any duration
+ * value is too large.
+ */
+static mozilla::Maybe<NormalizedTimeDuration> NormalizeSeconds(
+ const Duration& duration) {
+ do {
+ auto nanoseconds = NormalizeNanoseconds(duration.nanoseconds);
+ if (!nanoseconds) {
+ break;
+ }
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(*nanoseconds));
+
+ auto microseconds = NormalizeMicroseconds(duration.microseconds);
+ if (!microseconds) {
+ break;
+ }
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(*microseconds));
+
+ // Overflows for millis/seconds/minutes/hours/days always result in an
+ // invalid normalized time duration.
+
+ int64_t milliseconds;
+ if (!mozilla::NumberEqualsInt64(duration.milliseconds, &milliseconds)) {
+ break;
+ }
+
+ int64_t seconds;
+ if (!mozilla::NumberEqualsInt64(duration.seconds, &seconds)) {
+ break;
+ }
+
+ int64_t minutes;
+ if (!mozilla::NumberEqualsInt64(duration.minutes, &minutes)) {
+ break;
+ }
+
+ int64_t hours;
+ if (!mozilla::NumberEqualsInt64(duration.hours, &hours)) {
+ break;
+ }
+
+ int64_t days;
+ if (!mozilla::NumberEqualsInt64(duration.days, &days)) {
+ break;
+ }
+
+ // Compute the overall amount of milliseconds.
+ mozilla::CheckedInt64 millis = days;
+ millis *= 24;
+ millis += hours;
+ millis *= 60;
+ millis += minutes;
+ millis *= 60;
+ millis += seconds;
+ millis *= 1000;
+ millis += milliseconds;
+ if (!millis.isValid()) {
+ break;
+ }
+
+ auto milli = NormalizedTimeDuration::fromMilliseconds(millis.value());
+ if (!IsValidNormalizedTimeDuration(milli)) {
+ break;
+ }
+
+ // Compute the overall time duration.
+ auto result = milli + *microseconds + *nanoseconds;
+ if (!IsValidNormalizedTimeDuration(result)) {
+ break;
+ }
+
+ return mozilla::Some(result);
+ } while (false);
+
+ return mozilla::Nothing();
+}
+
+/**
+ * Normalize a days amount into a time duration. Return Nothing if the value is
+ * too large.
+ */
+static mozilla::Maybe<NormalizedTimeDuration> NormalizeDays(int64_t days) {
+ do {
+ // Compute the overall amount of milliseconds.
+ auto millis =
+ mozilla::CheckedInt64(days) * ToMilliseconds(TemporalUnit::Day);
+ if (!millis.isValid()) {
+ break;
+ }
+
+ auto result = NormalizedTimeDuration::fromMilliseconds(millis.value());
+ if (!IsValidNormalizedTimeDuration(result)) {
+ break;
+ }
+
+ return mozilla::Some(result);
+ } while (false);
+
+ return mozilla::Nothing();
+}
+
+/**
+ * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
+ * nanoseconds )
+ */
+static NormalizedTimeDuration NormalizeTimeDuration(
+ double hours, double minutes, double seconds, double milliseconds,
+ double microseconds, double nanoseconds) {
+ MOZ_ASSERT(IsInteger(hours));
+ MOZ_ASSERT(IsInteger(minutes));
+ MOZ_ASSERT(IsInteger(seconds));
+ MOZ_ASSERT(IsInteger(milliseconds));
+ MOZ_ASSERT(IsInteger(microseconds));
+ MOZ_ASSERT(IsInteger(nanoseconds));
+
+ // Steps 1-3.
+ mozilla::CheckedInt64 millis = int64_t(hours);
+ millis *= 60;
+ millis += int64_t(minutes);
+ millis *= 60;
+ millis += int64_t(seconds);
+ millis *= 1000;
+ millis += int64_t(milliseconds);
+ MOZ_ASSERT(millis.isValid());
+
+ auto normalized = NormalizedTimeDuration::fromMilliseconds(millis.value());
+
+ // Step 4.
+ auto micros = Int96::fromInteger(microseconds);
+ MOZ_ASSERT(micros);
+
+ normalized += NormalizeMicroseconds(*micros);
+
+ // Step 5.
+ auto nanos = Int96::fromInteger(nanoseconds);
+ MOZ_ASSERT(nanos);
+
+ normalized += NormalizeNanoseconds(*nanos);
+
+ // Step 6.
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized));
+
+ // Step 7.
+ return normalized;
+}
+
+/**
+ * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
+ * nanoseconds )
+ */
+NormalizedTimeDuration js::temporal::NormalizeTimeDuration(
+ int32_t hours, int32_t minutes, int32_t seconds, int32_t milliseconds,
+ int32_t microseconds, int32_t nanoseconds) {
+ // Steps 1-3.
+ mozilla::CheckedInt64 millis = int64_t(hours);
+ millis *= 60;
+ millis += int64_t(minutes);
+ millis *= 60;
+ millis += int64_t(seconds);
+ millis *= 1000;
+ millis += int64_t(milliseconds);
+ MOZ_ASSERT(millis.isValid());
+
+ auto normalized = NormalizedTimeDuration::fromMilliseconds(millis.value());
+
+ // Step 4.
+ normalized += NormalizeMicroseconds(Int96{microseconds});
+
+ // Step 5.
+ normalized += NormalizeNanoseconds(Int96{nanoseconds});
+
+ // Step 6.
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized));
+
+ // Step 7.
+ return normalized;
+}
+
+/**
+ * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
+ * nanoseconds )
+ */
+NormalizedTimeDuration js::temporal::NormalizeTimeDuration(
+ const Duration& duration) {
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ return ::NormalizeTimeDuration(duration.hours, duration.minutes,
+ duration.seconds, duration.milliseconds,
+ duration.microseconds, duration.nanoseconds);
+}
+
+/**
+ * AddNormalizedTimeDuration ( one, two )
+ */
+static bool AddNormalizedTimeDuration(JSContext* cx,
+ const NormalizedTimeDuration& one,
+ const NormalizedTimeDuration& two,
+ NormalizedTimeDuration* result) {
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(one));
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(two));
+
+ // Step 1.
+ auto sum = one + two;
+
+ // Step 2.
+ if (!IsValidNormalizedTimeDuration(sum)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
+ return false;
+ }
+
+ // Step 3.
+ *result = sum;
+ return true;
+}
+
+/**
+ * SubtractNormalizedTimeDuration ( one, two )
+ */
+static bool SubtractNormalizedTimeDuration(JSContext* cx,
+ const NormalizedTimeDuration& one,
+ const NormalizedTimeDuration& two,
+ NormalizedTimeDuration* result) {
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(one));
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(two));
+
+ // Step 1.
+ auto sum = one - two;
+
+ // Step 2.
+ if (!IsValidNormalizedTimeDuration(sum)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
+ return false;
+ }
+
+ // Step 3.
+ *result = sum;
+ return true;
+}
+
+/**
+ * Add24HourDaysToNormalizedTimeDuration ( d, days )
+ */
+bool js::temporal::Add24HourDaysToNormalizedTimeDuration(
+ JSContext* cx, const NormalizedTimeDuration& d, int64_t days,
+ NormalizedTimeDuration* result) {
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(d));
+
+ // Step 1.
+ auto normalizedDays = NormalizeDays(days);
+ if (!normalizedDays) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
+ return false;
+ }
+
+ // Step 2.
+ auto sum = d + *normalizedDays;
+ if (!IsValidNormalizedTimeDuration(sum)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
+ return false;
+ }
+
+ // Step 3.
+ *result = sum;
+ return true;
+}
+
+/**
+ * CombineDateAndNormalizedTimeDuration ( dateDurationRecord, norm )
+ */
+bool js::temporal::CombineDateAndNormalizedTimeDuration(
+ JSContext* cx, const DateDuration& date, const NormalizedTimeDuration& time,
+ NormalizedDuration* result) {
+ MOZ_ASSERT(IsValidDuration(date));
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(time));
+
+ // Step 1.
+ int32_t dateSign = DurationSign(date);
+
+ // Step 2.
+ int32_t timeSign = NormalizedTimeDurationSign(time);
+
+ // Step 3
+ if ((dateSign * timeSign) < 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DURATION_COMBINE_INVALID_SIGN);
+ return false;
+ }
+
+ // Step 4.
+ *result = {date, time};
+ return true;
+}
+
+/**
+ * NormalizedTimeDurationFromEpochNanosecondsDifference ( one, two )
+ */
+NormalizedTimeDuration
+js::temporal::NormalizedTimeDurationFromEpochNanosecondsDifference(
+ const Instant& one, const Instant& two) {
+ MOZ_ASSERT(IsValidEpochInstant(one));
+ MOZ_ASSERT(IsValidEpochInstant(two));
+
+ // Step 1.
+ auto result = one - two;
+
+ // Step 2.
+ MOZ_ASSERT(IsValidInstantSpan(result));
+
+ // Step 3.
+ return result.to<NormalizedTimeDuration>();
+}
+
+/**
* IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
* milliseconds, microseconds, nanoseconds )
*/
bool js::temporal::IsValidDuration(const Duration& duration) {
MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
- auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
- microseconds, nanoseconds] = duration;
+ const auto& [years, months, weeks, days, hours, minutes, seconds,
+ milliseconds, microseconds, nanoseconds] = duration;
// Step 1.
int32_t sign = DurationSign(duration);
@@ -165,9 +577,61 @@ bool js::temporal::IsValidDuration(const Duration& duration) {
}
// Step 3.
+ if (std::abs(years) >= double(int64_t(1) << 32)) {
+ return false;
+ }
+
+ // Step 4.
+ if (std::abs(months) >= double(int64_t(1) << 32)) {
+ return false;
+ }
+
+ // Step 5.
+ if (std::abs(weeks) >= double(int64_t(1) << 32)) {
+ return false;
+ }
+
+ // Steps 6-8.
+ if (!NormalizeSeconds(duration)) {
+ return false;
+ }
+
+ // Step 9.
return true;
}
+#ifdef DEBUG
+/**
+ * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds )
+ */
+bool js::temporal::IsValidDuration(const DateDuration& duration) {
+ return IsValidDuration(duration.toDuration());
+}
+
+/**
+ * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds )
+ */
+bool js::temporal::IsValidDuration(const NormalizedDuration& duration) {
+ return IsValidDuration(duration.date) &&
+ IsValidNormalizedTimeDuration(duration.time) &&
+ (DurationSign(duration.date) *
+ NormalizedTimeDurationSign(duration.time) >=
+ 0);
+}
+#endif
+
+static bool ThrowInvalidDurationPart(JSContext* cx, double value,
+ const char* name, unsigned errorNumber) {
+ ToCStringBuf cbuf;
+ const char* numStr = NumberToCString(&cbuf, value);
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber, name,
+ numStr);
+ return false;
+}
+
/**
* IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
* milliseconds, microseconds, nanoseconds )
@@ -176,36 +640,36 @@ bool js::temporal::ThrowIfInvalidDuration(JSContext* cx,
const Duration& duration) {
MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
- auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
- microseconds, nanoseconds] = duration;
+ const auto& [years, months, weeks, days, hours, minutes, seconds,
+ milliseconds, microseconds, nanoseconds] = duration;
// Step 1.
int32_t sign = DurationSign(duration);
- auto report = [&](double v, const char* name, unsigned errorNumber) {
- ToCStringBuf cbuf;
- const char* numStr = NumberToCString(&cbuf, v);
-
- JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber, name,
- numStr);
- };
-
auto throwIfInvalid = [&](double v, const char* name) {
// Step 2.a.
if (!std::isfinite(v)) {
- report(v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
- return false;
+ return ThrowInvalidDurationPart(
+ cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
}
// Steps 2.b-c.
if ((v < 0 && sign > 0) || (v > 0 && sign < 0)) {
- report(v, name, JSMSG_TEMPORAL_DURATION_INVALID_SIGN);
- return false;
+ return ThrowInvalidDurationPart(cx, v, name,
+ JSMSG_TEMPORAL_DURATION_INVALID_SIGN);
}
return true;
};
+ auto throwIfTooLarge = [&](double v, const char* name) {
+ if (std::abs(v) >= double(int64_t(1) << 32)) {
+ return ThrowInvalidDurationPart(
+ cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
+ }
+ return true;
+ };
+
// Step 2.
if (!throwIfInvalid(years, "years")) {
return false;
@@ -238,9 +702,104 @@ bool js::temporal::ThrowIfInvalidDuration(JSContext* cx,
return false;
}
+ // Step 3.
+ if (!throwIfTooLarge(years, "years")) {
+ return false;
+ }
+
+ // Step 4.
+ if (!throwIfTooLarge(months, "months")) {
+ return false;
+ }
+
+ // Step 5.
+ if (!throwIfTooLarge(weeks, "weeks")) {
+ return false;
+ }
+
+ // Steps 6-8.
+ if (!NormalizeSeconds(duration)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
+ return false;
+ }
+
MOZ_ASSERT(IsValidDuration(duration));
+ // Step 9.
+ return true;
+}
+
+/**
+ * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds )
+ */
+bool js::temporal::ThrowIfInvalidDuration(JSContext* cx,
+ const DateDuration& duration) {
+ const auto& [years, months, weeks, days] = duration;
+
+ // Step 1.
+ int32_t sign = DurationSign(duration);
+
+ auto throwIfInvalid = [&](int64_t v, const char* name) {
+ // Step 2.a. (Not applicable)
+
+ // Steps 2.b-c.
+ if ((v < 0 && sign > 0) || (v > 0 && sign < 0)) {
+ return ThrowInvalidDurationPart(cx, double(v), name,
+ JSMSG_TEMPORAL_DURATION_INVALID_SIGN);
+ }
+
+ return true;
+ };
+
+ auto throwIfTooLarge = [&](int64_t v, const char* name) {
+ if (std::abs(v) >= (int64_t(1) << 32)) {
+ return ThrowInvalidDurationPart(
+ cx, double(v), name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
+ }
+ return true;
+ };
+
+ // Step 2.
+ if (!throwIfInvalid(years, "years")) {
+ return false;
+ }
+ if (!throwIfInvalid(months, "months")) {
+ return false;
+ }
+ if (!throwIfInvalid(weeks, "weeks")) {
+ return false;
+ }
+ if (!throwIfInvalid(days, "days")) {
+ return false;
+ }
+
// Step 3.
+ if (!throwIfTooLarge(years, "years")) {
+ return false;
+ }
+
+ // Step 4.
+ if (!throwIfTooLarge(months, "months")) {
+ return false;
+ }
+
+ // Step 5.
+ if (!throwIfTooLarge(weeks, "weeks")) {
+ return false;
+ }
+
+ // Steps 6-8.
+ if (std::abs(days) > ((int64_t(1) << 53) / 86400)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
+ return false;
+ }
+
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ // Step 9.
return true;
}
@@ -307,8 +866,8 @@ static TemporalUnit DefaultTemporalLargestUnit(const Duration& duration) {
static DurationObject* CreateTemporalDuration(JSContext* cx,
const CallArgs& args,
const Duration& duration) {
- auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
- microseconds, nanoseconds] = duration;
+ const auto& [years, months, weeks, days, hours, minutes, seconds,
+ milliseconds, microseconds, nanoseconds] = duration;
// Step 1.
if (!ThrowIfInvalidDuration(cx, duration)) {
@@ -355,8 +914,8 @@ static DurationObject* CreateTemporalDuration(JSContext* cx,
*/
DurationObject* js::temporal::CreateTemporalDuration(JSContext* cx,
const Duration& duration) {
- auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
- microseconds, nanoseconds] = duration;
+ const auto& [years, months, weeks, days, hours, minutes, seconds,
+ milliseconds, microseconds, nanoseconds] = duration;
MOZ_ASSERT(IsInteger(years));
MOZ_ASSERT(IsInteger(months));
@@ -621,11 +1180,12 @@ int32_t js::temporal::DaysUntil(const PlainDate& earlier,
// Steps 1-2.
int32_t epochDaysEarlier = MakeDay(earlier);
- MOZ_ASSERT(std::abs(epochDaysEarlier) <= 100'000'000);
+ MOZ_ASSERT(MinEpochDay <= epochDaysEarlier &&
+ epochDaysEarlier <= MaxEpochDay);
// Steps 3-4.
int32_t epochDaysLater = MakeDay(later);
- MOZ_ASSERT(std::abs(epochDaysLater) <= 100'000'000);
+ MOZ_ASSERT(MinEpochDay <= epochDaysLater && epochDaysLater <= MaxEpochDay);
// Step 5.
return epochDaysLater - epochDaysEarlier;
@@ -636,7 +1196,7 @@ int32_t js::temporal::DaysUntil(const PlainDate& earlier,
*/
static bool MoveRelativeDate(
JSContext* cx, Handle<CalendarRecord> calendar,
- Handle<Wrapped<PlainDateObject*>> relativeTo, const Duration& duration,
+ Handle<Wrapped<PlainDateObject*>> relativeTo, const DateDuration& duration,
MutableHandle<Wrapped<PlainDateObject*>> relativeToResult,
int32_t* daysResult) {
auto* unwrappedRelativeTo = relativeTo.unwrap(cx);
@@ -655,7 +1215,7 @@ static bool MoveRelativeDate(
// Step 2.
*daysResult = DaysUntil(relativeToDate, later);
- MOZ_ASSERT(std::abs(*daysResult) <= 200'000'000);
+ MOZ_ASSERT(std::abs(*daysResult) <= MaxEpochDaysDuration);
// Step 3.
return true;
@@ -668,7 +1228,7 @@ static bool MoveRelativeDate(
static bool MoveRelativeZonedDateTime(
JSContext* cx, Handle<ZonedDateTime> zonedDateTime,
Handle<CalendarRecord> calendar, Handle<TimeZoneRecord> timeZone,
- const Duration& duration,
+ const DateDuration& duration,
mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
MutableHandle<ZonedDateTime> result) {
// Step 1.
@@ -683,13 +1243,13 @@ static bool MoveRelativeZonedDateTime(
Instant intermediateNs;
if (precalculatedPlainDateTime) {
if (!AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar,
- duration.date(), *precalculatedPlainDateTime,
+ duration, *precalculatedPlainDateTime,
&intermediateNs)) {
return false;
}
} else {
if (!AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar,
- duration.date(), &intermediateNs)) {
+ duration, &intermediateNs)) {
return false;
}
}
@@ -702,235 +1262,27 @@ static bool MoveRelativeZonedDateTime(
}
/**
- * TotalDurationNanoseconds ( hours, minutes, seconds, milliseconds,
- * microseconds, nanoseconds )
- */
-static mozilla::Maybe<int64_t> TotalDurationNanoseconds(
- const Duration& duration) {
- // Our implementation supports |duration.days| to avoid computing |days * 24|
- // in the caller, which may not be representable as a double value.
- int64_t days;
- if (!mozilla::NumberEqualsInt64(duration.days, &days)) {
- return mozilla::Nothing();
- }
- int64_t hours;
- if (!mozilla::NumberEqualsInt64(duration.hours, &hours)) {
- return mozilla::Nothing();
- }
- mozilla::CheckedInt64 result = days;
- result *= 24;
- result += hours;
-
- // Step 1.
- int64_t minutes;
- if (!mozilla::NumberEqualsInt64(duration.minutes, &minutes)) {
- return mozilla::Nothing();
- }
- result *= 60;
- result += minutes;
-
- // Step 2.
- int64_t seconds;
- if (!mozilla::NumberEqualsInt64(duration.seconds, &seconds)) {
- return mozilla::Nothing();
- }
- result *= 60;
- result += seconds;
-
- // Step 3.
- int64_t milliseconds;
- if (!mozilla::NumberEqualsInt64(duration.milliseconds, &milliseconds)) {
- return mozilla::Nothing();
- }
- result *= 1000;
- result += milliseconds;
-
- // Step 4.
- int64_t microseconds;
- if (!mozilla::NumberEqualsInt64(duration.microseconds, &microseconds)) {
- return mozilla::Nothing();
- }
- result *= 1000;
- result += microseconds;
-
- // Step 5.
- int64_t nanoseconds;
- if (!mozilla::NumberEqualsInt64(duration.nanoseconds, &nanoseconds)) {
- return mozilla::Nothing();
- }
- result *= 1000;
- result += nanoseconds;
-
- // Step 5 (Return).
- if (!result.isValid()) {
- return mozilla::Nothing();
- }
- return mozilla::Some(result.value());
-}
-
-/**
- * TotalDurationNanoseconds ( hours, minutes, seconds, milliseconds,
- * microseconds, nanoseconds )
- */
-static BigInt* TotalDurationNanosecondsSlow(JSContext* cx,
- const Duration& duration) {
- // Our implementation supports |duration.days| to avoid computing |days * 24|
- // in the caller, which may not be representable as a double value.
- Rooted<BigInt*> result(cx, BigInt::createFromDouble(cx, duration.days));
- if (!result) {
- return nullptr;
- }
-
- Rooted<BigInt*> temp(cx);
- auto multiplyAdd = [&](int32_t factor, double number) {
- temp = BigInt::createFromInt64(cx, factor);
- if (!temp) {
- return false;
- }
-
- result = BigInt::mul(cx, result, temp);
- if (!result) {
- return false;
- }
-
- temp = BigInt::createFromDouble(cx, number);
- if (!temp) {
- return false;
- }
-
- result = BigInt::add(cx, result, temp);
- return !!result;
- };
-
- if (!multiplyAdd(24, duration.hours)) {
- return nullptr;
- }
-
- // Step 1.
- if (!multiplyAdd(60, duration.minutes)) {
- return nullptr;
- }
-
- // Step 2.
- if (!multiplyAdd(60, duration.seconds)) {
- return nullptr;
- }
-
- // Step 3.
- if (!multiplyAdd(1000, duration.milliseconds)) {
- return nullptr;
- }
-
- // Step 4.
- if (!multiplyAdd(1000, duration.microseconds)) {
- return nullptr;
- }
-
- // Step 5.
- if (!multiplyAdd(1000, duration.nanoseconds)) {
- return nullptr;
- }
-
- // Step 5 (Return).
- return result;
-}
-
-struct NanosecondsAndDays final {
- int32_t days = 0;
- int64_t nanoseconds = 0;
-};
-
-/**
- * Split duration into full days and remainding nanoseconds.
- */
-static ::NanosecondsAndDays NanosecondsToDays(int64_t nanoseconds) {
- constexpr int64_t dayLengthNs = ToNanoseconds(TemporalUnit::Day);
-
- static_assert(INT64_MAX / dayLengthNs <= INT32_MAX,
- "days doesn't exceed INT32_MAX");
-
- return {int32_t(nanoseconds / dayLengthNs), nanoseconds % dayLengthNs};
-}
-
-/**
- * Split duration into full days and remainding nanoseconds.
- */
-static bool NanosecondsToDaysSlow(
- JSContext* cx, Handle<BigInt*> nanoseconds,
- MutableHandle<temporal::NanosecondsAndDays> result) {
- constexpr int64_t dayLengthNs = ToNanoseconds(TemporalUnit::Day);
-
- Rooted<BigInt*> dayLength(cx, BigInt::createFromInt64(cx, dayLengthNs));
- if (!dayLength) {
- return false;
- }
-
- Rooted<BigInt*> days(cx);
- Rooted<BigInt*> nanos(cx);
- if (!BigInt::divmod(cx, nanoseconds, dayLength, &days, &nanos)) {
- return false;
- }
-
- result.set(temporal::NanosecondsAndDays::from(
- days, ToInstantSpan(nanos), InstantSpan::fromNanoseconds(dayLengthNs)));
- return true;
-}
-
-/**
* Split duration into full days and remainding nanoseconds.
*/
-static bool NanosecondsToDays(
- JSContext* cx, const Duration& duration,
- MutableHandle<temporal::NanosecondsAndDays> result) {
- if (auto total = TotalDurationNanoseconds(duration.time())) {
- auto nanosAndDays = ::NanosecondsToDays(*total);
-
- result.set(temporal::NanosecondsAndDays::from(
- nanosAndDays.days,
- InstantSpan::fromNanoseconds(nanosAndDays.nanoseconds),
- InstantSpan::fromNanoseconds(ToNanoseconds(TemporalUnit::Day))));
- return true;
- }
+static NormalizedTimeAndDays NormalizedTimeDurationToDays(
+ const NormalizedTimeDuration& duration) {
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
- Rooted<BigInt*> nanoseconds(
- cx, TotalDurationNanosecondsSlow(cx, duration.time()));
- if (!nanoseconds) {
- return false;
+ auto [seconds, nanoseconds] = duration;
+ if (seconds < 0 && nanoseconds > 0) {
+ seconds += 1;
+ nanoseconds -= 1'000'000'000;
}
- return ::NanosecondsToDaysSlow(cx, nanoseconds, result);
-}
-
-/**
- * NanosecondsToDays ( nanoseconds, zonedRelativeTo, timeZoneRec [ ,
- * precalculatedPlainDateTime ] )
- */
-static bool NanosecondsToDays(
- JSContext* cx, const Duration& duration,
- Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone,
- MutableHandle<temporal::NanosecondsAndDays> result) {
- if (auto total = TotalDurationNanoseconds(duration.time())) {
- auto nanoseconds = InstantSpan::fromNanoseconds(*total);
- MOZ_ASSERT(IsValidInstantSpan(nanoseconds));
+ int64_t days = seconds / ToSeconds(TemporalUnit::Day);
+ seconds = seconds % ToSeconds(TemporalUnit::Day);
- return NanosecondsToDays(cx, nanoseconds, zonedRelativeTo, timeZone,
- result);
- }
+ int64_t time = seconds * ToNanoseconds(TemporalUnit::Second) + nanoseconds;
- auto* nanoseconds = TotalDurationNanosecondsSlow(cx, duration.time());
- if (!nanoseconds) {
- return false;
- }
+ constexpr int64_t dayLength = ToNanoseconds(TemporalUnit::Day);
+ MOZ_ASSERT(std::abs(time) < dayLength);
- // NanosecondsToDays, step 6.
- if (!IsValidInstantSpan(nanoseconds)) {
- JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
- JSMSG_TEMPORAL_INSTANT_INVALID);
- return false;
- }
-
- return NanosecondsToDays(cx, ToInstantSpan(nanoseconds), zonedRelativeTo,
- timeZone, result);
+ return {days, time, dayLength};
}
/**
@@ -943,14 +1295,28 @@ static TimeDuration CreateTimeDurationRecord(int64_t days, int64_t hours,
int64_t microseconds,
int64_t nanoseconds) {
// Step 1.
- MOZ_ASSERT(IsValidDuration({0, 0, 0, double(days), double(hours),
- double(minutes), double(seconds),
- double(microseconds), double(nanoseconds)}));
+ MOZ_ASSERT(IsValidDuration(
+ {0, 0, 0, double(days), double(hours), double(minutes), double(seconds),
+ double(milliseconds), double(microseconds), double(nanoseconds)}));
+
+ // All values are safe integers, so we don't need to convert to `double` and
+ // back for the `ℝ(𝔽(x))` conversion.
+ MOZ_ASSERT(IsSafeInteger(days));
+ MOZ_ASSERT(IsSafeInteger(hours));
+ MOZ_ASSERT(IsSafeInteger(minutes));
+ MOZ_ASSERT(IsSafeInteger(seconds));
+ MOZ_ASSERT(IsSafeInteger(milliseconds));
+ MOZ_ASSERT(IsSafeInteger(microseconds));
+ MOZ_ASSERT(IsSafeInteger(nanoseconds));
// Step 2.
return {
- double(days), double(hours), double(minutes),
- double(seconds), double(milliseconds), double(microseconds),
+ days,
+ hours,
+ minutes,
+ seconds,
+ milliseconds,
+ double(microseconds),
double(nanoseconds),
};
}
@@ -959,647 +1325,372 @@ static TimeDuration CreateTimeDurationRecord(int64_t days, int64_t hours,
* CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
* microseconds, nanoseconds )
*/
-static TimeDuration CreateTimeDurationRecord(double days, double hours,
- double minutes, double seconds,
- double milliseconds,
- double microseconds,
- double nanoseconds) {
+static TimeDuration CreateTimeDurationRecord(int64_t milliseconds,
+ const Int128& microseconds,
+ const Int128& nanoseconds) {
// Step 1.
- MOZ_ASSERT(IsValidDuration({0, 0, 0, days, hours, minutes, seconds,
- milliseconds, microseconds, nanoseconds}));
+ MOZ_ASSERT(IsValidDuration({0, 0, 0, 0, 0, 0, 0, double(milliseconds),
+ double(microseconds), double(nanoseconds)}));
// Step 2.
- // NB: Adds +0.0 to correctly handle negative zero.
return {
- days + (+0.0), hours + (+0.0), minutes + (+0.0),
- seconds + (+0.0), milliseconds + (+0.0), microseconds + (+0.0),
- nanoseconds + (+0.0),
+ 0, 0, 0, 0, milliseconds, double(microseconds), double(nanoseconds),
};
}
/**
- * BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds,
- * microseconds, nanoseconds, largestUnit )
- *
- * BalancePossiblyInfiniteTimeDuration ( days, hours, minutes, seconds,
- * milliseconds, microseconds, nanoseconds, largestUnit )
+ * BalanceTimeDuration ( norm, largestUnit )
*/
-static TimeDuration BalanceTimeDuration(int64_t nanoseconds,
- TemporalUnit largestUnit) {
- // Step 1. (Handled in caller.)
+TimeDuration js::temporal::BalanceTimeDuration(
+ const NormalizedTimeDuration& duration, TemporalUnit largestUnit) {
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
+ MOZ_ASSERT(largestUnit <= TemporalUnit::Second,
+ "fallible fractional seconds units");
- // Step 2.
+ auto [seconds, nanoseconds] = duration;
+
+ // Negative nanoseconds are represented as the difference to 1'000'000'000.
+ // Convert these back to their absolute value and adjust the seconds part
+ // accordingly.
+ //
+ // For example the nanoseconds duration |-1n| is represented as the
+ // duration {seconds: -1, nanoseconds: 999'999'999}.
+ if (seconds < 0 && nanoseconds > 0) {
+ seconds += 1;
+ nanoseconds -= ToNanoseconds(TemporalUnit::Second);
+ }
+
+ // Step 1.
int64_t days = 0;
int64_t hours = 0;
int64_t minutes = 0;
- int64_t seconds = 0;
int64_t milliseconds = 0;
int64_t microseconds = 0;
- // Steps 3-4. (Not applicable in our implementation.)
+ // Steps 2-3. (Not applicable in our implementation.)
//
// We don't need to convert to positive numbers, because integer division
// truncates and the %-operator has modulo semantics.
- // Steps 5-11.
+ // Steps 4-10.
switch (largestUnit) {
- // Step 5.
+ // Step 4.
case TemporalUnit::Year:
case TemporalUnit::Month:
case TemporalUnit::Week:
case TemporalUnit::Day: {
- // Step 5.a.
+ // Step 4.a.
microseconds = nanoseconds / 1000;
- // Step 5.b.
+ // Step 4.b.
nanoseconds = nanoseconds % 1000;
- // Step 5.c.
+ // Step 4.c.
milliseconds = microseconds / 1000;
- // Step 5.d.
+ // Step 4.d.
microseconds = microseconds % 1000;
- // Step 5.e.
- seconds = milliseconds / 1000;
+ // Steps 4.e-f. (Not applicable)
+ MOZ_ASSERT(std::abs(milliseconds) <= 999);
- // Step 5.f.
- milliseconds = milliseconds % 1000;
-
- // Step 5.g.
+ // Step 4.g.
minutes = seconds / 60;
- // Step 5.h.
+ // Step 4.h.
seconds = seconds % 60;
- // Step 5.i.
+ // Step 4.i.
hours = minutes / 60;
- // Step 5.j.
+ // Step 4.j.
minutes = minutes % 60;
- // Step 5.k.
+ // Step 4.k.
days = hours / 24;
- // Step 5.l.
+ // Step 4.l.
hours = hours % 24;
break;
}
+ // Step 5.
case TemporalUnit::Hour: {
- // Step 6.a.
+ // Step 5.a.
microseconds = nanoseconds / 1000;
- // Step 6.b.
+ // Step 5.b.
nanoseconds = nanoseconds % 1000;
- // Step 6.c.
+ // Step 5.c.
milliseconds = microseconds / 1000;
- // Step 6.d.
+ // Step 5.d.
microseconds = microseconds % 1000;
- // Step 6.e.
- seconds = milliseconds / 1000;
-
- // Step 6.f.
- milliseconds = milliseconds % 1000;
+ // Steps 5.e-f. (Not applicable)
+ MOZ_ASSERT(std::abs(milliseconds) <= 999);
- // Step 6.g.
+ // Step 5.g.
minutes = seconds / 60;
- // Step 6.h.
+ // Step 5.h.
seconds = seconds % 60;
- // Step 6.i.
+ // Step 5.i.
hours = minutes / 60;
- // Step 6.j.
+ // Step 5.j.
minutes = minutes % 60;
break;
}
- // Step 7.
case TemporalUnit::Minute: {
- // Step 7.a.
+ // Step 6.a.
microseconds = nanoseconds / 1000;
- // Step 7.b.
+ // Step 6.b.
nanoseconds = nanoseconds % 1000;
- // Step 7.c.
+ // Step 6.c.
milliseconds = microseconds / 1000;
- // Step 7.d.
+ // Step 6.d.
microseconds = microseconds % 1000;
- // Step 7.e.
- seconds = milliseconds / 1000;
+ // Steps 6.e-f. (Not applicable)
+ MOZ_ASSERT(std::abs(milliseconds) <= 999);
- // Step 7.f.
- milliseconds = milliseconds % 1000;
-
- // Step 7.g.
+ // Step 6.g.
minutes = seconds / 60;
- // Step 7.h.
+ // Step 6.h.
seconds = seconds % 60;
break;
}
- // Step 8.
+ // Step 7.
case TemporalUnit::Second: {
- // Step 8.a.
- microseconds = nanoseconds / 1000;
-
- // Step 8.b.
- nanoseconds = nanoseconds % 1000;
-
- // Step 8.c.
- milliseconds = microseconds / 1000;
-
- // Step 8.d.
- microseconds = microseconds % 1000;
-
- // Step 8.e.
- seconds = milliseconds / 1000;
-
- // Step 8.f.
- milliseconds = milliseconds % 1000;
-
- break;
- }
-
- // Step 9.
- case TemporalUnit::Millisecond: {
- // Step 9.a.
+ // Step 7.a.
microseconds = nanoseconds / 1000;
- // Step 9.b.
+ // Step 7.b.
nanoseconds = nanoseconds % 1000;
- // Step 9.c.
+ // Step 7.c.
milliseconds = microseconds / 1000;
- // Step 9.d.
+ // Step 7.d.
microseconds = microseconds % 1000;
- break;
- }
-
- // Step 10.
- case TemporalUnit::Microsecond: {
- // Step 10.a.
- microseconds = nanoseconds / 1000;
-
- // Step 10.b.
- nanoseconds = nanoseconds % 1000;
+ // Steps 7.e-f. (Not applicable)
+ MOZ_ASSERT(std::abs(milliseconds) <= 999);
break;
}
- // Step 11.
- case TemporalUnit::Nanosecond: {
- // Nothing to do.
- break;
- }
-
+ case TemporalUnit::Millisecond:
+ case TemporalUnit::Microsecond:
+ case TemporalUnit::Nanosecond:
case TemporalUnit::Auto:
MOZ_CRASH("Unexpected temporal unit");
}
- // Step 12. (Not applicable, all values are finite)
-
- // Step 13.
+ // Step 11.
return CreateTimeDurationRecord(days, hours, minutes, seconds, milliseconds,
microseconds, nanoseconds);
}
/**
- * BalancePossiblyInfiniteTimeDuration ( days, hours, minutes, seconds,
- * milliseconds, microseconds, nanoseconds, largestUnit )
+ * BalanceTimeDuration ( norm, largestUnit )
*/
-static bool BalancePossiblyInfiniteTimeDurationSlow(JSContext* cx,
- Handle<BigInt*> nanos,
- TemporalUnit largestUnit,
- TimeDuration* result) {
- // Steps 1-2. (Handled in caller.)
-
- BigInt* zero = BigInt::zero(cx);
- if (!zero) {
- return false;
- }
+bool js::temporal::BalanceTimeDuration(JSContext* cx,
+ const NormalizedTimeDuration& duration,
+ TemporalUnit largestUnit,
+ TimeDuration* result) {
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
- // Step 3.
- Rooted<BigInt*> days(cx, zero);
- Rooted<BigInt*> hours(cx, zero);
- Rooted<BigInt*> minutes(cx, zero);
- Rooted<BigInt*> seconds(cx, zero);
- Rooted<BigInt*> milliseconds(cx, zero);
- Rooted<BigInt*> microseconds(cx, zero);
- Rooted<BigInt*> nanoseconds(cx, nanos);
+ auto [seconds, nanoseconds] = duration;
- // Steps 4-5.
+ // Negative nanoseconds are represented as the difference to 1'000'000'000.
+ // Convert these back to their absolute value and adjust the seconds part
+ // accordingly.
//
- // We don't need to convert to positive numbers, because BigInt division
- // truncates and BigInt modulo has modulo semantics.
-
- // Steps 6-12.
- Rooted<BigInt*> thousand(cx, BigInt::createFromInt64(cx, 1000));
- if (!thousand) {
- return false;
+ // For example the nanoseconds duration |-1n| is represented as the
+ // duration {seconds: -1, nanoseconds: 999'999'999}.
+ if (seconds < 0 && nanoseconds > 0) {
+ seconds += 1;
+ nanoseconds -= ToNanoseconds(TemporalUnit::Second);
}
- Rooted<BigInt*> sixty(cx, BigInt::createFromInt64(cx, 60));
- if (!sixty) {
- return false;
- }
-
- Rooted<BigInt*> twentyfour(cx, BigInt::createFromInt64(cx, 24));
- if (!twentyfour) {
- return false;
- }
+ // Steps 1-3. (Not applicable in our implementation.)
+ //
+ // We don't need to convert to positive numbers, because integer division
+ // truncates and the %-operator has modulo semantics.
+ // Steps 4-10.
switch (largestUnit) {
- // Step 6.
+ // Steps 4-7.
case TemporalUnit::Year:
case TemporalUnit::Month:
case TemporalUnit::Week:
- case TemporalUnit::Day: {
- // Steps 6.a-b.
- if (!BigInt::divmod(cx, nanoseconds, thousand, &microseconds,
- &nanoseconds)) {
- return false;
- }
-
- // Steps 6.c-d.
- if (!BigInt::divmod(cx, microseconds, thousand, &milliseconds,
- &microseconds)) {
- return false;
- }
-
- // Steps 6.e-f.
- if (!BigInt::divmod(cx, milliseconds, thousand, &seconds,
- &milliseconds)) {
- return false;
- }
-
- // Steps 6.g-h.
- if (!BigInt::divmod(cx, seconds, sixty, &minutes, &seconds)) {
- return false;
- }
-
- // Steps 6.i-j.
- if (!BigInt::divmod(cx, minutes, sixty, &hours, &minutes)) {
- return false;
- }
-
- // Steps 6.k-l.
- if (!BigInt::divmod(cx, hours, twentyfour, &days, &hours)) {
- return false;
- }
-
- break;
- }
+ case TemporalUnit::Day:
+ case TemporalUnit::Hour:
+ case TemporalUnit::Minute:
+ case TemporalUnit::Second:
+ *result = BalanceTimeDuration(duration, largestUnit);
+ return true;
- // Step 7.
- case TemporalUnit::Hour: {
- // Steps 7.a-b.
- if (!BigInt::divmod(cx, nanoseconds, thousand, &microseconds,
- &nanoseconds)) {
- return false;
- }
+ // Step 8.
+ case TemporalUnit::Millisecond: {
+ // The number of normalized seconds must not exceed `2**53 - 1`.
+ constexpr auto limit =
+ (int64_t(1) << 53) * ToMilliseconds(TemporalUnit::Second);
- // Steps 7.c-d.
- if (!BigInt::divmod(cx, microseconds, thousand, &milliseconds,
- &microseconds)) {
- return false;
- }
+ // The largest possible milliseconds value whose double representation
+ // doesn't exceed the normalized seconds limit.
+ constexpr auto max = int64_t(0x7cff'ffff'ffff'fdff);
- // Steps 7.e-f.
- if (!BigInt::divmod(cx, milliseconds, thousand, &seconds,
- &milliseconds)) {
- return false;
- }
+ // Assert |max| is the maximum allowed milliseconds value.
+ static_assert(double(max) < double(limit));
+ static_assert(double(max + 1) >= double(limit));
- // Steps 7.g-h.
- if (!BigInt::divmod(cx, seconds, sixty, &minutes, &seconds)) {
- return false;
- }
+ static_assert((NormalizedTimeDuration::max().seconds + 1) *
+ ToMilliseconds(TemporalUnit::Second) <=
+ INT64_MAX,
+ "total number duration milliseconds fits into int64");
- // Steps 7.i-j.
- if (!BigInt::divmod(cx, minutes, sixty, &hours, &minutes)) {
- return false;
- }
-
- break;
- }
+ // Step 8.a.
+ int64_t microseconds = nanoseconds / 1000;
- // Step 8.
- case TemporalUnit::Minute: {
- // Steps 8.a-b.
- if (!BigInt::divmod(cx, nanoseconds, thousand, &microseconds,
- &nanoseconds)) {
- return false;
- }
+ // Step 8.b.
+ nanoseconds = nanoseconds % 1000;
- // Steps 8.c-d.
- if (!BigInt::divmod(cx, microseconds, thousand, &milliseconds,
- &microseconds)) {
- return false;
- }
+ // Step 8.c.
+ int64_t milliseconds = microseconds / 1000;
+ MOZ_ASSERT(std::abs(milliseconds) <= 999);
- // Steps 8.e-f.
- if (!BigInt::divmod(cx, milliseconds, thousand, &seconds,
- &milliseconds)) {
- return false;
- }
+ // Step 8.d.
+ microseconds = microseconds % 1000;
- // Steps 8.g-h.
- if (!BigInt::divmod(cx, seconds, sixty, &minutes, &seconds)) {
+ auto millis =
+ (seconds * ToMilliseconds(TemporalUnit::Second)) + milliseconds;
+ if (std::abs(millis) > max) {
+ JS_ReportErrorNumberASCII(
+ cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
return false;
}
- break;
+ // Step 11.
+ *result = CreateTimeDurationRecord(millis, Int128{microseconds},
+ Int128{nanoseconds});
+ return true;
}
// Step 9.
- case TemporalUnit::Second: {
- // Steps 9.a-b.
- if (!BigInt::divmod(cx, nanoseconds, thousand, &microseconds,
- &nanoseconds)) {
- return false;
- }
+ case TemporalUnit::Microsecond: {
+ // The number of normalized seconds must not exceed `2**53 - 1`.
+ constexpr auto limit = Uint128{int64_t(1) << 53} *
+ Uint128{ToMicroseconds(TemporalUnit::Second)};
- // Steps 9.c-d.
- if (!BigInt::divmod(cx, microseconds, thousand, &milliseconds,
- &microseconds)) {
- return false;
- }
+ // The largest possible microseconds value whose double representation
+ // doesn't exceed the normalized seconds limit.
+ constexpr auto max =
+ (Uint128{0x1e8} << 64) + Uint128{0x47ff'ffff'fff7'ffff};
+ static_assert(max < limit);
- // Steps 9.e-f.
- if (!BigInt::divmod(cx, milliseconds, thousand, &seconds,
- &milliseconds)) {
- return false;
- }
+ // Assert |max| is the maximum allowed microseconds value.
+ MOZ_ASSERT(double(max) < double(limit));
+ MOZ_ASSERT(double(max + Uint128{1}) >= double(limit));
- break;
- }
+ // Step 9.a.
+ int64_t microseconds = nanoseconds / 1000;
+ MOZ_ASSERT(std::abs(microseconds) <= 999'999);
- // Step 10.
- case TemporalUnit::Millisecond: {
- // Steps 10.a-b.
- if (!BigInt::divmod(cx, nanoseconds, thousand, &microseconds,
- &nanoseconds)) {
- return false;
- }
+ // Step 9.b.
+ nanoseconds = nanoseconds % 1000;
- // Steps 10.c-d.
- if (!BigInt::divmod(cx, microseconds, thousand, &milliseconds,
- &microseconds)) {
+ auto micros =
+ (Int128{seconds} * Int128{ToMicroseconds(TemporalUnit::Second)}) +
+ Int128{microseconds};
+ if (micros.abs() > max) {
+ JS_ReportErrorNumberASCII(
+ cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
return false;
}
- break;
+ // Step 11.
+ *result = CreateTimeDurationRecord(0, micros, Int128{nanoseconds});
+ return true;
}
- // Step 11.
- case TemporalUnit::Microsecond: {
- // Steps 11.a-b.
- if (!BigInt::divmod(cx, nanoseconds, thousand, &microseconds,
- &nanoseconds)) {
+ // Step 10.
+ case TemporalUnit::Nanosecond: {
+ // The number of normalized seconds must not exceed `2**53 - 1`.
+ constexpr auto limit = Uint128{int64_t(1) << 53} *
+ Uint128{ToNanoseconds(TemporalUnit::Second)};
+
+ // The largest possible nanoseconds value whose double representation
+ // doesn't exceed the normalized seconds limit.
+ constexpr auto max =
+ (Uint128{0x77359} << 64) + Uint128{0x3fff'ffff'dfff'ffff};
+ static_assert(max < limit);
+
+ // Assert |max| is the maximum allowed nanoseconds value.
+ MOZ_ASSERT(double(max) < double(limit));
+ MOZ_ASSERT(double(max + Uint128{1}) >= double(limit));
+
+ MOZ_ASSERT(std::abs(nanoseconds) <= 999'999'999);
+
+ auto nanos =
+ (Int128{seconds} * Int128{ToNanoseconds(TemporalUnit::Second)}) +
+ Int128{nanoseconds};
+ if (nanos.abs() > max) {
+ JS_ReportErrorNumberASCII(
+ cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
return false;
}
- break;
- }
-
- // Step 12.
- case TemporalUnit::Nanosecond: {
- // Nothing to do.
- break;
- }
-
- case TemporalUnit::Auto:
- MOZ_CRASH("Unexpected temporal unit");
- }
-
- double daysNumber = BigInt::numberValue(days);
- double hoursNumber = BigInt::numberValue(hours);
- double minutesNumber = BigInt::numberValue(minutes);
- double secondsNumber = BigInt::numberValue(seconds);
- double millisecondsNumber = BigInt::numberValue(milliseconds);
- double microsecondsNumber = BigInt::numberValue(microseconds);
- double nanosecondsNumber = BigInt::numberValue(nanoseconds);
-
- // Step 13.
- for (double v : {daysNumber, hoursNumber, minutesNumber, secondsNumber,
- millisecondsNumber, microsecondsNumber, nanosecondsNumber}) {
- if (std::isinf(v)) {
- *result = {
- daysNumber, hoursNumber, minutesNumber,
- secondsNumber, millisecondsNumber, microsecondsNumber,
- nanosecondsNumber,
- };
+ // Step 11.
+ *result = CreateTimeDurationRecord(0, Int128{}, nanos);
return true;
}
- }
-
- // Step 14.
- *result = CreateTimeDurationRecord(daysNumber, hoursNumber, minutesNumber,
- secondsNumber, millisecondsNumber,
- microsecondsNumber, nanosecondsNumber);
- return true;
-}
-
-/**
- * BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds,
- * microseconds, nanoseconds, largestUnit )
- */
-static bool BalanceTimeDurationSlow(JSContext* cx, Handle<BigInt*> nanoseconds,
- TemporalUnit largestUnit,
- TimeDuration* result) {
- // Step 1.
- if (!BalancePossiblyInfiniteTimeDurationSlow(cx, nanoseconds, largestUnit,
- result)) {
- return false;
- }
-
- // Steps 2-3.
- return ThrowIfInvalidDuration(cx, result->toDuration());
-}
-
-/**
- * BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds,
- * microseconds, nanoseconds, largestUnit )
- */
-static bool BalanceTimeDuration(JSContext* cx, const Duration& one,
- const Duration& two, TemporalUnit largestUnit,
- TimeDuration* result) {
- MOZ_ASSERT(IsValidDuration(one));
- MOZ_ASSERT(IsValidDuration(two));
- MOZ_ASSERT(largestUnit >= TemporalUnit::Day);
-
- // Fast-path when we can perform the whole computation with int64 values.
- if (auto oneNanoseconds = TotalDurationNanoseconds(one)) {
- if (auto twoNanoseconds = TotalDurationNanoseconds(two)) {
- mozilla::CheckedInt64 nanoseconds = *oneNanoseconds;
- nanoseconds += *twoNanoseconds;
- if (nanoseconds.isValid()) {
- *result = ::BalanceTimeDuration(nanoseconds.value(), largestUnit);
- return true;
- }
- }
- }
-
- Rooted<BigInt*> oneNanoseconds(cx, TotalDurationNanosecondsSlow(cx, one));
- if (!oneNanoseconds) {
- return false;
- }
-
- Rooted<BigInt*> twoNanoseconds(cx, TotalDurationNanosecondsSlow(cx, two));
- if (!twoNanoseconds) {
- return false;
- }
-
- Rooted<BigInt*> nanoseconds(cx,
- BigInt::add(cx, oneNanoseconds, twoNanoseconds));
- if (!nanoseconds) {
- return false;
- }
-
- return BalanceTimeDurationSlow(cx, nanoseconds, largestUnit, result);
-}
-
-/**
- * BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds,
- * microseconds, nanoseconds, largestUnit )
- */
-static bool BalanceTimeDuration(JSContext* cx, double days, const Duration& one,
- const Duration& two, TemporalUnit largestUnit,
- TimeDuration* result) {
- MOZ_ASSERT(IsInteger(days));
- MOZ_ASSERT(IsValidDuration(one));
- MOZ_ASSERT(IsValidDuration(two));
-
- // Fast-path when we can perform the whole computation with int64 values.
- if (auto oneNanoseconds = TotalDurationNanoseconds(one)) {
- if (auto twoNanoseconds = TotalDurationNanoseconds(two)) {
- int64_t intDays;
- if (mozilla::NumberEqualsInt64(days, &intDays)) {
- mozilla::CheckedInt64 daysNanoseconds = intDays;
- daysNanoseconds *= ToNanoseconds(TemporalUnit::Day);
-
- mozilla::CheckedInt64 nanoseconds = *oneNanoseconds;
- nanoseconds += *twoNanoseconds;
- nanoseconds += daysNanoseconds;
-
- if (nanoseconds.isValid()) {
- *result = ::BalanceTimeDuration(nanoseconds.value(), largestUnit);
- return true;
- }
- }
- }
- }
-
- Rooted<BigInt*> oneNanoseconds(cx, TotalDurationNanosecondsSlow(cx, one));
- if (!oneNanoseconds) {
- return false;
- }
-
- Rooted<BigInt*> twoNanoseconds(cx, TotalDurationNanosecondsSlow(cx, two));
- if (!twoNanoseconds) {
- return false;
- }
-
- Rooted<BigInt*> nanoseconds(cx,
- BigInt::add(cx, oneNanoseconds, twoNanoseconds));
- if (!nanoseconds) {
- return false;
- }
-
- if (days) {
- Rooted<BigInt*> daysNanoseconds(
- cx, TotalDurationNanosecondsSlow(cx, {0, 0, 0, days}));
- if (!daysNanoseconds) {
- return false;
- }
-
- nanoseconds = BigInt::add(cx, nanoseconds, daysNanoseconds);
- if (!nanoseconds) {
- return false;
- }
- }
-
- return BalanceTimeDurationSlow(cx, nanoseconds, largestUnit, result);
-}
-/**
- * BalancePossiblyInfiniteTimeDuration ( days, hours, minutes, seconds,
- * milliseconds, microseconds, nanoseconds, largestUnit )
- */
-static bool BalancePossiblyInfiniteTimeDuration(JSContext* cx,
- const Duration& duration,
- TemporalUnit largestUnit,
- TimeDuration* result) {
- // NB: |duration.days| can have a different sign than the time components.
- MOZ_ASSERT(IsValidDuration(duration.time()));
-
- // Fast-path when we can perform the whole computation with int64 values.
- if (auto nanoseconds = TotalDurationNanoseconds(duration)) {
- *result = ::BalanceTimeDuration(*nanoseconds, largestUnit);
- return true;
- }
-
- // Steps 1-2.
- Rooted<BigInt*> nanoseconds(cx, TotalDurationNanosecondsSlow(cx, duration));
- if (!nanoseconds) {
- return false;
- }
-
- // Steps 3-14.
- return ::BalancePossiblyInfiniteTimeDurationSlow(cx, nanoseconds, largestUnit,
- result);
-}
-
-/**
- * BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds,
- * microseconds, nanoseconds, largestUnit )
- */
-bool js::temporal::BalanceTimeDuration(JSContext* cx, const Duration& duration,
- TemporalUnit largestUnit,
- TimeDuration* result) {
- if (!::BalancePossiblyInfiniteTimeDuration(cx, duration, largestUnit,
- result)) {
- return false;
+ case TemporalUnit::Auto:
+ break;
}
- return ThrowIfInvalidDuration(cx, result->toDuration());
+ MOZ_CRASH("Unexpected temporal unit");
}
/**
- * BalancePossiblyInfiniteTimeDurationRelative ( days, hours, minutes, seconds,
- * milliseconds, microseconds, nanoseconds, largestUnit, zonedRelativeTo,
- * timeZoneRec [ , precalculatedPlainDateTime ] )
+ * BalanceTimeDurationRelative ( days, norm, largestUnit, zonedRelativeTo,
+ * timeZoneRec, precalculatedPlainDateTime )
*/
-static bool BalancePossiblyInfiniteTimeDurationRelative(
- JSContext* cx, const Duration& duration, TemporalUnit largestUnit,
+static bool BalanceTimeDurationRelative(
+ JSContext* cx, const NormalizedDuration& duration, TemporalUnit largestUnit,
Handle<ZonedDateTime> relativeTo, Handle<TimeZoneRecord> timeZone,
mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
TimeDuration* result) {
- // Step 1. (Not applicable)
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ // Step 1.
+ const auto& startNs = relativeTo.instant();
// Step 2.
- auto intermediateNs = relativeTo.instant();
+ const auto& startInstant = startNs;
// Step 3.
- const auto& startInstant = relativeTo.instant();
+ auto intermediateNs = startNs;
// Step 4.
PlainDateTime startDateTime;
- if (duration.days != 0) {
+ if (duration.date.days != 0) {
// Step 4.a.
if (!precalculatedPlainDateTime) {
if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &startDateTime)) {
@@ -1612,7 +1703,7 @@ static bool BalancePossiblyInfiniteTimeDurationRelative(
// Steps 4.b-c.
Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601));
if (!AddDaysToZonedDateTime(cx, startInstant, *precalculatedPlainDateTime,
- timeZone, isoCalendar, duration.days,
+ timeZone, isoCalendar, duration.date.days,
&intermediateNs)) {
return false;
}
@@ -1620,23 +1711,23 @@ static bool BalancePossiblyInfiniteTimeDurationRelative(
// Step 5.
Instant endNs;
- if (!AddInstant(cx, intermediateNs, duration.time(), &endNs)) {
+ if (!AddInstant(cx, intermediateNs, duration.time, &endNs)) {
return false;
}
MOZ_ASSERT(IsValidEpochInstant(endNs));
// Step 6.
- auto nanoseconds = endNs - relativeTo.instant();
- MOZ_ASSERT(IsValidInstantSpan(nanoseconds));
+ auto normalized =
+ NormalizedTimeDurationFromEpochNanosecondsDifference(endNs, startInstant);
// Step 7.
- if (nanoseconds == InstantSpan{}) {
+ if (normalized == NormalizedTimeDuration{}) {
*result = {};
return true;
}
// Steps 8-9.
- double days = 0;
+ int64_t days = 0;
if (TemporalUnit::Year <= largestUnit && largestUnit <= TemporalUnit::Day) {
// Step 8.a.
if (!precalculatedPlainDateTime) {
@@ -1648,61 +1739,32 @@ static bool BalancePossiblyInfiniteTimeDurationRelative(
}
// Step 8.b.
- Rooted<temporal::NanosecondsAndDays> nanosAndDays(cx);
- if (!NanosecondsToDays(cx, nanoseconds, relativeTo, timeZone,
- *precalculatedPlainDateTime, &nanosAndDays)) {
+ NormalizedTimeAndDays timeAndDays;
+ if (!NormalizedTimeDurationToDays(cx, normalized, relativeTo, timeZone,
+ *precalculatedPlainDateTime,
+ &timeAndDays)) {
return false;
}
- // NB: |days| is passed to CreateTimeDurationRecord, which performs
- // |ℝ(𝔽(days))|, so it's safe to convert from BigInt to double here.
-
// Step 8.c.
- days = nanosAndDays.daysNumber();
- MOZ_ASSERT(IsInteger(days));
-
- // FIXME: spec issue - `result.[[Nanoseconds]]` not created in all branches
- // https://github.com/tc39/proposal-temporal/issues/2616
+ days = timeAndDays.days;
// Step 8.d.
- nanoseconds = nanosAndDays.nanoseconds();
- MOZ_ASSERT_IF(days > 0, nanoseconds >= InstantSpan{});
- MOZ_ASSERT_IF(days < 0, nanoseconds <= InstantSpan{});
+ normalized = NormalizedTimeDuration::fromNanoseconds(timeAndDays.time);
+ MOZ_ASSERT_IF(days > 0, normalized >= NormalizedTimeDuration{});
+ MOZ_ASSERT_IF(days < 0, normalized <= NormalizedTimeDuration{});
// Step 8.e.
largestUnit = TemporalUnit::Hour;
}
- // Step 10. (Not applicable in our implementation.)
-
- // Steps 11-12.
+ // Step 10.
TimeDuration balanceResult;
- if (auto nanos = nanoseconds.toNanoseconds(); nanos.isValid()) {
- // Step 11.
- balanceResult = ::BalanceTimeDuration(nanos.value(), largestUnit);
-
- // Step 12.
- MOZ_ASSERT(IsValidDuration(balanceResult.toDuration()));
- } else {
- Rooted<BigInt*> ns(cx, ToEpochNanoseconds(cx, nanoseconds));
- if (!ns) {
- return false;
- }
-
- // Step 11.
- if (!::BalancePossiblyInfiniteTimeDurationSlow(cx, ns, largestUnit,
- &balanceResult)) {
- return false;
- }
-
- // Step 12.
- if (!IsValidDuration(balanceResult.toDuration())) {
- *result = balanceResult;
- return true;
- }
+ if (!BalanceTimeDuration(cx, normalized, largestUnit, &balanceResult)) {
+ return false;
}
- // Step 13.
+ // Step 11.
*result = {
days,
balanceResult.hours,
@@ -1712,99 +1774,44 @@ static bool BalancePossiblyInfiniteTimeDurationRelative(
balanceResult.microseconds,
balanceResult.nanoseconds,
};
+ MOZ_ASSERT(IsValidDuration(result->toDuration()));
return true;
}
/**
- * BalancePossiblyInfiniteTimeDurationRelative ( days, hours, minutes, seconds,
- * milliseconds, microseconds, nanoseconds, largestUnit, zonedRelativeTo,
- * timeZoneRec [ , precalculatedPlainDateTime ] )
- */
-static bool BalancePossiblyInfiniteTimeDurationRelative(
- JSContext* cx, const Duration& duration, TemporalUnit largestUnit,
- Handle<ZonedDateTime> relativeTo, Handle<TimeZoneRecord> timeZone,
- TimeDuration* result) {
- return BalancePossiblyInfiniteTimeDurationRelative(
- cx, duration, largestUnit, relativeTo, timeZone, mozilla::Nothing(),
- result);
-}
-
-/**
- * BalanceTimeDurationRelative ( days, hours, minutes, seconds, milliseconds,
- * microseconds, nanoseconds, largestUnit, zonedRelativeTo, timeZoneRec,
- * precalculatedPlainDateTime )
- */
-static bool BalanceTimeDurationRelative(
- JSContext* cx, const Duration& duration, TemporalUnit largestUnit,
- Handle<ZonedDateTime> relativeTo, Handle<TimeZoneRecord> timeZone,
- mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
- TimeDuration* result) {
- // Step 1.
- if (!BalancePossiblyInfiniteTimeDurationRelative(
- cx, duration, largestUnit, relativeTo, timeZone,
- precalculatedPlainDateTime, result)) {
- return false;
- }
-
- // Steps 2-3.
- return ThrowIfInvalidDuration(cx, result->toDuration());
-}
-
-/**
- * BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds,
- * microseconds, nanoseconds, largestUnit )
- */
-bool js::temporal::BalanceTimeDuration(JSContext* cx,
- const InstantSpan& nanoseconds,
- TemporalUnit largestUnit,
- TimeDuration* result) {
- MOZ_ASSERT(IsValidInstantSpan(nanoseconds));
-
- // Steps 1-3. (Not applicable)
-
- // Fast-path when we can perform the whole computation with int64 values.
- if (auto nanos = nanoseconds.toNanoseconds(); nanos.isValid()) {
- *result = ::BalanceTimeDuration(nanos.value(), largestUnit);
- return true;
- }
-
- Rooted<BigInt*> nanos(cx, ToEpochNanoseconds(cx, nanoseconds));
- if (!nanos) {
- return false;
- }
-
- // Steps 4-16.
- return ::BalanceTimeDurationSlow(cx, nanos, largestUnit, result);
-}
-
-/**
* CreateDateDurationRecord ( years, months, weeks, days )
*/
-static DateDuration CreateDateDurationRecord(double years, double months,
- double weeks, double days) {
- MOZ_ASSERT(IsValidDuration({years, months, weeks, days}));
+static DateDuration CreateDateDurationRecord(int64_t years, int64_t months,
+ int64_t weeks, int64_t days) {
+ MOZ_ASSERT(IsValidDuration(Duration{
+ double(years),
+ double(months),
+ double(weeks),
+ double(days),
+ }));
return {years, months, weeks, days};
}
/**
* CreateDateDurationRecord ( years, months, weeks, days )
*/
-static bool CreateDateDurationRecord(JSContext* cx, double years, double months,
- double weeks, double days,
- DateDuration* result) {
- if (!ThrowIfInvalidDuration(cx, {years, months, weeks, days})) {
+static bool CreateDateDurationRecord(JSContext* cx, int64_t years,
+ int64_t months, int64_t weeks,
+ int64_t days, DateDuration* result) {
+ auto duration = DateDuration{years, months, weeks, days};
+ if (!ThrowIfInvalidDuration(cx, duration)) {
return false;
}
- *result = {years, months, weeks, days};
+ *result = duration;
return true;
}
-static bool UnbalanceDateDurationRelativeHasEffect(const Duration& duration,
+static bool UnbalanceDateDurationRelativeHasEffect(const DateDuration& duration,
TemporalUnit largestUnit) {
MOZ_ASSERT(largestUnit != TemporalUnit::Auto);
- // Steps 2, 3.a-b, 4.a-b, 6-7.
+ // Steps 2-4.
return (largestUnit > TemporalUnit::Year && duration.years != 0) ||
(largestUnit > TemporalUnit::Month && duration.months != 0) ||
(largestUnit > TemporalUnit::Week && duration.weeks != 0);
@@ -1815,84 +1822,67 @@ static bool UnbalanceDateDurationRelativeHasEffect(const Duration& duration,
* plainRelativeTo, calendarRec )
*/
static bool UnbalanceDateDurationRelative(
- JSContext* cx, const Duration& duration, TemporalUnit largestUnit,
+ JSContext* cx, const DateDuration& duration, TemporalUnit largestUnit,
Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
Handle<CalendarRecord> calendar, DateDuration* result) {
MOZ_ASSERT(IsValidDuration(duration));
- double years = duration.years;
- double months = duration.months;
- double weeks = duration.weeks;
- double days = duration.days;
+ auto [years, months, weeks, days] = duration;
// Step 1. (Not applicable in our implementation.)
- // Steps 2, 3.a, 4.a, and 6.
+ // Steps 2-4.
if (!UnbalanceDateDurationRelativeHasEffect(duration, largestUnit)) {
- // Steps 2.a, 3.a, 4.a, and 6.
- *result = CreateDateDurationRecord(years, months, weeks, days);
+ *result = duration;
return true;
}
- // Step 3.
- if (largestUnit == TemporalUnit::Month) {
- // Step 3.a. (Handled above)
- MOZ_ASSERT(years != 0);
+ // Step 5.
+ MOZ_ASSERT(largestUnit != TemporalUnit::Year);
- // Step 3.b. (Not applicable in our implementation.)
+ // Step 6. (Not applicable in our implementation.)
- // Step 3.c.
- MOZ_ASSERT(
- CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
+ // Step 7.
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
- // Step 3.d.
+ // Step 8.
+ if (largestUnit == TemporalUnit::Month) {
+ // Step 8.a.
MOZ_ASSERT(
CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
- // Step 3.e.
- auto yearsDuration = Duration{years};
+ // Step 8.b.
+ auto yearsDuration = DateDuration{years};
- // Step 3.f.
+ // Step 8.c.
Rooted<Wrapped<PlainDateObject*>> later(
cx, CalendarDateAdd(cx, calendar, plainRelativeTo, yearsDuration));
if (!later) {
return false;
}
- // Steps 3.g-i.
- Duration untilResult;
+ // Steps 8.d-f.
+ DateDuration untilResult;
if (!CalendarDateUntil(cx, calendar, plainRelativeTo, later,
TemporalUnit::Month, &untilResult)) {
return false;
}
- // Step 3.j.
- double yearsInMonths = untilResult.months;
+ // Step 8.g.
+ int64_t yearsInMonths = untilResult.months;
- // Step 3.k.
- //
- // The addition |months + yearsInMonths| can be imprecise, but this is
- // safe to ignore, because all values are passed to
- // CreateDateDurationRecord, which converts the values to Numbers.
+ // Step 8.h.
return CreateDateDurationRecord(cx, 0, months + yearsInMonths, weeks, days,
result);
}
- // Step 4.
+ // Step 9.
if (largestUnit == TemporalUnit::Week) {
- // Step 4.a. (Handled above)
- MOZ_ASSERT(years != 0 || months != 0);
-
- // Step 4.b. (Not applicable in our implementation.)
-
- // Step 4.c.
- MOZ_ASSERT(
- CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
-
- // Step 4.d.
- auto yearsMonthsDuration = Duration{years, months};
+ // Step 9.a.
+ auto yearsMonthsDuration = DateDuration{years, months};
- // Step 4.e.
+ // Step 9.b.
auto later =
CalendarDateAdd(cx, calendar, plainRelativeTo, yearsMonthsDuration);
if (!later) {
@@ -1906,35 +1896,20 @@ static bool UnbalanceDateDurationRelative(
}
auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
- // Step 4.f.
+ // Step 9.c.
int32_t yearsMonthsInDays = DaysUntil(relativeToDate, laterDate);
- // Step 4.g.
- //
- // The addition |days + yearsMonthsInDays| can be imprecise, but this is
- // safe to ignore, because all values are passed to
- // CreateDateDurationRecord, which converts the values to Numbers.
+ // Step 9.d.
return CreateDateDurationRecord(cx, 0, 0, weeks, days + yearsMonthsInDays,
result);
}
- // Step 5. (Not applicable in our implementation.)
-
- // Step 6. (Handled above)
- MOZ_ASSERT(years != 0 || months != 0 || weeks != 0);
-
- // FIXME: why don't we unconditionally throw an error for missing calendars?
-
- // Step 7. (Not applicable in our implementation.)
-
- // Step 8.
- MOZ_ASSERT(
- CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
+ // Step 10. (Not applicable in our implementation.)
- // Step 9.
- auto yearsMonthsWeeksDuration = Duration{years, months, weeks};
+ // Step 11.
+ auto yearsMonthsWeeksDuration = DateDuration{years, months, weeks};
- // Step 10.
+ // Step 12.
auto later =
CalendarDateAdd(cx, calendar, plainRelativeTo, yearsMonthsWeeksDuration);
if (!later) {
@@ -1948,14 +1923,10 @@ static bool UnbalanceDateDurationRelative(
}
auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
- // Step 11.
+ // Step 13.
int32_t yearsMonthsWeeksInDay = DaysUntil(relativeToDate, laterDate);
- // Step 12.
- //
- // The addition |days + yearsMonthsWeeksInDay| can be imprecise, but this is
- // safe to ignore, because all values are passed to CreateDateDurationRecord,
- // which converts the values to Numbers.
+ // Step 14.
return CreateDateDurationRecord(cx, 0, 0, 0, days + yearsMonthsWeeksInDay,
result);
}
@@ -1965,28 +1936,23 @@ static bool UnbalanceDateDurationRelative(
* plainRelativeTo, calendarRec )
*/
static bool UnbalanceDateDurationRelative(JSContext* cx,
- const Duration& duration,
+ const DateDuration& duration,
TemporalUnit largestUnit,
DateDuration* result) {
MOZ_ASSERT(IsValidDuration(duration));
- double years = duration.years;
- double months = duration.months;
- double weeks = duration.weeks;
- double days = duration.days;
-
// Step 1. (Not applicable.)
- // Steps 2, 3.a, 4.a, and 6.
+ // Step 2-4.
if (!UnbalanceDateDurationRelativeHasEffect(duration, largestUnit)) {
- // Steps 2.a, 3.a, 4.a, and 6.
- *result = CreateDateDurationRecord(years, months, weeks, days);
+ *result = duration;
return true;
}
- // Step 5. (Not applicable.)
+ // Step 5.
+ MOZ_ASSERT(largestUnit != TemporalUnit::Year);
- // Steps 3.b, 4.b, and 7.
+ // Steps 6.
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_DURATION_UNCOMPARABLE, "calendar");
return false;
@@ -1997,17 +1963,14 @@ static bool UnbalanceDateDurationRelative(JSContext* cx,
* smallestUnit, plainRelativeTo, calendarRec )
*/
static bool BalanceDateDurationRelative(
- JSContext* cx, const Duration& duration, TemporalUnit largestUnit,
+ JSContext* cx, const DateDuration& duration, TemporalUnit largestUnit,
TemporalUnit smallestUnit,
Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
Handle<CalendarRecord> calendar, DateDuration* result) {
MOZ_ASSERT(IsValidDuration(duration));
MOZ_ASSERT(largestUnit <= smallestUnit);
- double years = duration.years;
- double months = duration.months;
- double weeks = duration.weeks;
- double days = duration.days;
+ auto [years, months, weeks, days] = duration;
// FIXME: spec issue - effectful code paths should be more fine-grained
// similar to UnbalanceDateDurationRelative. For example:
@@ -2023,7 +1986,7 @@ static bool BalanceDateDurationRelative(
if (largestUnit > TemporalUnit::Week ||
(years == 0 && months == 0 && weeks == 0 && days == 0)) {
// Step 4.a.
- *result = CreateDateDurationRecord(years, months, weeks, days);
+ *result = duration;
return true;
}
@@ -2045,7 +2008,8 @@ static bool BalanceDateDurationRelative(
// Steps 8-9. (Not applicable in our implementation.)
- auto untilAddedDate = [&](const Duration& duration, Duration* untilResult) {
+ auto untilAddedDate = [&](const DateDuration& duration,
+ DateDuration* untilResult) {
Rooted<Wrapped<PlainDateObject*>> later(
cx, AddDate(cx, calendar, plainRelativeTo, duration));
if (!later) {
@@ -2064,16 +2028,14 @@ static bool BalanceDateDurationRelative(
MOZ_ASSERT(days == 0);
// Step 10.a.ii.
- auto yearsMonthsDuration = Duration{years, months};
+ auto yearsMonthsDuration = DateDuration{years, months};
// Steps 10.a.iii-iv.
- Duration untilResult;
+ DateDuration untilResult;
if (!untilAddedDate(yearsMonthsDuration, &untilResult)) {
return false;
}
- // FIXME: spec bug - CreateDateDurationRecord is infallible
-
// Step 10.a.v.
*result = CreateDateDurationRecord(untilResult.years, untilResult.months,
weeks, 0);
@@ -2081,17 +2043,14 @@ static bool BalanceDateDurationRelative(
}
// Step 10.b.
- auto yearsMonthsWeeksDaysDuration = Duration{years, months, weeks, days};
+ const auto& yearsMonthsWeeksDaysDuration = duration;
// Steps 10.c-d.
- Duration untilResult;
+ DateDuration untilResult;
if (!untilAddedDate(yearsMonthsWeeksDaysDuration, &untilResult)) {
return false;
}
- // FIXME: spec bug - CreateDateDurationRecord is infallible
- // https://github.com/tc39/proposal-temporal/issues/2750
-
// Step 10.e.
*result = CreateDateDurationRecord(untilResult.years, untilResult.months,
untilResult.weeks, untilResult.days);
@@ -2114,17 +2073,14 @@ static bool BalanceDateDurationRelative(
}
// Step 11.c.
- auto monthsWeeksDaysDuration = Duration{0, months, weeks, days};
+ const auto& monthsWeeksDaysDuration = duration;
// Steps 11.d-e.
- Duration untilResult;
+ DateDuration untilResult;
if (!untilAddedDate(monthsWeeksDaysDuration, &untilResult)) {
return false;
}
- // FIXME: spec bug - CreateDateDurationRecord is infallible
- // https://github.com/tc39/proposal-temporal/issues/2750
-
// Step 11.f.
*result = CreateDateDurationRecord(0, untilResult.months, untilResult.weeks,
untilResult.days);
@@ -2141,17 +2097,14 @@ static bool BalanceDateDurationRelative(
MOZ_ASSERT(months == 0);
// Step 15.
- auto weeksDaysDuration = Duration{0, 0, weeks, days};
+ const auto& weeksDaysDuration = duration;
// Steps 16-17.
- Duration untilResult;
+ DateDuration untilResult;
if (!untilAddedDate(weeksDaysDuration, &untilResult)) {
return false;
}
- // FIXME: spec bug - CreateDateDurationRecord is infallible
- // https://github.com/tc39/proposal-temporal/issues/2750
-
// Step 18.
*result = CreateDateDurationRecord(0, 0, untilResult.weeks, untilResult.days);
return true;
@@ -2162,7 +2115,7 @@ static bool BalanceDateDurationRelative(
* smallestUnit, plainRelativeTo, calendarRec )
*/
bool js::temporal::BalanceDateDurationRelative(
- JSContext* cx, const Duration& duration, TemporalUnit largestUnit,
+ JSContext* cx, const DateDuration& duration, TemporalUnit largestUnit,
TemporalUnit smallestUnit,
Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
Handle<CalendarRecord> calendar, DateDuration* result) {
@@ -2179,7 +2132,7 @@ bool js::temporal::BalanceDateDurationRelative(
* zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
*/
static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
- Duration* duration) {
+ Duration* result) {
MOZ_ASSERT(IsValidDuration(one));
MOZ_ASSERT(IsValidDuration(two));
@@ -2194,7 +2147,13 @@ static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
// Step 5.
auto largestUnit = std::min(largestUnit1, largestUnit2);
- // Step 6.a.
+ // Step 6.
+ auto normalized1 = NormalizeTimeDuration(one);
+
+ // Step 7.
+ auto normalized2 = NormalizeTimeDuration(two);
+
+ // Step 8.a.
if (largestUnit <= TemporalUnit::Week) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
@@ -2202,14 +2161,31 @@ static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
return false;
}
- // Step 6.b.
- TimeDuration result;
- if (!BalanceTimeDuration(cx, one, two, largestUnit, &result)) {
+ // Step 8.b.
+ NormalizedTimeDuration normalized;
+ if (!AddNormalizedTimeDuration(cx, normalized1, normalized2, &normalized)) {
+ return false;
+ }
+
+ // Step 8.c.
+ int64_t days1 = mozilla::AssertedCast<int64_t>(one.days);
+ int64_t days2 = mozilla::AssertedCast<int64_t>(two.days);
+ auto totalDays = mozilla::CheckedInt64(days1) + days2;
+ MOZ_ASSERT(totalDays.isValid(), "adding two duration days can't overflow");
+
+ if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized, totalDays.value(),
+ &normalized)) {
return false;
}
- // Steps 6.c.
- *duration = result.toDuration();
+ // Step 8.d.
+ TimeDuration balanced;
+ if (!temporal::BalanceTimeDuration(cx, normalized, largestUnit, &balanced)) {
+ return false;
+ }
+
+ // Steps 8.e.
+ *result = balanced.toDuration();
return true;
}
@@ -2220,15 +2196,12 @@ static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
*/
static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
- Handle<CalendarRecord> calendar, Duration* duration) {
+ Handle<CalendarRecord> calendar, Duration* result) {
MOZ_ASSERT(IsValidDuration(one));
MOZ_ASSERT(IsValidDuration(two));
// Steps 1-2. (Not applicable)
- // FIXME: spec issue - calendarRec is not undefined when plainRelativeTo is
- // not undefined.
-
// Step 3.
auto largestUnit1 = DefaultTemporalLargestUnit(one);
@@ -2238,66 +2211,75 @@ static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
// Step 5.
auto largestUnit = std::min(largestUnit1, largestUnit2);
- // Step 6. (Not applicable)
-
- // Step 7.a. (Not applicable in our implementation.)
+ // Step 6.
+ auto normalized1 = NormalizeTimeDuration(one);
- // Step 7.b.
- auto dateDuration1 = one.date();
+ // Step 7.
+ auto normalized2 = NormalizeTimeDuration(two);
- // Step 7.c.
- auto dateDuration2 = two.date();
+ // Step 8. (Not applicable)
- // FIXME: spec issue - calendarUnitsPresent is unused.
+ // Step 9.a. (Not applicable in our implementation.)
- // Step 7.d.
- [[maybe_unused]] bool calendarUnitsPresent = true;
+ // Step 9.b.
+ auto dateDuration1 = one.toDateDuration();
- // Step 7.e.
- if (dateDuration1.years == 0 && dateDuration1.months == 0 &&
- dateDuration1.weeks == 0 && dateDuration2.years == 0 &&
- dateDuration2.months == 0 && dateDuration2.weeks == 0) {
- calendarUnitsPresent = false;
- }
+ // Step 9.c.
+ auto dateDuration2 = two.toDateDuration();
- // Step 7.f.
+ // Step 9.d.
Rooted<Wrapped<PlainDateObject*>> intermediate(
cx, AddDate(cx, calendar, plainRelativeTo, dateDuration1));
if (!intermediate) {
return false;
}
- // Step 7.g.
+ // Step 9.e.
Rooted<Wrapped<PlainDateObject*>> end(
cx, AddDate(cx, calendar, intermediate, dateDuration2));
if (!end) {
return false;
}
- // Step 7.h.
+ // Step 9.f.
auto dateLargestUnit = std::min(TemporalUnit::Day, largestUnit);
- // Steps 7.i-k.
- Duration dateDifference;
+ // Steps 9.g-i.
+ DateDuration dateDifference;
if (!DifferenceDate(cx, calendar, plainRelativeTo, end, dateLargestUnit,
&dateDifference)) {
return false;
}
- // Step 7.l.
- TimeDuration result;
- if (!BalanceTimeDuration(cx, dateDifference.days, one.time(), two.time(),
- largestUnit, &result)) {
+ // Step 9.j.
+ NormalizedTimeDuration normalized1WithDays;
+ if (!Add24HourDaysToNormalizedTimeDuration(
+ cx, normalized1, dateDifference.days, &normalized1WithDays)) {
+ return false;
+ }
+
+ // Step 9.k.
+ NormalizedTimeDuration normalized;
+ if (!AddNormalizedTimeDuration(cx, normalized1WithDays, normalized2,
+ &normalized)) {
+ return false;
+ }
+
+ // Step 9.l.
+ TimeDuration balanced;
+ if (!temporal::BalanceTimeDuration(cx, normalized, largestUnit, &balanced)) {
return false;
}
- // Steps 7.m.
- *duration = {
- dateDifference.years, dateDifference.months, dateDifference.weeks,
- result.days, result.hours, result.minutes,
- result.seconds, result.milliseconds, result.microseconds,
- result.nanoseconds,
+ // Steps 9.m.
+ *result = {
+ double(dateDifference.years), double(dateDifference.months),
+ double(dateDifference.weeks), double(balanced.days),
+ double(balanced.hours), double(balanced.minutes),
+ double(balanced.seconds), double(balanced.milliseconds),
+ balanced.microseconds, balanced.nanoseconds,
};
+ MOZ_ASSERT(IsValidDuration(*result));
return true;
}
@@ -2323,89 +2305,107 @@ static bool AddDuration(
// Step 5.
auto largestUnit = std::min(largestUnit1, largestUnit2);
- // Steps 6-7. (Not applicable)
+ // Step 6.
+ auto normalized1 = NormalizeTimeDuration(one);
- // Steps 8-9. (Not applicable in our implementation.)
+ // Step 7.
+ auto normalized2 = NormalizeTimeDuration(two);
- // FIXME: spec issue - GetPlainDateTimeFor called unnecessarily
- //
- // clang-format off
- //
- // 10. If largestUnit is one of "year", "month", "week", or "day", then
- // a. If precalculatedPlainDateTime is undefined, then
- // i. Let startDateTime be ? GetPlainDateTimeFor(timeZone, zonedRelativeTo.[[Nanoseconds]], calendar).
- // b. Else,
- // i. Let startDateTime be precalculatedPlainDateTime.
- // c. Let intermediateNs be ? AddZonedDateTime(zonedRelativeTo.[[Nanoseconds]], timeZone, calendar, y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, startDateTime).
- // d. Let endNs be ? AddZonedDateTime(intermediateNs, timeZone, calendar, y2, mon2, w2, d2, h2, min2, s2, ms2, mus2, ns2).
- // e. Return ? DifferenceZonedDateTime(zonedRelativeTo.[[Nanoseconds]], endNs, timeZone, calendar, largestUnit, OrdinaryObjectCreate(null), startDateTime).
- // 11. Let intermediateNs be ? AddInstant(zonedRelativeTo.[[Nanoseconds]], h1, min1, s1, ms1, mus1, ns1).
- // 12. Let endNs be ? AddInstant(intermediateNs, h2, min2, s2, ms2, mus2, ns2).
- // 13. Let result be DifferenceInstant(zonedRelativeTo.[[Nanoseconds]], endNs, 1, "nanosecond", largestUnit, "halfExpand").
- // 14. Return ! CreateDurationRecord(0, 0, 0, 0, result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]).
- //
- // clang-format on
+ // Steps 8-9. (Not applicable)
- // Step 10.
+ // Steps 10-11. (Not applicable in our implementation.)
+
+ // Step 12.
bool startDateTimeNeeded = largestUnit <= TemporalUnit::Day;
- // Steps 11-14 and 16.
- if (startDateTimeNeeded) {
- // Steps 11-12.
- PlainDateTime startDateTime;
- if (!precalculatedPlainDateTime) {
- if (!GetPlainDateTimeFor(cx, timeZone, zonedRelativeTo.instant(),
- &startDateTime)) {
- return false;
- }
- } else {
- startDateTime = *precalculatedPlainDateTime;
- }
+ // Steps 13-17.
+ if (!startDateTimeNeeded) {
+ // Steps 13-14. (Not applicable)
- // Step 13.
+ // Step 15. (Inlined AddZonedDateTime, step 6.)
Instant intermediateNs;
- if (!AddZonedDateTime(cx, zonedRelativeTo.instant(), timeZone, calendar,
- one, startDateTime, &intermediateNs)) {
+ if (!AddInstant(cx, zonedRelativeTo.instant(), normalized1,
+ &intermediateNs)) {
return false;
}
MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
- // Step 14.
+ // Step 16. (Inlined AddZonedDateTime, step 6.)
Instant endNs;
- if (!AddZonedDateTime(cx, intermediateNs, timeZone, calendar, two,
- &endNs)) {
+ if (!AddInstant(cx, intermediateNs, normalized2, &endNs)) {
return false;
}
MOZ_ASSERT(IsValidEpochInstant(endNs));
- // Step 15. (Not applicable)
+ // Step 17.a.
+ auto normalized = NormalizedTimeDurationFromEpochNanosecondsDifference(
+ endNs, zonedRelativeTo.instant());
- // Step 16.
- return DifferenceZonedDateTime(cx, zonedRelativeTo.instant(), endNs,
- timeZone, calendar, largestUnit,
- startDateTime, result);
+ // Step 17.b.
+ TimeDuration balanced;
+ if (!BalanceTimeDuration(cx, normalized, largestUnit, &balanced)) {
+ return false;
+ }
+
+ // Step 17.c.
+ *result = balanced.toDuration();
+ return true;
}
- // Steps 11-12. (Not applicable)
+ // Steps 13-14.
+ PlainDateTime startDateTime;
+ if (!precalculatedPlainDateTime) {
+ if (!GetPlainDateTimeFor(cx, timeZone, zonedRelativeTo.instant(),
+ &startDateTime)) {
+ return false;
+ }
+ } else {
+ startDateTime = *precalculatedPlainDateTime;
+ }
- // Step 13. (Inlined AddZonedDateTime, step 6.)
+ // Step 15.
+ auto norm1 =
+ CreateNormalizedDurationRecord(one.toDateDuration(), normalized1);
Instant intermediateNs;
- if (!AddInstant(cx, zonedRelativeTo.instant(), one, &intermediateNs)) {
+ if (!AddZonedDateTime(cx, zonedRelativeTo.instant(), timeZone, calendar,
+ norm1, startDateTime, &intermediateNs)) {
return false;
}
MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
- // Step 14. (Inlined AddZonedDateTime, step 6.)
+ // Step 16.
+ auto norm2 =
+ CreateNormalizedDurationRecord(two.toDateDuration(), normalized2);
Instant endNs;
- if (!AddInstant(cx, intermediateNs, two, &endNs)) {
+ if (!AddZonedDateTime(cx, intermediateNs, timeZone, calendar, norm2,
+ &endNs)) {
return false;
}
MOZ_ASSERT(IsValidEpochInstant(endNs));
- // Steps 15.a-b.
- return DifferenceInstant(cx, zonedRelativeTo.instant(), endNs, Increment{1},
- TemporalUnit::Nanosecond, largestUnit,
- TemporalRoundingMode::HalfExpand, result);
+ // Step 17. (Not applicable)
+
+ // Step 18.
+ NormalizedDuration difference;
+ if (!DifferenceZonedDateTime(cx, zonedRelativeTo.instant(), endNs, timeZone,
+ calendar, largestUnit, startDateTime,
+ &difference)) {
+ return false;
+ }
+
+ // Step 19.
+ auto balanced = BalanceTimeDuration(difference.time, TemporalUnit::Hour);
+
+ // Step 20.
+ *result = {
+ double(difference.date.years), double(difference.date.months),
+ double(difference.date.weeks), double(difference.date.days),
+ double(balanced.hours), double(balanced.minutes),
+ double(balanced.seconds), double(balanced.milliseconds),
+ balanced.microseconds, balanced.nanoseconds,
+ };
+ MOZ_ASSERT(IsValidDuration(*result));
+ return true;
}
/**
@@ -2421,214 +2421,18 @@ static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
mozilla::Nothing(), result);
}
-static bool RoundDuration(JSContext* cx, int64_t totalNanoseconds,
- TemporalUnit unit, Increment increment,
- TemporalRoundingMode roundingMode, Duration* result) {
- MOZ_ASSERT(unit >= TemporalUnit::Hour);
-
- double rounded;
- if (!RoundNumberToIncrement(cx, totalNanoseconds, unit, increment,
- roundingMode, &rounded)) {
- return false;
- }
-
- double hours = 0;
- double minutes = 0;
- double seconds = 0;
- double milliseconds = 0;
- double microseconds = 0;
- double nanoseconds = 0;
-
- switch (unit) {
- case TemporalUnit::Auto:
- case TemporalUnit::Year:
- case TemporalUnit::Week:
- case TemporalUnit::Month:
- case TemporalUnit::Day:
- MOZ_CRASH("Unexpected temporal unit");
-
- case TemporalUnit::Hour:
- hours = rounded;
- break;
- case TemporalUnit::Minute:
- minutes = rounded;
- break;
- case TemporalUnit::Second:
- seconds = rounded;
- break;
- case TemporalUnit::Millisecond:
- milliseconds = rounded;
- break;
- case TemporalUnit::Microsecond:
- microseconds = rounded;
- break;
- case TemporalUnit::Nanosecond:
- nanoseconds = rounded;
- break;
- }
-
- *result = {
- 0, 0, 0, 0, hours, minutes, seconds, milliseconds, microseconds,
- nanoseconds,
- };
- return ThrowIfInvalidDuration(cx, *result);
-}
-
-static bool RoundDuration(JSContext* cx, Handle<BigInt*> totalNanoseconds,
- TemporalUnit unit, Increment increment,
- TemporalRoundingMode roundingMode, Duration* result) {
- MOZ_ASSERT(unit >= TemporalUnit::Hour);
-
- double rounded;
- if (!RoundNumberToIncrement(cx, totalNanoseconds, unit, increment,
- roundingMode, &rounded)) {
- return false;
- }
-
- double hours = 0;
- double minutes = 0;
- double seconds = 0;
- double milliseconds = 0;
- double microseconds = 0;
- double nanoseconds = 0;
-
- switch (unit) {
- case TemporalUnit::Auto:
- case TemporalUnit::Year:
- case TemporalUnit::Week:
- case TemporalUnit::Month:
- case TemporalUnit::Day:
- MOZ_CRASH("Unexpected temporal unit");
-
- case TemporalUnit::Hour:
- hours = rounded;
- break;
- case TemporalUnit::Minute:
- minutes = rounded;
- break;
- case TemporalUnit::Second:
- seconds = rounded;
- break;
- case TemporalUnit::Millisecond:
- milliseconds = rounded;
- break;
- case TemporalUnit::Microsecond:
- microseconds = rounded;
- break;
- case TemporalUnit::Nanosecond:
- nanoseconds = rounded;
- break;
- }
-
- *result = {
- 0, 0, 0, 0, hours, minutes, seconds, milliseconds, microseconds,
- nanoseconds,
- };
- return ThrowIfInvalidDuration(cx, *result);
-}
-
/**
- * AdjustRoundedDurationDays ( years, months, weeks, days, hours, minutes,
- * seconds, milliseconds, microseconds, nanoseconds, increment, unit,
- * roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
- * precalculatedPlainDateTime )
- */
-static bool AdjustRoundedDurationDaysSlow(
- JSContext* cx, const Duration& duration, Increment increment,
- TemporalUnit unit, TemporalRoundingMode roundingMode,
- Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
- Handle<TimeZoneRecord> timeZone,
- mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
- InstantSpan dayLength, Duration* result) {
- MOZ_ASSERT(IsValidDuration(duration));
- MOZ_ASSERT(IsValidInstantSpan(dayLength));
-
- // Step 3.
- Rooted<BigInt*> timeRemainderNs(
- cx, TotalDurationNanosecondsSlow(cx, duration.time()));
- if (!timeRemainderNs) {
- return false;
- }
-
- // Steps 4-6.
- int32_t direction = timeRemainderNs->sign();
-
- // Steps 7-10. (Computed in caller)
-
- // Step 11.
- Rooted<BigInt*> dayLengthNs(cx, ToEpochNanoseconds(cx, dayLength));
- if (!dayLengthNs) {
- return false;
- }
- MOZ_ASSERT(IsValidInstantSpan(dayLengthNs));
-
- // Step 12.
- Rooted<BigInt*> oneDayLess(cx, BigInt::sub(cx, timeRemainderNs, dayLengthNs));
- if (!oneDayLess) {
- return false;
- }
-
- // Step 13.
- if ((direction > 0 && oneDayLess->sign() < 0) ||
- (direction < 0 && oneDayLess->sign() > 0)) {
- *result = duration;
- return true;
- }
-
- // Step 14.
- Duration adjustedDateDuration;
- if (!AddDuration(cx,
- {
- duration.years,
- duration.months,
- duration.weeks,
- duration.days,
- },
- {0, 0, 0, double(direction)}, zonedRelativeTo, calendar,
- timeZone, precalculatedPlainDateTime,
- &adjustedDateDuration)) {
- return false;
- }
-
- // Step 15.
- Duration roundedTimeDuration;
- if (!RoundDuration(cx, oneDayLess, unit, increment, roundingMode,
- &roundedTimeDuration)) {
- return false;
- }
-
- // Step 16.
- TimeDuration adjustedTimeDuration;
- if (!BalanceTimeDuration(cx, roundedTimeDuration, TemporalUnit::Hour,
- &adjustedTimeDuration)) {
- return false;
- }
-
- // Step 17.
- *result = {
- adjustedDateDuration.years, adjustedDateDuration.months,
- adjustedDateDuration.weeks, adjustedDateDuration.days,
- adjustedTimeDuration.hours, adjustedTimeDuration.minutes,
- adjustedTimeDuration.seconds, adjustedTimeDuration.milliseconds,
- adjustedTimeDuration.microseconds, adjustedTimeDuration.nanoseconds,
- };
- MOZ_ASSERT(IsValidDuration(*result));
- return true;
-}
-
-/**
- * AdjustRoundedDurationDays ( years, months, weeks, days, hours, minutes,
- * seconds, milliseconds, microseconds, nanoseconds, increment, unit,
- * roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
+ * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
+ * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
* precalculatedPlainDateTime )
*/
static bool AdjustRoundedDurationDays(
- JSContext* cx, const Duration& duration, Increment increment,
+ JSContext* cx, const NormalizedDuration& duration, Increment increment,
TemporalUnit unit, TemporalRoundingMode roundingMode,
Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
Handle<TimeZoneRecord> timeZone,
mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
- Duration* result) {
+ NormalizedDuration* result) {
MOZ_ASSERT(IsValidDuration(duration));
// Step 1.
@@ -2644,27 +2448,25 @@ static bool AdjustRoundedDurationDays(
// Step 2.
MOZ_ASSERT(precalculatedPlainDateTime);
- // Steps 4-6.
- //
- // Step 3 is moved below, so compute |direction| through DurationSign.
- int32_t direction = DurationSign(duration.time());
+ // Step 3.
+ int32_t direction = NormalizedTimeDurationSign(duration.time);
- // Steps 7-8.
+ // Steps 4-5.
Instant dayStart;
if (!AddZonedDateTime(cx, zonedRelativeTo.instant(), timeZone, calendar,
- duration.date(), *precalculatedPlainDateTime,
+ duration.date, *precalculatedPlainDateTime,
&dayStart)) {
return false;
}
MOZ_ASSERT(IsValidEpochInstant(dayStart));
- // Step 9.
+ // Step 6.
PlainDateTime dayStartDateTime;
if (!GetPlainDateTimeFor(cx, timeZone, dayStart, &dayStartDateTime)) {
return false;
}
- // Step 10.
+ // Step 7.
Instant dayEnd;
if (!AddDaysToZonedDateTime(cx, dayStart, dayStartDateTime, timeZone,
zonedRelativeTo.calendar(), direction, &dayEnd)) {
@@ -2672,153 +2474,74 @@ static bool AdjustRoundedDurationDays(
}
MOZ_ASSERT(IsValidEpochInstant(dayEnd));
- // Step 11.
- auto dayLength = dayEnd - dayStart;
- MOZ_ASSERT(IsValidInstantSpan(dayLength));
-
- // Step 3. (Reordered)
- auto timeRemainderNs = TotalDurationNanoseconds(duration.time());
- if (!timeRemainderNs) {
- return AdjustRoundedDurationDaysSlow(
- cx, duration, increment, unit, roundingMode, zonedRelativeTo, calendar,
- timeZone, precalculatedPlainDateTime, dayLength, result);
- }
+ // Step 8.
+ auto dayLengthNs =
+ NormalizedTimeDurationFromEpochNanosecondsDifference(dayEnd, dayStart);
+ MOZ_ASSERT(IsValidInstantSpan(dayLengthNs.to<InstantSpan>()));
- // Step 12.
- auto checkedOneDayLess = *timeRemainderNs - dayLength.toNanoseconds();
- if (!checkedOneDayLess.isValid()) {
- return AdjustRoundedDurationDaysSlow(
- cx, duration, increment, unit, roundingMode, zonedRelativeTo, calendar,
- timeZone, precalculatedPlainDateTime, dayLength, result);
+ // Step 9.
+ NormalizedTimeDuration oneDayLess;
+ if (!SubtractNormalizedTimeDuration(cx, duration.time, dayLengthNs,
+ &oneDayLess)) {
+ return false;
}
- auto oneDayLess = checkedOneDayLess.value();
- // Step 13.
- if ((direction > 0 && oneDayLess < 0) || (direction < 0 && oneDayLess > 0)) {
+ // Step 10.
+ int32_t oneDayLessSign = NormalizedTimeDurationSign(oneDayLess);
+ if ((direction > 0 && oneDayLessSign < 0) ||
+ (direction < 0 && oneDayLessSign > 0)) {
*result = duration;
return true;
}
- // Step 14.
+ // Step 11.
Duration adjustedDateDuration;
- if (!AddDuration(cx,
- {
- duration.years,
- duration.months,
- duration.weeks,
- duration.days,
- },
- {0, 0, 0, double(direction)}, zonedRelativeTo, calendar,
- timeZone, precalculatedPlainDateTime,
- &adjustedDateDuration)) {
- return false;
- }
-
- // Step 15.
- Duration roundedTimeDuration;
- if (!RoundDuration(cx, oneDayLess, unit, increment, roundingMode,
- &roundedTimeDuration)) {
+ if (!AddDuration(cx, duration.date.toDuration(), {0, 0, 0, double(direction)},
+ zonedRelativeTo, calendar, timeZone,
+ precalculatedPlainDateTime, &adjustedDateDuration)) {
return false;
}
- // Step 16.
- TimeDuration adjustedTimeDuration;
- if (!BalanceTimeDuration(cx, roundedTimeDuration, TemporalUnit::Hour,
- &adjustedTimeDuration)) {
+ // Step 12.
+ NormalizedTimeDuration roundedTime;
+ if (!RoundDuration(cx, oneDayLess, increment, unit, roundingMode,
+ &roundedTime)) {
return false;
}
- // FIXME: spec bug - CreateDurationRecord is fallible because the adjusted
- // date and time durations can be have different signs.
- // https://github.com/tc39/proposal-temporal/issues/2536
- //
- // clang-format off
- //
- // {
- // let calendar = new class extends Temporal.Calendar {
- // dateAdd(date, duration, options) {
- // console.log(`dateAdd(${date}, ${duration})`);
- // if (duration.days === 10) {
- // return super.dateAdd(date, duration.negated(), options);
- // }
- // return super.dateAdd(date, duration, options);
- // }
- // }("iso8601");
- //
- // let zdt = new Temporal.ZonedDateTime(0n, "UTC", calendar);
- //
- // let d = Temporal.Duration.from({
- // days: 10,
- // hours: 25,
- // });
- //
- // let r = d.round({
- // smallestUnit: "nanoseconds",
- // roundingIncrement: 5,
- // relativeTo: zdt,
- // });
- // console.log(r.toString());
- // }
- //
- // clang-format on
-
- // Step 17.
- *result = {
- adjustedDateDuration.years, adjustedDateDuration.months,
- adjustedDateDuration.weeks, adjustedDateDuration.days,
- adjustedTimeDuration.hours, adjustedTimeDuration.minutes,
- adjustedTimeDuration.seconds, adjustedTimeDuration.milliseconds,
- adjustedTimeDuration.microseconds, adjustedTimeDuration.nanoseconds,
- };
- return ThrowIfInvalidDuration(cx, *result);
+ // Step 13.
+ return CombineDateAndNormalizedTimeDuration(
+ cx, adjustedDateDuration.toDateDuration(), roundedTime, result);
}
/**
- * AdjustRoundedDurationDays ( years, months, weeks, days, hours, minutes,
- * seconds, milliseconds, microseconds, nanoseconds, increment, unit,
- * roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
+ * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
+ * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
* precalculatedPlainDateTime )
*/
bool js::temporal::AdjustRoundedDurationDays(
- JSContext* cx, const Duration& duration, Increment increment,
+ JSContext* cx, const NormalizedDuration& duration, Increment increment,
TemporalUnit unit, TemporalRoundingMode roundingMode,
Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
Handle<TimeZoneRecord> timeZone,
- const PlainDateTime& precalculatedPlainDateTime, Duration* result) {
+ const PlainDateTime& precalculatedPlainDateTime,
+ NormalizedDuration* result) {
return ::AdjustRoundedDurationDays(
cx, duration, increment, unit, roundingMode, zonedRelativeTo, calendar,
timeZone, mozilla::SomeRef(precalculatedPlainDateTime), result);
}
-static bool BigIntToStringBuilder(JSContext* cx, Handle<BigInt*> num,
- JSStringBuilder& sb) {
- MOZ_ASSERT(!num->isNegative());
-
- JSLinearString* str = BigInt::toString<CanGC>(cx, num, 10);
- if (!str) {
- return false;
- }
- return sb.append(str);
-}
-
static bool NumberToStringBuilder(JSContext* cx, double num,
JSStringBuilder& sb) {
MOZ_ASSERT(IsInteger(num));
MOZ_ASSERT(num >= 0);
+ MOZ_ASSERT(num < DOUBLE_INTEGRAL_PRECISION_LIMIT);
- if (num < DOUBLE_INTEGRAL_PRECISION_LIMIT) {
- ToCStringBuf cbuf;
- size_t length;
- const char* numStr = NumberToCString(&cbuf, num, &length);
-
- return sb.append(numStr, length);
- }
+ ToCStringBuf cbuf;
+ size_t length;
+ const char* numStr = NumberToCString(&cbuf, num, &length);
- Rooted<BigInt*> bi(cx, BigInt::createFromDouble(cx, num));
- if (!bi) {
- return false;
- }
- return BigIntToStringBuilder(cx, bi, sb);
+ return sb.append(numStr, length);
}
static Duration AbsoluteDuration(const Duration& duration) {
@@ -2853,7 +2576,7 @@ static Duration AbsoluteDuration(const Duration& duration) {
}
// Steps 1.b-c.
- uint32_t k = 100'000'000;
+ int32_t k = 100'000'000;
do {
if (!result.append(char('0' + (subSecondNanoseconds / k)))) {
return false;
@@ -2874,7 +2597,7 @@ static Duration AbsoluteDuration(const Duration& duration) {
}
// Steps 2.b-c.
- uint32_t k = 100'000'000;
+ int32_t k = 100'000'000;
for (uint8_t i = 0; i < precision.value(); i++) {
if (!result.append(char('0' + (subSecondNanoseconds / k)))) {
return false;
@@ -2889,7 +2612,7 @@ static Duration AbsoluteDuration(const Duration& duration) {
/**
* TemporalDurationToString ( years, months, weeks, days, hours, minutes,
- * seconds, milliseconds, microseconds, nanoseconds, precision )
+ * normSeconds, precision )
*/
static JSString* TemporalDurationToString(JSContext* cx,
const Duration& duration,
@@ -2897,187 +2620,49 @@ static JSString* TemporalDurationToString(JSContext* cx,
MOZ_ASSERT(IsValidDuration(duration));
MOZ_ASSERT(precision != Precision::Minute());
- // Convert to absolute values up front. This is okay to do, because when the
- // duration is valid, all components have the same sign.
- const auto& [years, months, weeks, days, hours, minutes, seconds,
- milliseconds, microseconds, nanoseconds] =
- AbsoluteDuration(duration);
-
// Fast path for zero durations.
- if (years == 0 && months == 0 && weeks == 0 && days == 0 && hours == 0 &&
- minutes == 0 && seconds == 0 && milliseconds == 0 && microseconds == 0 &&
- nanoseconds == 0 &&
+ if (duration == Duration{} &&
(precision == Precision::Auto() || precision.value() == 0)) {
return NewStringCopyZ<CanGC>(cx, "PT0S");
}
- Rooted<BigInt*> totalSecondsBigInt(cx);
- double totalSeconds = seconds;
- int32_t fraction = 0;
- if (milliseconds != 0 || microseconds != 0 || nanoseconds != 0) {
- bool imprecise = false;
- do {
- int64_t sec;
- int64_t milli;
- int64_t micro;
- int64_t nano;
- if (!mozilla::NumberEqualsInt64(seconds, &sec) ||
- !mozilla::NumberEqualsInt64(milliseconds, &milli) ||
- !mozilla::NumberEqualsInt64(microseconds, &micro) ||
- !mozilla::NumberEqualsInt64(nanoseconds, &nano)) {
- imprecise = true;
- break;
- }
-
- mozilla::CheckedInt64 intermediate;
-
- // Step 2.
- intermediate = micro;
- intermediate += (nano / 1000);
- if (!intermediate.isValid()) {
- imprecise = true;
- break;
- }
- micro = intermediate.value();
-
- // Step 3.
- nano %= 1000;
-
- // Step 4.
- intermediate = milli;
- intermediate += (micro / 1000);
- if (!intermediate.isValid()) {
- imprecise = true;
- break;
- }
- milli = intermediate.value();
-
- // Step 5.
- micro %= 1000;
-
- // Step 6.
- intermediate = sec;
- intermediate += (milli / 1000);
- if (!intermediate.isValid()) {
- imprecise = true;
- break;
- }
- sec = intermediate.value();
-
- // Step 7.
- milli %= 1000;
-
- if (sec < int64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT)) {
- totalSeconds = double(sec);
- } else {
- totalSecondsBigInt = BigInt::createFromInt64(cx, sec);
- if (!totalSecondsBigInt) {
- return nullptr;
- }
- }
-
- // These are now all in the range [0, 999].
- MOZ_ASSERT(0 <= milli && milli <= 999);
- MOZ_ASSERT(0 <= micro && micro <= 999);
- MOZ_ASSERT(0 <= nano && nano <= 999);
-
- // Step 20.b. (Reordered)
- fraction = milli * 1'000'000 + micro * 1'000 + nano;
- MOZ_ASSERT(0 <= fraction && fraction < 1'000'000'000);
- } while (false);
-
- // If a result was imprecise, recompute with BigInt to get full precision.
- if (imprecise) {
- Rooted<BigInt*> secs(cx, BigInt::createFromDouble(cx, seconds));
- if (!secs) {
- return nullptr;
- }
-
- Rooted<BigInt*> millis(cx, BigInt::createFromDouble(cx, milliseconds));
- if (!millis) {
- return nullptr;
- }
-
- Rooted<BigInt*> micros(cx, BigInt::createFromDouble(cx, microseconds));
- if (!micros) {
- return nullptr;
- }
-
- Rooted<BigInt*> nanos(cx, BigInt::createFromDouble(cx, nanoseconds));
- if (!nanos) {
- return nullptr;
- }
-
- Rooted<BigInt*> thousand(cx, BigInt::createFromInt64(cx, 1000));
- if (!thousand) {
- return nullptr;
- }
-
- // Steps 2-3.
- Rooted<BigInt*> quotient(cx);
- if (!BigInt::divmod(cx, nanos, thousand, &quotient, &nanos)) {
- return nullptr;
- }
-
- micros = BigInt::add(cx, micros, quotient);
- if (!micros) {
- return nullptr;
- }
-
- // Steps 4-5.
- if (!BigInt::divmod(cx, micros, thousand, &quotient, &micros)) {
- return nullptr;
- }
-
- millis = BigInt::add(cx, millis, quotient);
- if (!millis) {
- return nullptr;
- }
-
- // Steps 6-7.
- if (!BigInt::divmod(cx, millis, thousand, &quotient, &millis)) {
- return nullptr;
- }
-
- totalSecondsBigInt = BigInt::add(cx, secs, quotient);
- if (!totalSecondsBigInt) {
- return nullptr;
- }
+ // Convert to absolute values up front. This is okay to do, because when the
+ // duration is valid, all components have the same sign.
+ const auto& [years, months, weeks, days, hours, minutes, seconds,
+ milliseconds, microseconds, nanoseconds] =
+ AbsoluteDuration(duration);
- // These are now all in the range [0, 999].
- int64_t milli = BigInt::toInt64(millis);
- int64_t micro = BigInt::toInt64(micros);
- int64_t nano = BigInt::toInt64(nanos);
+ // Years to seconds parts are all safe integers for valid durations.
+ MOZ_ASSERT(years < DOUBLE_INTEGRAL_PRECISION_LIMIT);
+ MOZ_ASSERT(months < DOUBLE_INTEGRAL_PRECISION_LIMIT);
+ MOZ_ASSERT(weeks < DOUBLE_INTEGRAL_PRECISION_LIMIT);
+ MOZ_ASSERT(days < DOUBLE_INTEGRAL_PRECISION_LIMIT);
+ MOZ_ASSERT(hours < DOUBLE_INTEGRAL_PRECISION_LIMIT);
+ MOZ_ASSERT(minutes < DOUBLE_INTEGRAL_PRECISION_LIMIT);
+ MOZ_ASSERT(seconds < DOUBLE_INTEGRAL_PRECISION_LIMIT);
- MOZ_ASSERT(0 <= milli && milli <= 999);
- MOZ_ASSERT(0 <= micro && micro <= 999);
- MOZ_ASSERT(0 <= nano && nano <= 999);
+ auto secondsDuration = NormalizeTimeDuration(0.0, 0.0, seconds, milliseconds,
+ microseconds, nanoseconds);
- // Step 20.b. (Reordered)
- fraction = milli * 1'000'000 + micro * 1'000 + nano;
- MOZ_ASSERT(0 <= fraction && fraction < 1'000'000'000);
- }
- }
+ // Step 1.
+ int32_t sign = DurationSign(duration);
- // Steps 8 and 13.
+ // Steps 2 and 7.
JSStringBuilder result(cx);
- // Step 1. (Reordered)
- int32_t sign = DurationSign(duration);
-
- // Step 21. (Reordered)
+ // Step 13. (Reordered)
if (sign < 0) {
if (!result.append('-')) {
return nullptr;
}
}
- // Step 22. (Reordered)
+ // Step 14. (Reordered)
if (!result.append('P')) {
return nullptr;
}
- // Step 9.
+ // Step 3.
if (years != 0) {
if (!NumberToStringBuilder(cx, years, result)) {
return nullptr;
@@ -3087,7 +2672,7 @@ static JSString* TemporalDurationToString(JSContext* cx,
}
}
- // Step 10.
+ // Step 4.
if (months != 0) {
if (!NumberToStringBuilder(cx, months, result)) {
return nullptr;
@@ -3097,7 +2682,7 @@ static JSString* TemporalDurationToString(JSContext* cx,
}
}
- // Step 11.
+ // Step 5.
if (weeks != 0) {
if (!NumberToStringBuilder(cx, weeks, result)) {
return nullptr;
@@ -3107,7 +2692,7 @@ static JSString* TemporalDurationToString(JSContext* cx,
}
}
- // Step 12.
+ // Step 6.
if (days != 0) {
if (!NumberToStringBuilder(cx, days, result)) {
return nullptr;
@@ -3117,29 +2702,22 @@ static JSString* TemporalDurationToString(JSContext* cx,
}
}
- // Steps 16-17.
- bool nonzeroSecondsAndLower = seconds != 0 || milliseconds != 0 ||
- microseconds != 0 || nanoseconds != 0;
- MOZ_ASSERT(nonzeroSecondsAndLower ==
- (totalSeconds != 0 ||
- (totalSecondsBigInt && !totalSecondsBigInt->isZero()) ||
- fraction != 0));
+ // Step 7. (Moved above)
- // Steps 18-19.
+ // Steps 10-11. (Reordered)
bool zeroMinutesAndHigher = years == 0 && months == 0 && weeks == 0 &&
days == 0 && hours == 0 && minutes == 0;
- // Step 20. (if-condition)
- bool hasSecondsPart = nonzeroSecondsAndLower || zeroMinutesAndHigher ||
- precision != Precision::Auto();
-
+ // Steps 8-9, 12, and 15.
+ bool hasSecondsPart = (secondsDuration != NormalizedTimeDuration{}) ||
+ zeroMinutesAndHigher || precision != Precision::Auto();
if (hours != 0 || minutes != 0 || hasSecondsPart) {
- // Step 23. (Reordered)
+ // Step 15. (Reordered)
if (!result.append('T')) {
return nullptr;
}
- // Step 14.
+ // Step 8.
if (hours != 0) {
if (!NumberToStringBuilder(cx, hours, result)) {
return nullptr;
@@ -3149,7 +2727,7 @@ static JSString* TemporalDurationToString(JSContext* cx,
}
}
- // Step 15.
+ // Step 9.
if (minutes != 0) {
if (!NumberToStringBuilder(cx, minutes, result)) {
return nullptr;
@@ -3159,34 +2737,29 @@ static JSString* TemporalDurationToString(JSContext* cx,
}
}
- // Step 20.
+ // Step 12.
if (hasSecondsPart) {
- // Step 20.a.
- if (totalSecondsBigInt) {
- if (!BigIntToStringBuilder(cx, totalSecondsBigInt, result)) {
- return nullptr;
- }
- } else {
- if (!NumberToStringBuilder(cx, totalSeconds, result)) {
- return nullptr;
- }
+ // Step 12.a.
+ if (!NumberToStringBuilder(cx, double(secondsDuration.seconds), result)) {
+ return nullptr;
}
- // Step 20.b. (Moved above)
-
- // Step 20.c.
- if (!FormatFractionalSeconds(result, fraction, precision)) {
+ // Step 12.b.
+ if (!FormatFractionalSeconds(result, secondsDuration.nanoseconds,
+ precision)) {
return nullptr;
}
- // Step 20.d.
+ // Step 12.c.
if (!result.append('S')) {
return nullptr;
}
}
}
- // Step 24.
+ // Steps 13-15. (Moved above)
+
+ // Step 16.
return result.finishString();
}
@@ -3206,9 +2779,6 @@ static bool ToRelativeTemporalObject(
// Step 2.
if (value.isUndefined()) {
- // FIXME: spec issue - switch return record fields for consistency.
- // FIXME: spec bug - [[TimeZoneRec]] field not created
-
plainRelativeTo.set(nullptr);
zonedRelativeTo.set(ZonedDateTime{});
timeZoneRecord.set(TimeZoneRecord{});
@@ -3414,20 +2984,20 @@ static bool ToRelativeTemporalObject(
bool isUTC;
bool hasOffset;
int64_t timeZoneOffset;
- Rooted<ParsedTimeZone> timeZoneName(cx);
+ Rooted<ParsedTimeZone> timeZoneAnnotation(cx);
Rooted<JSString*> calendarString(cx);
if (!ParseTemporalRelativeToString(cx, string, &dateTime, &isUTC,
&hasOffset, &timeZoneOffset,
- &timeZoneName, &calendarString)) {
+ &timeZoneAnnotation, &calendarString)) {
return false;
}
// Step 6.c. (Not applicable in our implementation.)
// Steps 6.e-f.
- if (timeZoneName) {
+ if (timeZoneAnnotation) {
// Step 6.f.i.
- if (!ToTemporalTimeZone(cx, timeZoneName, &timeZone)) {
+ if (!ToTemporalTimeZone(cx, timeZoneAnnotation, &timeZone)) {
return false;
}
@@ -3545,820 +3115,216 @@ static bool CreateCalendarMethodsRecordFromRelativeTo(
return true;
}
-static constexpr bool IsSafeInteger(int64_t x) {
- constexpr int64_t MaxSafeInteger = int64_t(1) << 53;
- constexpr int64_t MinSafeInteger = -MaxSafeInteger;
- return MinSafeInteger < x && x < MaxSafeInteger;
-}
+struct RoundedDuration final {
+ NormalizedDuration duration;
+ double total = 0;
+};
-/**
- * RoundNumberToIncrement ( x, increment, roundingMode )
- */
-static void TruncateNumber(int64_t numerator, int64_t denominator,
- double* quotient, double* total) {
- // Computes the quotient and real number value of the rational number
- // |numerator / denominator|.
-
- // Int64 division truncates.
- int64_t q = numerator / denominator;
- int64_t r = numerator % denominator;
-
- // The total value is stored as a mathematical number in the draft proposal,
- // so we can't convert it to a double without loss of precision. We use two
- // different approaches to compute the total value based on the input range.
- //
- // For example:
- //
- // When |numerator = 1000001| and |denominator = 60 * 1000|, the exact result
- // is |16.66668333...| and the best possible approximation is
- // |16.666683333333335070...𝔽|. We can this approximation when casting both
- // numerator and denominator to doubles and then performing a double division.
- //
- // When |numerator = 14400000000000001| and |denominator = 3600000000000|, we
- // can't use double division, because |14400000000000001| can't be represented
- // as an exact double value. The exact result is |4000.0000000000002777...|.
- //
- // The best possible approximation is |4000.0000000000004547...𝔽|, which can
- // be computed through |q + r / denominator|.
- if (::IsSafeInteger(numerator) && ::IsSafeInteger(denominator)) {
- *quotient = double(q);
- *total = double(numerator) / double(denominator);
- } else {
- *quotient = double(q);
- *total = double(q) + double(r) / double(denominator);
- }
-}
+enum class ComputeRemainder : bool { No, Yes };
/**
- * RoundNumberToIncrement ( x, increment, roundingMode )
+ * RoundNormalizedTimeDurationToIncrement ( d, increment, roundingMode )
*/
-static bool TruncateNumber(JSContext* cx, Handle<BigInt*> numerator,
- Handle<BigInt*> denominator, double* quotient,
- double* total) {
- MOZ_ASSERT(!denominator->isNegative());
- MOZ_ASSERT(!denominator->isZero());
-
- // Dividing zero is always zero.
- if (numerator->isZero()) {
- *quotient = 0;
- *total = 0;
- return true;
- }
+static NormalizedTimeDuration RoundNormalizedTimeDurationToIncrement(
+ const NormalizedTimeDuration& duration, const TemporalUnit unit,
+ Increment increment, TemporalRoundingMode roundingMode) {
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
+ MOZ_ASSERT(unit > TemporalUnit::Day);
+ MOZ_ASSERT(increment <= MaximumTemporalDurationRoundingIncrement(unit));
- int64_t num, denom;
- if (BigInt::isInt64(numerator, &num) &&
- BigInt::isInt64(denominator, &denom)) {
- TruncateNumber(num, denom, quotient, total);
- return true;
- }
+ int64_t divisor = ToNanoseconds(unit) * increment.value();
+ MOZ_ASSERT(divisor > 0);
+ MOZ_ASSERT(divisor <= ToNanoseconds(TemporalUnit::Day));
- // BigInt division truncates.
- Rooted<BigInt*> quot(cx);
- Rooted<BigInt*> rem(cx);
- if (!BigInt::divmod(cx, numerator, denominator, &quot, &rem)) {
- return false;
- }
-
- double q = BigInt::numberValue(quot);
- *quotient = q;
- *total = q + BigInt::numberValue(rem) / BigInt::numberValue(denominator);
- return true;
+ auto totalNanoseconds = duration.toNanoseconds();
+ auto rounded =
+ RoundNumberToIncrement(totalNanoseconds, Int128{divisor}, roundingMode);
+ return NormalizedTimeDuration::fromNanoseconds(rounded);
}
/**
- * RoundNumberToIncrement ( x, increment, roundingMode )
+ * RoundNormalizedTimeDurationToIncrement ( d, increment, roundingMode )
*/
-static bool TruncateNumber(JSContext* cx, const Duration& toRound,
- TemporalUnit unit, double* quotient, double* total) {
- MOZ_ASSERT(unit >= TemporalUnit::Day);
-
- int64_t denominator = ToNanoseconds(unit);
- MOZ_ASSERT(denominator > 0);
- MOZ_ASSERT(denominator <= 86'400'000'000'000);
-
- // Fast-path when we can perform the whole computation with int64 values.
- if (auto numerator = TotalDurationNanoseconds(toRound)) {
- TruncateNumber(*numerator, denominator, quotient, total);
- return true;
- }
-
- Rooted<BigInt*> numerator(cx, TotalDurationNanosecondsSlow(cx, toRound));
- if (!numerator) {
- return false;
- }
-
- // Division by one has no remainder.
- if (denominator == 1) {
- double q = BigInt::numberValue(numerator);
- *quotient = q;
- *total = q;
- return true;
- }
-
- Rooted<BigInt*> denom(cx, BigInt::createFromInt64(cx, denominator));
- if (!denom) {
- return false;
- }
+static bool RoundNormalizedTimeDurationToIncrement(
+ JSContext* cx, const NormalizedTimeDuration& duration,
+ const TemporalUnit unit, Increment increment,
+ TemporalRoundingMode roundingMode, NormalizedTimeDuration* result) {
+ // Step 1.
+ auto rounded = RoundNormalizedTimeDurationToIncrement(
+ duration, unit, increment, roundingMode);
- // BigInt division truncates.
- Rooted<BigInt*> quot(cx);
- Rooted<BigInt*> rem(cx);
- if (!BigInt::divmod(cx, numerator, denom, &quot, &rem)) {
+ // Step 2.
+ if (!IsValidNormalizedTimeDuration(rounded)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
return false;
}
- double q = BigInt::numberValue(quot);
- *quotient = q;
- *total = q + BigInt::numberValue(rem) / double(denominator);
+ // Step 3.
+ *result = rounded;
return true;
}
/**
- * RoundNumberToIncrement ( x, increment, roundingMode )
+ * DivideNormalizedTimeDuration ( d, divisor )
*/
-static bool RoundNumberToIncrement(JSContext* cx, const Duration& toRound,
- TemporalUnit unit, Increment increment,
- TemporalRoundingMode roundingMode,
- double* result) {
- MOZ_ASSERT(unit >= TemporalUnit::Day);
-
- // Fast-path when we can perform the whole computation with int64 values.
- if (auto total = TotalDurationNanoseconds(toRound)) {
- return RoundNumberToIncrement(cx, *total, unit, increment, roundingMode,
- result);
- }
+static double TotalNormalizedTimeDuration(
+ const NormalizedTimeDuration& duration, const TemporalUnit unit) {
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
+ MOZ_ASSERT(unit > TemporalUnit::Day);
- Rooted<BigInt*> totalNs(cx, TotalDurationNanosecondsSlow(cx, toRound));
- if (!totalNs) {
- return false;
- }
-
- return RoundNumberToIncrement(cx, totalNs, unit, increment, roundingMode,
- result);
+ auto numerator = duration.toNanoseconds();
+ auto denominator = Int128{ToNanoseconds(unit)};
+ return FractionToDouble(numerator, denominator);
}
-struct RoundedDuration final {
- Duration duration;
- double total = 0;
-};
-
-enum class ComputeRemainder : bool { No, Yes };
-
/**
- * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
- * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
- * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
- * precalculatedPlainDateTime ] ] ] ] ] )
+ * RoundDuration ( years, months, weeks, days, norm, increment, unit,
+ * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
+ * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
*/
-static bool RoundDuration(JSContext* cx, const Duration& duration,
- Increment increment, TemporalUnit unit,
- TemporalRoundingMode roundingMode,
- ComputeRemainder computeRemainder,
- RoundedDuration* result) {
- // The remainder is only needed when called from |Duration_total|. And `total`
- // always passes |increment=1| and |roundingMode=trunc|.
- MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
- increment == Increment{1});
- MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
- roundingMode == TemporalRoundingMode::Trunc);
+NormalizedTimeDuration js::temporal::RoundDuration(
+ const NormalizedTimeDuration& duration, Increment increment,
+ TemporalUnit unit, TemporalRoundingMode roundingMode) {
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
+ MOZ_ASSERT(unit > TemporalUnit::Day);
- auto [years, months, weeks, days, hours, minutes, seconds, milliseconds,
- microseconds, nanoseconds] = duration;
+ // Steps 1-12. (Not applicable)
- // Steps 1-5. (Not applicable.)
-
- // Step 6.
- if (unit <= TemporalUnit::Week) {
- JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
- JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
- "relativeTo");
- return false;
- }
-
- // TODO: We could directly return here if unit=nanoseconds and increment=1,
- // because in that case this operation is a no-op. This case happens for
- // example when calling Temporal.PlainTime.prototype.{since,until} without an
- // options object.
- //
- // But maybe this can be even more efficiently handled in the callers. For
- // example when Temporal.PlainTime.prototype.{since,until} is called without
- // an options object, we can not only skip the RoundDuration call, but also
- // the following BalanceTimeDuration call.
-
- // Step 7. (Not applicable.)
-
- // Step 8. (Moved below.)
-
- // Step 9. (Not applicable.)
-
- // Steps 10-19.
- Duration toRound;
- double* roundedTime;
- switch (unit) {
- case TemporalUnit::Auto:
- case TemporalUnit::Year:
- case TemporalUnit::Week:
- case TemporalUnit::Month:
- // Steps 10-12. (Not applicable.)
- MOZ_CRASH("Unexpected temporal unit");
-
- case TemporalUnit::Day: {
- // clang-format off
- //
- // Relevant steps from the spec algorithm:
- //
- // 6.a Let nanoseconds be ! TotalDurationNanoseconds(0, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, 0).
- // 6.d Let result be ? NanosecondsToDays(nanoseconds, intermediate).
- // 6.e Set days to days + result.[[Days]] + result.[[Nanoseconds]] / abs(result.[[DayLength]]).
- // ...
- // 12.a Let fractionalDays be days.
- // 12.b Set days to ? RoundNumberToIncrement(days, increment, roundingMode).
- // 12.c Set remainder to fractionalDays - days.
- //
- // Where `result.[[Days]]` is `the integral part of nanoseconds / dayLengthNs`
- // and `result.[[Nanoseconds]]` is `nanoseconds modulo dayLengthNs`.
- // With `dayLengthNs = 8.64 × 10^13`.
- //
- // So we have:
- // d + r.days + (r.nanoseconds / len)
- // = d + [ns / len] + ((ns % len) / len)
- // = d + [ns / len] + ((ns - ([ns / len] × len)) / len)
- // = d + [ns / len] + (ns / len) - (([ns / len] × len) / len)
- // = d + [ns / len] + (ns / len) - [ns / len]
- // = d + (ns / len)
- // = ((d × len) / len) + (ns / len)
- // = ((d × len) + ns) / len
- //
- // `((d × len) + ns)` is the result of calling TotalDurationNanoseconds(),
- // which means we can use the same code for all time computations in this
- // function.
- //
- // clang-format on
-
- MOZ_ASSERT(increment <= Increment{1'000'000'000},
- "limited by ToTemporalRoundingIncrement");
-
- // Steps 7.a, 7.c, and 13.a-b.
- toRound = duration;
- roundedTime = &days;
-
- // Step 7.b. (Not applicable)
-
- // Steps 7.d-e.
- hours = 0;
- minutes = 0;
- seconds = 0;
- milliseconds = 0;
- microseconds = 0;
- nanoseconds = 0;
- break;
- }
-
- case TemporalUnit::Hour: {
- MOZ_ASSERT(increment <= Increment{24},
- "limited by MaximumTemporalDurationRoundingIncrement");
-
- // Steps 8 and 14.a-c.
- toRound = {
- 0,
- 0,
- 0,
- 0,
- hours,
- minutes,
- seconds,
- milliseconds,
- microseconds,
- nanoseconds,
- };
- roundedTime = &hours;
-
- // Step 14.d.
- minutes = 0;
- seconds = 0;
- milliseconds = 0;
- microseconds = 0;
- nanoseconds = 0;
- break;
- }
-
- case TemporalUnit::Minute: {
- MOZ_ASSERT(increment <= Increment{60},
- "limited by MaximumTemporalDurationRoundingIncrement");
-
- // Steps 8 and 15.a-c.
- toRound = {
- 0, 0, 0, 0, 0, minutes, seconds, milliseconds, microseconds,
- nanoseconds,
- };
- roundedTime = &minutes;
-
- // Step 15.d.
- seconds = 0;
- milliseconds = 0;
- microseconds = 0;
- nanoseconds = 0;
- break;
- }
-
- case TemporalUnit::Second: {
- MOZ_ASSERT(increment <= Increment{60},
- "limited by MaximumTemporalDurationRoundingIncrement");
-
- // Steps 8 and 16.a-b.
- toRound = {
- 0, 0, 0, 0, 0, 0, seconds, milliseconds, microseconds, nanoseconds,
- };
- roundedTime = &seconds;
-
- // Step 16.c.
- milliseconds = 0;
- microseconds = 0;
- nanoseconds = 0;
- break;
- }
-
- case TemporalUnit::Millisecond: {
- MOZ_ASSERT(increment <= Increment{1000},
- "limited by MaximumTemporalDurationRoundingIncrement");
-
- // Steps 17.a-c.
- toRound = {0, 0, 0, 0, 0, 0, 0, milliseconds, microseconds, nanoseconds};
- roundedTime = &milliseconds;
-
- // Step 17.d.
- microseconds = 0;
- nanoseconds = 0;
- break;
- }
-
- case TemporalUnit::Microsecond: {
- MOZ_ASSERT(increment <= Increment{1000},
- "limited by MaximumTemporalDurationRoundingIncrement");
-
- // Steps 18.a-c.
- toRound = {0, 0, 0, 0, 0, 0, 0, 0, microseconds, nanoseconds};
- roundedTime = &microseconds;
-
- // Step 18.d.
- nanoseconds = 0;
- break;
- }
-
- case TemporalUnit::Nanosecond: {
- MOZ_ASSERT(increment <= Increment{1000},
- "limited by MaximumTemporalDurationRoundingIncrement");
-
- // Step 19.a. (Implicit)
-
- // Steps 19.b-c.
- toRound = {0, 0, 0, 0, 0, 0, 0, 0, 0, nanoseconds};
- roundedTime = &nanoseconds;
- break;
- }
- }
+ // Steps 13-18.
+ auto rounded = RoundNormalizedTimeDurationToIncrement(
+ duration, unit, increment, roundingMode);
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(rounded));
- // clang-format off
- //
- // The specification uses mathematical values in its computations, which
- // requires to be able to represent decimals with arbitrary precision. To
- // avoid having to struggle with decimals, we can transform the steps to work
- // on integer values, which we can conveniently represent with BigInts.
- //
- // As an example here are the transformation steps for "hours", but all other
- // units can be handled similarly.
- //
- // Relevant spec steps:
- //
- // 8.a Let fractionalSeconds be nanoseconds × 10^9 + microseconds × 10^6 + milliseconds × 10^3 + seconds.
- // ...
- // 14.a Let fractionalHours be (fractionalSeconds / 60 + minutes) / 60 + hours.
- // 14.b Set hours to ? RoundNumberToIncrement(fractionalHours, increment, roundingMode).
- //
- // And from RoundNumberToIncrement:
- //
- // 1. Let quotient be x / increment.
- // 2-7. Let rounded be op(quotient).
- // 8. Return rounded × increment.
- //
- // With `fractionalHours = (totalNs / nsPerHour)`, the rounding operation
- // computes:
- //
- // op(fractionalHours / increment) × increment
- // = op((totalNs / nsPerHour) / increment) × increment
- // = op(totalNs / (nsPerHour × increment)) × increment
- //
- // So when we pass `totalNs` and `nsPerHour` as separate arguments to
- // RoundNumberToIncrement, we can avoid any precision losses and instead
- // compute with exact values.
- //
- // clang-format on
-
- double total = 0;
- if (computeRemainder == ComputeRemainder::No) {
- if (!RoundNumberToIncrement(cx, toRound, unit, increment, roundingMode,
- roundedTime)) {
- return false;
- }
- } else {
- // clang-format off
- //
- // The remainder is only used for Duration.prototype.total(), which calls
- // this operation with increment=1 and roundingMode=trunc.
- //
- // That means the remainder computation is actually just
- // `(totalNs % toNanos) / toNanos`, where `totalNs % toNanos` is already
- // computed in RoundNumberToIncrement():
- //
- // rounded = trunc(totalNs / toNanos)
- // = [totalNs / toNanos]
- //
- // roundedTime = ℝ(𝔽(rounded))
- //
- // remainder = (totalNs - (rounded * toNanos)) / toNanos
- // = (totalNs - ([totalNs / toNanos] * toNanos)) / toNanos
- // = (totalNs % toNanos) / toNanos
- //
- // When used in Duration.prototype.total(), the overall computed value is
- // `[totalNs / toNanos] + (totalNs % toNanos) / toNanos`.
- //
- // Applying normal math rules would allow to simplify this to:
- //
- // [totalNs / toNanos] + (totalNs % toNanos) / toNanos
- // = [totalNs / toNanos] + (totalNs - [totalNs / toNanos] * toNanos) / toNanos
- // = total / toNanos
- //
- // We can't apply this simplification because it'd introduce double
- // precision issues. Instead of that, we use a specialized version of
- // RoundNumberToIncrement which directly returns the remainder. The
- // remainder `(totalNs % toNanos) / toNanos` is a value near zero, so this
- // approach is as exact as possible. (Double numbers near zero can be
- // computed more precisely than large numbers with fractional parts.)
- //
- // clang-format on
-
- MOZ_ASSERT(increment == Increment{1});
- MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
-
- if (!TruncateNumber(cx, toRound, unit, roundedTime, &total)) {
- return false;
- }
- }
-
- MOZ_ASSERT(years == duration.years);
- MOZ_ASSERT(months == duration.months);
- MOZ_ASSERT(weeks == duration.weeks);
- MOZ_ASSERT(IsIntegerOrInfinity(days));
-
- // Step 20.
- Duration resultDuration = {years, months, weeks, days,
- hours, minutes, seconds, milliseconds,
- microseconds, nanoseconds};
- if (!ThrowIfInvalidDuration(cx, resultDuration)) {
- return false;
- }
-
- // Step 21.
- *result = {resultDuration, total};
- return true;
+ // Step 19.
+ return rounded;
}
/**
- * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
- * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
- * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
- * precalculatedPlainDateTime ] ] ] ] ] )
+ * RoundDuration ( years, months, weeks, days, norm, increment, unit,
+ * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
+ * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
*/
-static bool RoundDuration(JSContext* cx, const Duration& duration,
- Increment increment, TemporalUnit unit,
- TemporalRoundingMode roundingMode, double* result) {
- MOZ_ASSERT(IsValidDuration(duration));
-
- // Only called from |Duration_total|, which always passes |increment=1| and
- // |roundingMode=trunc|.
- MOZ_ASSERT(increment == Increment{1});
- MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
+bool js::temporal::RoundDuration(JSContext* cx,
+ const NormalizedTimeDuration& duration,
+ Increment increment, TemporalUnit unit,
+ TemporalRoundingMode roundingMode,
+ NormalizedTimeDuration* result) {
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
+ MOZ_ASSERT(unit > TemporalUnit::Day);
- RoundedDuration rounded;
- if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
- ComputeRemainder::Yes, &rounded)) {
- return false;
- }
+ // Steps 1-12. (Not applicable)
- *result = rounded.total;
- return true;
+ // Steps 13-19.
+ return RoundNormalizedTimeDurationToIncrement(cx, duration, unit, increment,
+ roundingMode, result);
}
-/**
- * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
- * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
- * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
- * precalculatedPlainDateTime ] ] ] ] ] )
- */
-static bool RoundDuration(JSContext* cx, const Duration& duration,
- Increment increment, TemporalUnit unit,
- TemporalRoundingMode roundingMode, Duration* result) {
- MOZ_ASSERT(IsValidDuration(duration));
+#ifdef DEBUG
+// Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
+static constexpr int64_t MaxDurationDays = (int64_t(1) << 53) / (24 * 60 * 60);
- RoundedDuration rounded;
- if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
- ComputeRemainder::No, &rounded)) {
- return false;
- }
+// Maximum number of days in |FractionalDays|.
+static constexpr int64_t MaxFractionalDays =
+ 2 * MaxDurationDays + 2 * MaxEpochDaysDuration;
+#endif
- *result = rounded.duration;
- return true;
-}
+struct FractionalDays final {
+ int64_t days = 0;
+ int64_t time = 0;
+ int64_t dayLength = 0;
-/**
- * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
- * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
- * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
- * precalculatedPlainDateTime ] ] ] ] ] )
- */
-bool js::temporal::RoundDuration(JSContext* cx, const Duration& duration,
- Increment increment, TemporalUnit unit,
- TemporalRoundingMode roundingMode,
- Duration* result) {
- MOZ_ASSERT(IsValidDuration(duration));
+ FractionalDays() = default;
- return ::RoundDuration(cx, duration, increment, unit, roundingMode, result);
-}
+ explicit FractionalDays(int64_t durationDays,
+ const NormalizedTimeAndDays& timeAndDays)
+ : days(durationDays + timeAndDays.days),
+ time(timeAndDays.time),
+ dayLength(timeAndDays.dayLength) {
+ MOZ_ASSERT(std::abs(durationDays) <= MaxDurationDays);
+ MOZ_ASSERT(std::abs(timeAndDays.days) <= MaxDurationDays);
+ MOZ_ASSERT(std::abs(days) <= MaxFractionalDays);
-static mozilla::Maybe<int64_t> DaysFrom(
- const temporal::NanosecondsAndDays& nanosAndDays) {
- if (auto* days = nanosAndDays.days) {
- int64_t daysInt;
- if (BigInt::isInt64(days, &daysInt)) {
- return mozilla::Some(daysInt);
- }
- return mozilla::Nothing();
- }
- return mozilla::Some(nanosAndDays.daysInt);
-}
+ // NormalizedTimeDurationToDays guarantees that |dayLength| is strictly
+ // positive and less than 2**53.
+ MOZ_ASSERT(dayLength > 0);
+ MOZ_ASSERT(dayLength < int64_t(1) << 53);
-static BigInt* DaysFrom(JSContext* cx,
- Handle<temporal::NanosecondsAndDays> nanosAndDays) {
- if (auto days = nanosAndDays.days()) {
- return days;
+ // NormalizedTimeDurationToDays guarantees that |abs(timeAndDays.time)| is
+ // less than |timeAndDays.dayLength|.
+ MOZ_ASSERT(std::abs(time) < dayLength);
}
- return BigInt::createFromInt64(cx, nanosAndDays.daysInt());
-}
-
-static bool TruncateDays(JSContext* cx,
- Handle<temporal::NanosecondsAndDays> nanosAndDays,
- double days, int32_t daysToAdd, double* result) {
- do {
- int64_t intDays;
- if (!mozilla::NumberEqualsInt64(days, &intDays)) {
- break;
- }
- auto nanoDays = DaysFrom(nanosAndDays);
- if (!nanoDays) {
- break;
- }
+ FractionalDays operator+=(int32_t epochDays) {
+ MOZ_ASSERT(std::abs(epochDays) <= MaxEpochDaysDuration);
+ days += epochDays;
+ MOZ_ASSERT(std::abs(days) <= MaxFractionalDays);
+ return *this;
+ }
- auto totalDays = mozilla::CheckedInt64(intDays);
- totalDays += *nanoDays;
- totalDays += daysToAdd;
- if (!totalDays.isValid()) {
- break;
- }
+ FractionalDays operator-=(int32_t epochDays) {
+ MOZ_ASSERT(std::abs(epochDays) <= MaxEpochDaysDuration);
+ days -= epochDays;
+ MOZ_ASSERT(std::abs(days) <= MaxFractionalDays);
+ return *this;
+ }
- int64_t truncatedDays = totalDays.value();
- if (nanosAndDays.nanoseconds() > InstantSpan{}) {
+ int64_t truncate() const {
+ int64_t truncatedDays = days;
+ if (time > 0) {
// Round toward positive infinity when the integer days are negative and
// the fractional part is positive.
if (truncatedDays < 0) {
truncatedDays += 1;
}
- } else if (nanosAndDays.nanoseconds() < InstantSpan{}) {
+ } else if (time < 0) {
// Round toward negative infinity when the integer days are positive and
// the fractional part is negative.
if (truncatedDays > 0) {
truncatedDays -= 1;
}
}
-
- *result = double(truncatedDays);
- return true;
- } while (false);
-
- Rooted<BigInt*> biDays(cx, BigInt::createFromDouble(cx, days));
- if (!biDays) {
- return false;
- }
-
- Rooted<BigInt*> biNanoDays(cx, DaysFrom(cx, nanosAndDays));
- if (!biNanoDays) {
- return false;
- }
-
- Rooted<BigInt*> biDaysToAdd(cx, BigInt::createFromInt64(cx, daysToAdd));
- if (!biDaysToAdd) {
- return false;
- }
-
- Rooted<BigInt*> truncatedDays(cx, BigInt::add(cx, biDays, biNanoDays));
- if (!truncatedDays) {
- return false;
- }
-
- truncatedDays = BigInt::add(cx, truncatedDays, biDaysToAdd);
- if (!truncatedDays) {
- return false;
+ MOZ_ASSERT(std::abs(truncatedDays) <= MaxFractionalDays + 1);
+ return truncatedDays;
}
- if (nanosAndDays.nanoseconds() > InstantSpan{}) {
- // Round toward positive infinity when the integer days are negative and
- // the fractional part is positive.
- if (truncatedDays->isNegative()) {
- truncatedDays = BigInt::inc(cx, truncatedDays);
- if (!truncatedDays) {
- return false;
- }
- }
- } else if (nanosAndDays.nanoseconds() < InstantSpan{}) {
- // Round toward negative infinity when the integer days are positive and
- // the fractional part is negative.
- if (!truncatedDays->isNegative() && !truncatedDays->isZero()) {
- truncatedDays = BigInt::dec(cx, truncatedDays);
- if (!truncatedDays) {
- return false;
- }
+ int32_t sign() const {
+ if (days != 0) {
+ return days < 0 ? -1 : 1;
}
+ return time < 0 ? -1 : time > 0 ? 1 : 0;
}
-
- *result = BigInt::numberValue(truncatedDays);
- return true;
-}
-
-static bool DaysIsNegative(double days,
- Handle<temporal::NanosecondsAndDays> nanosAndDays,
- int32_t daysToAdd) {
- // Numbers of days between nsMinInstant and nsMaxInstant.
- static constexpr int32_t epochDays = 200'000'000;
-
- MOZ_ASSERT(std::abs(daysToAdd) <= epochDays * 2);
-
- // We don't need the exact value, so it's safe to convert from BigInt.
- double nanoDays = nanosAndDays.daysNumber();
-
- // When non-zero |days| and |nanoDays| have oppositive signs, the absolute
- // value of |days| is less-or-equal to |epochDays|. That means when adding
- // |days + nanoDays| we don't have to worry about a case like:
- //
- // days = 9007199254740991 and
- // nanoDays = 𝔽(-9007199254740993) = -9007199254740992
- //
- // ℝ(𝔽(days) + 𝔽(nanoDays)) is -1, whereas the correct result is -2.
- MOZ_ASSERT((days <= 0 && nanoDays <= 0) || (days >= 0 && nanoDays >= 0) ||
- std::abs(days) <= epochDays);
-
- // This addition can be imprecise, so |daysApproximation| is only an
- // approximation of the actual value.
- double daysApproximation = days + nanoDays;
-
- if (std::abs(daysApproximation) <= epochDays * 2) {
- int32_t intDays = int32_t(daysApproximation) + daysToAdd;
- return intDays < 0 ||
- (intDays == 0 && nanosAndDays.nanoseconds() < InstantSpan{});
- }
-
- // |daysApproximation| is too large, adding |daysToAdd| and |daysToSubtract|
- // doesn't change the sign.
- return daysApproximation < 0;
-}
-
-struct RoundedNumber {
- double rounded;
- double total;
};
-static bool RoundNumberToIncrementSlow(
- JSContext* cx, double durationAmount, double amountPassed,
- double durationDays, int32_t daysToAdd,
- Handle<temporal::NanosecondsAndDays> nanosAndDays, int32_t oneUnitDays,
- Increment increment, TemporalRoundingMode roundingMode,
- ComputeRemainder computeRemainder, RoundedNumber* result) {
- MOZ_ASSERT(nanosAndDays.dayLength() > InstantSpan{});
- MOZ_ASSERT(nanosAndDays.nanoseconds().abs() < nanosAndDays.dayLength().abs());
- MOZ_ASSERT(oneUnitDays != 0);
-
- Rooted<BigInt*> biAmount(cx, BigInt::createFromDouble(cx, durationAmount));
- if (!biAmount) {
- return false;
- }
-
- Rooted<BigInt*> biAmountPassed(cx,
- BigInt::createFromDouble(cx, amountPassed));
- if (!biAmountPassed) {
- return false;
- }
+struct Fraction final {
+ int64_t numerator = 0;
+ int32_t denominator = 0;
- biAmount = BigInt::add(cx, biAmount, biAmountPassed);
- if (!biAmount) {
- return false;
- }
+ constexpr Fraction() = default;
- Rooted<BigInt*> days(cx, BigInt::createFromDouble(cx, durationDays));
- if (!days) {
- return false;
- }
-
- Rooted<BigInt*> nanoDays(cx, DaysFrom(cx, nanosAndDays));
- if (!nanoDays) {
- return false;
- }
-
- Rooted<BigInt*> biDaysToAdd(cx, BigInt::createFromInt64(cx, daysToAdd));
- if (!biDaysToAdd) {
- return false;
- }
-
- days = BigInt::add(cx, days, nanoDays);
- if (!days) {
- return false;
- }
-
- days = BigInt::add(cx, days, biDaysToAdd);
- if (!days) {
- return false;
- }
-
- Rooted<BigInt*> nanoseconds(
- cx, ToEpochNanoseconds(cx, nanosAndDays.nanoseconds()));
- if (!nanoseconds) {
- return false;
- }
-
- Rooted<BigInt*> dayLength(cx,
- ToEpochNanoseconds(cx, nanosAndDays.dayLength()));
- if (!dayLength) {
- return false;
- }
-
- Rooted<BigInt*> denominator(
- cx, BigInt::createFromInt64(cx, std::abs(oneUnitDays)));
- if (!denominator) {
- return false;
- }
-
- denominator = BigInt::mul(cx, denominator, dayLength);
- if (!denominator) {
- return false;
- }
-
- Rooted<BigInt*> totalNanoseconds(cx, BigInt::mul(cx, days, dayLength));
- if (!totalNanoseconds) {
- return false;
- }
-
- totalNanoseconds = BigInt::add(cx, totalNanoseconds, nanoseconds);
- if (!totalNanoseconds) {
- return false;
- }
-
- Rooted<BigInt*> amountNanos(cx, BigInt::mul(cx, biAmount, denominator));
- if (!amountNanos) {
- return false;
- }
-
- totalNanoseconds = BigInt::add(cx, totalNanoseconds, amountNanos);
- if (!totalNanoseconds) {
- return false;
+ constexpr Fraction(int64_t numerator, int32_t denominator)
+ : numerator(numerator), denominator(denominator) {
+ MOZ_ASSERT(denominator > 0);
}
+};
- double rounded;
+struct RoundedNumber final {
+ Int128 rounded;
double total = 0;
- if (computeRemainder == ComputeRemainder::No) {
- if (!temporal::RoundNumberToIncrement(cx, totalNanoseconds, denominator,
- increment, roundingMode, &rounded)) {
- return false;
- }
- } else {
- if (!::TruncateNumber(cx, totalNanoseconds, denominator, &rounded,
- &total)) {
- return false;
- }
- }
-
- *result = {rounded, total};
- return true;
-}
+};
-static bool RoundNumberToIncrement(
- JSContext* cx, double durationAmount, double amountPassed,
- double durationDays, int32_t daysToAdd,
- Handle<temporal::NanosecondsAndDays> nanosAndDays, int32_t oneUnitDays,
+static RoundedNumber RoundNumberToIncrement(
+ const Fraction& fraction, const FractionalDays& fractionalDays,
Increment increment, TemporalRoundingMode roundingMode,
- ComputeRemainder computeRemainder, RoundedNumber* result) {
- MOZ_ASSERT(nanosAndDays.dayLength() > InstantSpan{});
- MOZ_ASSERT(nanosAndDays.nanoseconds().abs() < nanosAndDays.dayLength().abs());
- MOZ_ASSERT(oneUnitDays != 0);
-
- // TODO(anba): Rename variables.
+ ComputeRemainder computeRemainder) {
+ MOZ_ASSERT(std::abs(fraction.numerator) < (int64_t(1) << 32) * 2);
+ MOZ_ASSERT(fraction.denominator > 0);
+ MOZ_ASSERT(fraction.denominator <= MaxEpochDaysDuration);
+ MOZ_ASSERT(std::abs(fractionalDays.days) <= MaxFractionalDays);
+ MOZ_ASSERT(fractionalDays.dayLength > 0);
+ MOZ_ASSERT(fractionalDays.dayLength < (int64_t(1) << 53));
+ MOZ_ASSERT(std::abs(fractionalDays.time) < fractionalDays.dayLength);
+ MOZ_ASSERT(increment <= Increment::max());
// clang-format off
//
@@ -4373,7 +3339,7 @@ static bool RoundNumberToIncrement(
//
// where days' = days + nanoseconds / dayLength.
//
- // The fractional part |nanoseconds / dayLength| is from step 4.
+ // The fractional part |nanoseconds / dayLength| is from step 7.
//
// The denominator for |fractionalWeeks| is |dayLength * abs(oneWeekDays)|.
//
@@ -4382,236 +3348,259 @@ static bool RoundNumberToIncrement(
// = weeks + days / abs(oneWeekDays) + nanoseconds / (dayLength * abs(oneWeekDays))
// = (weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds) / (dayLength * abs(oneWeekDays))
//
+ // Because |abs(nanoseconds / dayLength) < 0|, this operation can be rewritten
+ // to omit the multiplication by |dayLength| when the rounding conditions are
+ // appropriately modified to account for the |nanoseconds / dayLength| part.
+ // This allows to implement rounding using only int64 values.
+ //
+ // This optimization is currently only implemented when |nanoseconds| is zero.
+ //
+ // Example how to expand this optimization for non-zero |nanoseconds|:
+ //
+ // |Round(fraction / increment) * increment| with:
+ // fraction = numerator / denominator
+ // numerator = weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds
+ // denominator = dayLength * abs(oneWeekDays)
+ //
+ // When ignoring the |nanoseconds / dayLength| part, this can be simplified to:
+ //
+ // |Round(fraction / increment) * increment| with:
+ // fraction = numerator / denominator
+ // numerator = weeks * abs(oneWeekDays) + days
+ // denominator = abs(oneWeekDays)
+ //
+ // Where:
+ // fraction / increment
+ // = (numerator / denominator) / increment
+ // = numerator / (denominator * increment)
+ //
+ // And |numerator| and |denominator * increment| both fit into int64.
+ //
+ // The "ceiling" operation has to be modified from:
+ //
+ // CeilDiv(dividend, divisor)
+ // quot, rem = dividend / divisor
+ // return quot + (rem > 0)
+ //
+ // To:
+ //
+ // CeilDiv(dividend, divisor, fractional)
+ // quot, rem = dividend / divisor
+ // return quot + ((rem > 0) || (fractional > 0))
+ //
+ // To properly account for the fractional |nanoseconds| part. Alternatively
+ // |dividend| can be modified before calling `CeilDiv`.
+ //
// clang-format on
- do {
- auto nanoseconds = nanosAndDays.nanoseconds().toNanoseconds();
- if (!nanoseconds.isValid()) {
- break;
- }
+ if (fractionalDays.time == 0) {
+ auto [numerator, denominator] = fraction;
+ int64_t totalDays = fractionalDays.days + denominator * numerator;
- auto dayLength = nanosAndDays.dayLength().toNanoseconds();
- if (!dayLength.isValid()) {
- break;
+ if (computeRemainder == ComputeRemainder::Yes) {
+ constexpr auto rounded = Int128{0};
+ double total = FractionToDouble(totalDays, denominator);
+ return {rounded, total};
}
- auto denominator = dayLength * std::abs(oneUnitDays);
- if (!denominator.isValid()) {
- break;
- }
-
- int64_t intDays;
- if (!mozilla::NumberEqualsInt64(durationDays, &intDays)) {
- break;
- }
+ auto rounded =
+ RoundNumberToIncrement(totalDays, denominator, increment, roundingMode);
+ constexpr double total = 0;
+ return {rounded, total};
+ }
- auto nanoDays = DaysFrom(nanosAndDays);
- if (!nanoDays) {
- break;
- }
+ do {
+ auto dayLength = mozilla::CheckedInt64(fractionalDays.dayLength);
- auto totalDays = mozilla::CheckedInt64(intDays);
- totalDays += *nanoDays;
- totalDays += daysToAdd;
- if (!totalDays.isValid()) {
+ auto denominator = dayLength * fraction.denominator;
+ if (!denominator.isValid()) {
break;
}
- auto totalNanoseconds = dayLength * totalDays;
- if (!totalNanoseconds.isValid()) {
+ auto amountNanos = denominator * fraction.numerator;
+ if (!amountNanos.isValid()) {
break;
}
- totalNanoseconds += nanoseconds;
+ auto totalNanoseconds = dayLength * fractionalDays.days;
+ totalNanoseconds += fractionalDays.time;
+ totalNanoseconds += amountNanos;
if (!totalNanoseconds.isValid()) {
break;
}
- int64_t intAmount;
- if (!mozilla::NumberEqualsInt64(durationAmount, &intAmount)) {
- break;
+ if (computeRemainder == ComputeRemainder::Yes) {
+ constexpr auto rounded = Int128{0};
+ double total =
+ FractionToDouble(totalNanoseconds.value(), denominator.value());
+ return {rounded, total};
}
- int64_t intAmountPassed;
- if (!mozilla::NumberEqualsInt64(amountPassed, &intAmountPassed)) {
- break;
- }
-
- auto totalAmount = mozilla::CheckedInt64(intAmount) + intAmountPassed;
- if (!totalAmount.isValid()) {
- break;
- }
+ auto rounded = RoundNumberToIncrement(
+ totalNanoseconds.value(), denominator.value(), increment, roundingMode);
+ constexpr double total = 0;
+ return {rounded, total};
+ } while (false);
- auto amountNanos = denominator * totalAmount;
- if (!amountNanos.isValid()) {
- break;
- }
+ // Use int128 when values are too large for int64. Additionally assert all
+ // values fit into int128.
- totalNanoseconds += amountNanos;
- if (!totalNanoseconds.isValid()) {
- break;
- }
+ // `dayLength` < 2**53
+ auto dayLength = Int128{fractionalDays.dayLength};
+ MOZ_ASSERT(dayLength < Int128{1} << 53);
- double rounded;
- double total = 0;
- if (computeRemainder == ComputeRemainder::No) {
- if (!temporal::RoundNumberToIncrement(cx, totalNanoseconds.value(),
- denominator.value(), increment,
- roundingMode, &rounded)) {
- return false;
- }
- } else {
- TruncateNumber(totalNanoseconds.value(), denominator.value(), &rounded,
- &total);
- }
+ // `fraction.denominator` < MaxEpochDaysDuration
+ // log2(MaxEpochDaysDuration) = ~27.57.
+ auto denominator = dayLength * Int128{fraction.denominator};
+ MOZ_ASSERT(denominator < Int128{1} << (53 + 28));
- *result = {rounded, total};
- return true;
- } while (false);
+ // log2(24*60*60) = ~16.4 and log2(2 * MaxEpochDaysDuration) = ~28.57.
+ //
+ // `abs(MaxFractionalDays)`
+ // = `abs(2 * MaxDurationDays + 2 * MaxEpochDaysDuration)`
+ // = `abs(2 * 2**(53 - 16) + 2 * MaxEpochDaysDuration)`
+ // ≤ 2 * 2**37 + 2**29
+ // ≤ 2**39
+ auto totalDays = Int128{fractionalDays.days};
+ MOZ_ASSERT(totalDays.abs() <= Uint128{1} << 39);
+
+ // `abs(fraction.numerator)` ≤ (2**33)
+ auto totalAmount = Int128{fraction.numerator};
+ MOZ_ASSERT(totalAmount.abs() <= Uint128{1} << 33);
+
+ // `denominator` < 2**(53 + 28)
+ // `abs(totalAmount)` <= 2**33
+ //
+ // `denominator * totalAmount`
+ // ≤ 2**(53 + 28) * 2**33
+ // = 2**(53 + 28 + 33)
+ // = 2**114
+ auto amountNanos = denominator * totalAmount;
+ MOZ_ASSERT(amountNanos.abs() <= Uint128{1} << 114);
+
+ // `dayLength` < 2**53
+ // `totalDays` ≤ 2**39
+ // `fractionalDays.time` < `dayLength` < 2**53
+ // `amountNanos` ≤ 2**114
+ //
+ // `dayLength * totalDays`
+ // ≤ 2**(53 + 39) = 2**92
+ //
+ // `dayLength * totalDays + fractionalDays.time`
+ // ≤ 2**93
+ //
+ // `dayLength * totalDays + fractionalDays.time + amountNanos`
+ // ≤ 2**115
+ auto totalNanoseconds = dayLength * totalDays;
+ totalNanoseconds += Int128{fractionalDays.time};
+ totalNanoseconds += amountNanos;
+ MOZ_ASSERT(totalNanoseconds.abs() <= Uint128{1} << 115);
- return RoundNumberToIncrementSlow(
- cx, durationAmount, amountPassed, durationDays, daysToAdd, nanosAndDays,
- oneUnitDays, increment, roundingMode, computeRemainder, result);
-}
+ if (computeRemainder == ComputeRemainder::Yes) {
+ constexpr auto rounded = Int128{0};
+ double total = FractionToDouble(totalNanoseconds, denominator);
+ return {rounded, total};
+ }
-static bool RoundNumberToIncrement(
- JSContext* cx, double durationDays,
- Handle<temporal::NanosecondsAndDays> nanosAndDays, Increment increment,
- TemporalRoundingMode roundingMode, ComputeRemainder computeRemainder,
- RoundedNumber* result) {
- constexpr double daysAmount = 0;
- constexpr double daysPassed = 0;
- constexpr int32_t oneDayDays = 1;
- constexpr int32_t daysToAdd = 0;
-
- return RoundNumberToIncrement(cx, daysAmount, daysPassed, durationDays,
- daysToAdd, nanosAndDays, oneDayDays, increment,
- roundingMode, computeRemainder, result);
+ auto rounded = RoundNumberToIncrement(totalNanoseconds, denominator,
+ increment, roundingMode);
+ constexpr double total = 0;
+ return {rounded, total};
}
-static bool RoundDurationYear(JSContext* cx, const Duration& duration,
- Handle<temporal::NanosecondsAndDays> nanosAndDays,
+static bool RoundDurationYear(JSContext* cx, const NormalizedDuration& duration,
+ FractionalDays fractionalDays,
Increment increment,
TemporalRoundingMode roundingMode,
Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
Handle<CalendarRecord> calendar,
ComputeRemainder computeRemainder,
RoundedDuration* result) {
- // Numbers of days between nsMinInstant and nsMaxInstant.
- static constexpr int32_t epochDays = 200'000'000;
+ auto [years, months, weeks, days] = duration.date;
- double years = duration.years;
- double months = duration.months;
- double weeks = duration.weeks;
- double days = duration.days;
-
- // Step 10.a.
- Duration yearsDuration = {years};
+ // Step 9.a.
+ auto yearsDuration = DateDuration{years};
- // Step 10.b.
+ // Step 9.b.
auto yearsLater = AddDate(cx, calendar, dateRelativeTo, yearsDuration);
if (!yearsLater) {
return false;
}
auto yearsLaterDate = ToPlainDate(&yearsLater.unwrap());
- // Step 10.f. (Reordered)
+ // Step 9.f. (Reordered)
Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx, yearsLater);
- // Step 10.c.
- Duration yearsMonthsWeeks = {years, months, weeks};
+ // Step 9.c.
+ auto yearsMonthsWeeks = DateDuration{years, months, weeks};
- // Step 10.d.
+ // Step 9.d.
PlainDate yearsMonthsWeeksLater;
if (!AddDate(cx, calendar, dateRelativeTo, yearsMonthsWeeks,
&yearsMonthsWeeksLater)) {
return false;
}
- // Step 10.e.
+ // Step 9.e.
int32_t monthsWeeksInDays = DaysUntil(yearsLaterDate, yearsMonthsWeeksLater);
- MOZ_ASSERT(std::abs(monthsWeeksInDays) <= epochDays);
+ MOZ_ASSERT(std::abs(monthsWeeksInDays) <= MaxEpochDaysDuration);
- // Step 10.f. (Moved up)
+ // Step 9.f. (Moved up)
- // Step 10.g.
- // Our implementation keeps |days| and |monthsWeeksInDays| separate.
+ // Step 9.g.
+ fractionalDays += monthsWeeksInDays;
// FIXME: spec issue - truncation doesn't match the spec polyfill.
// https://github.com/tc39/proposal-temporal/issues/2540
- // Step 10.h.
- double truncatedDays;
- if (!TruncateDays(cx, nanosAndDays, days, monthsWeeksInDays,
- &truncatedDays)) {
- return false;
- }
-
- // FIXME: spec bug - truncated days can be infinity:
- //
- // Temporal.Duration.from({
- // days: Number.MAX_VALUE,
- // hours: Number.MAX_VALUE,
- // }).round({
- // smallestUnit: "years",
- // relativeTo: "1970-01-01",
- // });
- if (!IsInteger(truncatedDays)) {
- MOZ_ASSERT(std::isinf(truncatedDays));
- JS_ReportErrorASCII(cx, "truncated days is infinity");
- return false;
- }
-
+ // Step 9.h.
PlainDate isoResult;
- if (!AddISODate(cx, yearsLaterDate, {0, 0, 0, truncatedDays},
- TemporalOverflow::Constrain, &isoResult)) {
+ if (!BalanceISODate(cx, yearsLaterDate, fractionalDays.truncate(),
+ &isoResult)) {
return false;
}
- // Step 10.i.
+ // Step 9.i.
Rooted<PlainDateObject*> wholeDaysLater(
cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
if (!wholeDaysLater) {
return false;
}
- // Steps 10.j-l.
- Duration timePassed;
+ // Steps 9.j-l.
+ DateDuration timePassed;
if (!DifferenceDate(cx, calendar, newRelativeTo, wholeDaysLater,
TemporalUnit::Year, &timePassed)) {
return false;
}
- // Step 10.m.
- double yearsPassed = timePassed.years;
+ // Step 9.m.
+ int64_t yearsPassed = timePassed.years;
- // Step 10.n.
- // Our implementation keeps |years| and |yearsPassed| separate.
+ // Step 9.n.
+ years += yearsPassed;
- // Step 10.o.
- Duration yearsPassedDuration = {yearsPassed};
+ // Step 9.o.
+ auto yearsPassedDuration = DateDuration{yearsPassed};
- // Steps 10.p-r.
+ // Steps 9.p-r.
int32_t daysPassed;
if (!MoveRelativeDate(cx, calendar, newRelativeTo, yearsPassedDuration,
&newRelativeTo, &daysPassed)) {
return false;
}
- MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
+ MOZ_ASSERT(std::abs(daysPassed) <= MaxEpochDaysDuration);
- // Step 10.s.
- //
- // Our implementation keeps |days| and |daysPassed| separate.
- int32_t daysToAdd = monthsWeeksInDays - daysPassed;
- MOZ_ASSERT(std::abs(daysToAdd) <= epochDays * 2);
+ // Step 9.s.
+ fractionalDays -= daysPassed;
- // Steps 10.t.
- double sign = DaysIsNegative(days, nanosAndDays, daysToAdd) ? -1 : 1;
+ // Steps 9.t.
+ int32_t sign = fractionalDays.sign() < 0 ? -1 : 1;
- // Step 10.u.
- Duration oneYear = {sign};
+ // Step 9.u.
+ auto oneYear = DateDuration{sign};
- // Steps 10.v-w.
+ // Steps 9.v-w.
Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
int32_t oneYearDays;
if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneYear,
@@ -4619,158 +3608,137 @@ static bool RoundDurationYear(JSContext* cx, const Duration& duration,
return false;
}
- // Step 10.x.
+ // Step 9.x.
if (oneYearDays == 0) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_INVALID_NUMBER, "days");
return false;
}
- // Steps 10.y-aa.
- RoundedNumber rounded;
- if (!RoundNumberToIncrement(cx, years, yearsPassed, days, daysToAdd,
- nanosAndDays, oneYearDays, increment,
- roundingMode, computeRemainder, &rounded)) {
- return false;
- }
- auto [numYears, total] = rounded;
+ // Steps 9.y.
+ auto fractionalYears = Fraction{years, std::abs(oneYearDays)};
- // Step 10.ab.
- double numMonths = 0;
- double numWeeks = 0;
+ // Steps 9.z-aa.
+ auto [numYears, total] =
+ RoundNumberToIncrement(fractionalYears, fractionalDays, increment,
+ roundingMode, computeRemainder);
- // Step 20.
- Duration resultDuration = {numYears, numMonths, numWeeks};
+ // Step 9.ab.
+ int64_t numMonths = 0;
+ int64_t numWeeks = 0;
+
+ // Step 9.ac.
+ constexpr auto time = NormalizedTimeDuration{};
+
+ // Step 19.
+ if (numYears.abs() >= (Uint128{1} << 32)) {
+ return ThrowInvalidDurationPart(cx, double(numYears), "years",
+ JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
+ }
+
+ auto resultDuration = DateDuration{int64_t(numYears), numMonths, numWeeks};
if (!ThrowIfInvalidDuration(cx, resultDuration)) {
return false;
}
- // Step 21.
- *result = {resultDuration, total};
+ *result = {{resultDuration, time}, total};
return true;
}
-static bool RoundDurationMonth(
- JSContext* cx, const Duration& duration,
- Handle<temporal::NanosecondsAndDays> nanosAndDays, Increment increment,
- TemporalRoundingMode roundingMode,
- Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
- Handle<CalendarRecord> calendar, ComputeRemainder computeRemainder,
- RoundedDuration* result) {
- // Numbers of days between nsMinInstant and nsMaxInstant.
- static constexpr int32_t epochDays = 200'000'000;
+static bool RoundDurationMonth(JSContext* cx,
+ const NormalizedDuration& duration,
+ FractionalDays fractionalDays,
+ Increment increment,
+ TemporalRoundingMode roundingMode,
+ Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
+ Handle<CalendarRecord> calendar,
+ ComputeRemainder computeRemainder,
+ RoundedDuration* result) {
+ auto [years, months, weeks, days] = duration.date;
- double years = duration.years;
- double months = duration.months;
- double weeks = duration.weeks;
- double days = duration.days;
-
- // Step 11.a.
- Duration yearsMonths = {years, months};
+ // Step 10.a.
+ auto yearsMonths = DateDuration{years, months};
- // Step 11.b.
+ // Step 10.b.
auto yearsMonthsLater = AddDate(cx, calendar, dateRelativeTo, yearsMonths);
if (!yearsMonthsLater) {
return false;
}
auto yearsMonthsLaterDate = ToPlainDate(&yearsMonthsLater.unwrap());
- // Step 11.f. (Reordered)
+ // Step 10.f. (Reordered)
Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx, yearsMonthsLater);
- // Step 11.c.
- Duration yearsMonthsWeeks = {years, months, weeks};
+ // Step 10.c.
+ auto yearsMonthsWeeks = DateDuration{years, months, weeks};
- // Step 11.d.
+ // Step 10.d.
PlainDate yearsMonthsWeeksLater;
if (!AddDate(cx, calendar, dateRelativeTo, yearsMonthsWeeks,
&yearsMonthsWeeksLater)) {
return false;
}
- // Step 11.e.
+ // Step 10.e.
int32_t weeksInDays = DaysUntil(yearsMonthsLaterDate, yearsMonthsWeeksLater);
- MOZ_ASSERT(std::abs(weeksInDays) <= epochDays);
+ MOZ_ASSERT(std::abs(weeksInDays) <= MaxEpochDaysDuration);
- // Step 11.f. (Moved up)
+ // Step 10.f. (Moved up)
- // Step 11.g.
- // Our implementation keeps |days| and |weeksInDays| separate.
+ // Step 10.g.
+ fractionalDays += weeksInDays;
// FIXME: spec issue - truncation doesn't match the spec polyfill.
// https://github.com/tc39/proposal-temporal/issues/2540
- // Step 11.h.
- double truncatedDays;
- if (!TruncateDays(cx, nanosAndDays, days, weeksInDays, &truncatedDays)) {
- return false;
- }
-
- // FIXME: spec bug - truncated days can be infinity:
- //
- // Temporal.Duration.from({
- // days: Number.MAX_VALUE,
- // hours: Number.MAX_VALUE,
- // }).round({
- // smallestUnit: "months",
- // relativeTo: "1970-01-01",
- // });
- if (!IsInteger(truncatedDays)) {
- MOZ_ASSERT(std::isinf(truncatedDays));
- JS_ReportErrorASCII(cx, "truncated days is infinity");
- return false;
- }
-
+ // Step 10.h.
PlainDate isoResult;
- if (!AddISODate(cx, yearsMonthsLaterDate, {0, 0, 0, truncatedDays},
- TemporalOverflow::Constrain, &isoResult)) {
+ if (!BalanceISODate(cx, yearsMonthsLaterDate, fractionalDays.truncate(),
+ &isoResult)) {
return false;
}
- // Step 11.i.
+ // Step 10.i.
Rooted<PlainDateObject*> wholeDaysLater(
cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
if (!wholeDaysLater) {
return false;
}
- // Steps 11.j-l.
- Duration timePassed;
+ // Steps 10.j-l.
+ DateDuration timePassed;
if (!DifferenceDate(cx, calendar, newRelativeTo, wholeDaysLater,
TemporalUnit::Month, &timePassed)) {
return false;
}
- // Step 11.m.
- double monthsPassed = timePassed.months;
+ // Step 10.m.
+ int64_t monthsPassed = timePassed.months;
- // Step 11.n.
- // Our implementation keeps |months| and |monthsPassed| separate.
+ // Step 10.n.
+ months += monthsPassed;
- // Step 11.o.
- Duration monthsPassedDuration = {0, monthsPassed};
+ // Step 10.o.
+ auto monthsPassedDuration = DateDuration{0, monthsPassed};
- // Steps 11.p-r.
+ // Steps 10.p-r.
int32_t daysPassed;
if (!MoveRelativeDate(cx, calendar, newRelativeTo, monthsPassedDuration,
&newRelativeTo, &daysPassed)) {
return false;
}
- MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
+ MOZ_ASSERT(std::abs(daysPassed) <= MaxEpochDaysDuration);
- // Step 11.s.
- //
- // Our implementation keeps |days| and |daysPassed| separate.
- int32_t daysToAdd = weeksInDays - daysPassed;
- MOZ_ASSERT(std::abs(daysToAdd) <= epochDays * 2);
+ // Step 10.s.
+ fractionalDays -= daysPassed;
- // Steps 11.t.
- double sign = DaysIsNegative(days, nanosAndDays, daysToAdd) ? -1 : 1;
+ // Steps 10.t.
+ int32_t sign = fractionalDays.sign() < 0 ? -1 : 1;
- // Step 11.u.
- Duration oneMonth = {0, sign};
+ // Step 10.u.
+ auto oneMonth = DateDuration{0, sign};
- // Steps 11.v-w.
+ // Steps 10.v-w.
Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
int32_t oneMonthDays;
if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneMonth,
@@ -4778,51 +3746,51 @@ static bool RoundDurationMonth(
return false;
}
- // Step 11.x.
+ // Step 10.x.
if (oneMonthDays == 0) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_INVALID_NUMBER, "days");
return false;
}
- // Steps 11.y-aa.
- RoundedNumber rounded;
- if (!RoundNumberToIncrement(cx, months, monthsPassed, days, daysToAdd,
- nanosAndDays, oneMonthDays, increment,
- roundingMode, computeRemainder, &rounded)) {
- return false;
- }
- auto [numMonths, total] = rounded;
+ // Step 10.y.
+ auto fractionalMonths = Fraction{months, std::abs(oneMonthDays)};
- // Step 11.ab.
- double numWeeks = 0;
+ // Steps 10.z-aa.
+ auto [numMonths, total] =
+ RoundNumberToIncrement(fractionalMonths, fractionalDays, increment,
+ roundingMode, computeRemainder);
- // Step 20.
- Duration resultDuration = {years, numMonths, numWeeks};
+ // Step 10.ab.
+ int64_t numWeeks = 0;
+
+ // Step 10.ac.
+ constexpr auto time = NormalizedTimeDuration{};
+
+ // Step 19.
+ if (numMonths.abs() >= (Uint128{1} << 32)) {
+ return ThrowInvalidDurationPart(cx, double(numMonths), "months",
+ JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
+ }
+
+ auto resultDuration = DateDuration{years, int64_t(numMonths), numWeeks};
if (!ThrowIfInvalidDuration(cx, resultDuration)) {
return false;
}
- // Step 21.
- *result = {resultDuration, total};
+ *result = {{resultDuration, time}, total};
return true;
}
-static bool RoundDurationWeek(JSContext* cx, const Duration& duration,
- Handle<temporal::NanosecondsAndDays> nanosAndDays,
+static bool RoundDurationWeek(JSContext* cx, const NormalizedDuration& duration,
+ FractionalDays fractionalDays,
Increment increment,
TemporalRoundingMode roundingMode,
Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
Handle<CalendarRecord> calendar,
ComputeRemainder computeRemainder,
RoundedDuration* result) {
- // Numbers of days between nsMinInstant and nsMaxInstant.
- static constexpr int32_t epochDays = 200'000'000;
-
- double years = duration.years;
- double months = duration.months;
- double weeks = duration.weeks;
- double days = duration.days;
+ auto [years, months, weeks, days] = duration.date;
auto* unwrappedRelativeTo = dateRelativeTo.unwrap(cx);
if (!unwrappedRelativeTo) {
@@ -4830,78 +3798,55 @@ static bool RoundDurationWeek(JSContext* cx, const Duration& duration,
}
auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
- // Step 12.a
- double truncatedDays;
- if (!TruncateDays(cx, nanosAndDays, days, 0, &truncatedDays)) {
- return false;
- }
-
- // FIXME: spec bug - truncated days can be infinity:
- //
- // Temporal.Duration.from({
- // days: Number.MAX_VALUE,
- // hours: Number.MAX_VALUE,
- // }).round({
- // smallestUnit: "weeks",
- // relativeTo: "1970-01-01",
- // });
- if (!IsInteger(truncatedDays)) {
- MOZ_ASSERT(std::isinf(truncatedDays));
- JS_ReportErrorASCII(cx, "truncated days is infinity");
- return false;
- }
-
+ // Step 11.a
PlainDate isoResult;
- if (!AddISODate(cx, relativeToDate, {0, 0, 0, truncatedDays},
- TemporalOverflow::Constrain, &isoResult)) {
+ if (!BalanceISODate(cx, relativeToDate, fractionalDays.truncate(),
+ &isoResult)) {
return false;
}
- // Step 12.b.
+ // Step 11.b.
Rooted<PlainDateObject*> wholeDaysLater(
cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
if (!wholeDaysLater) {
return false;
}
- // Steps 12.c-e.
- Duration timePassed;
+ // Steps 11.c-e.
+ DateDuration timePassed;
if (!DifferenceDate(cx, calendar, dateRelativeTo, wholeDaysLater,
TemporalUnit::Week, &timePassed)) {
return false;
}
- // Step 12.f.
- double weeksPassed = timePassed.weeks;
+ // Step 11.f.
+ int64_t weeksPassed = timePassed.weeks;
- // Step 12.g.
- // Our implementation keeps |weeks| and |weeksPassed| separate.
+ // Step 11.g.
+ weeks += weeksPassed;
- // Step 12.h.
- Duration weeksPassedDuration = {0, 0, weeksPassed};
+ // Step 11.h.
+ auto weeksPassedDuration = DateDuration{0, 0, weeksPassed};
- // Steps 12.i-k.
+ // Steps 11.i-k.
Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx);
int32_t daysPassed;
if (!MoveRelativeDate(cx, calendar, dateRelativeTo, weeksPassedDuration,
&newRelativeTo, &daysPassed)) {
return false;
}
- MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
+ MOZ_ASSERT(std::abs(daysPassed) <= MaxEpochDaysDuration);
- // Step 12.l.
- //
- // Our implementation keeps |days| and |daysPassed| separate.
- int32_t daysToAdd = -daysPassed;
- MOZ_ASSERT(std::abs(daysToAdd) <= epochDays);
+ // Step 11.l.
+ fractionalDays -= daysPassed;
- // Steps 12.m.
- double sign = DaysIsNegative(days, nanosAndDays, daysToAdd) ? -1 : 1;
+ // Steps 11.m.
+ int32_t sign = fractionalDays.sign() < 0 ? -1 : 1;
- // Step 12.n.
- Duration oneWeek = {0, 0, sign};
+ // Step 11.n.
+ auto oneWeek = DateDuration{0, 0, sign};
- // Steps 12.o-p.
+ // Steps 11.o-p.
Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
int32_t oneWeekDays;
if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneWeek,
@@ -4909,71 +3854,157 @@ static bool RoundDurationWeek(JSContext* cx, const Duration& duration,
return false;
}
- // Step 12.q.
+ // Step 11.q.
if (oneWeekDays == 0) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_INVALID_NUMBER, "days");
return false;
}
- // Steps 12.r-t.
- RoundedNumber rounded;
- if (!RoundNumberToIncrement(cx, weeks, weeksPassed, days, daysToAdd,
- nanosAndDays, oneWeekDays, increment,
- roundingMode, computeRemainder, &rounded)) {
- return false;
+ // Step 11.r.
+ auto fractionalWeeks = Fraction{weeks, std::abs(oneWeekDays)};
+
+ // Steps 11.s-t.
+ auto [numWeeks, total] =
+ RoundNumberToIncrement(fractionalWeeks, fractionalDays, increment,
+ roundingMode, computeRemainder);
+
+ // Step 11.u.
+ constexpr auto time = NormalizedTimeDuration{};
+
+ // Step 19.
+ if (numWeeks.abs() >= (Uint128{1} << 32)) {
+ return ThrowInvalidDurationPart(cx, double(numWeeks), "weeks",
+ JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
}
- auto [numWeeks, total] = rounded;
- // Step 20.
- Duration resultDuration = {years, months, numWeeks};
+ auto resultDuration = DateDuration{years, months, int64_t(numWeeks)};
if (!ThrowIfInvalidDuration(cx, resultDuration)) {
return false;
}
- // Step 21.
- *result = {resultDuration, total};
+ *result = {{resultDuration, time}, total};
return true;
}
-static bool RoundDurationDay(JSContext* cx, const Duration& duration,
- Handle<temporal::NanosecondsAndDays> nanosAndDays,
+static bool RoundDurationDay(JSContext* cx, const NormalizedDuration& duration,
+ const FractionalDays& fractionalDays,
Increment increment,
TemporalRoundingMode roundingMode,
ComputeRemainder computeRemainder,
RoundedDuration* result) {
- double years = duration.years;
- double months = duration.months;
- double weeks = duration.weeks;
- double days = duration.days;
+ auto [years, months, weeks, days] = duration.date;
+
+ // Pass zero fraction.
+ constexpr auto zero = Fraction{0, 1};
+
+ // Steps 12.a-b.
+ auto [numDays, total] = RoundNumberToIncrement(
+ zero, fractionalDays, increment, roundingMode, computeRemainder);
- // Steps 13.a-b.
- RoundedNumber rounded;
- if (!RoundNumberToIncrement(cx, days, nanosAndDays, increment, roundingMode,
- computeRemainder, &rounded)) {
+ MOZ_ASSERT(Int128{INT64_MIN} <= numDays && numDays <= Int128{INT64_MAX},
+ "rounded days fits in int64");
+
+ // Step 12.c.
+ constexpr auto time = NormalizedTimeDuration{};
+
+ // Step 19.
+ auto resultDuration = DateDuration{years, months, weeks, int64_t(numDays)};
+ if (!ThrowIfInvalidDuration(cx, resultDuration)) {
return false;
}
- auto [numDays, total] = rounded;
- // Step 20.
- Duration resultDuration = {years, months, weeks, numDays};
- if (!ThrowIfInvalidDuration(cx, resultDuration)) {
+ *result = {{resultDuration, time}, total};
+ return true;
+}
+
+/**
+ * RoundDuration ( years, months, weeks, days, norm, increment, unit,
+ * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
+ * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
+ */
+static bool RoundDuration(JSContext* cx, const NormalizedDuration& duration,
+ Increment increment, TemporalUnit unit,
+ TemporalRoundingMode roundingMode,
+ ComputeRemainder computeRemainder,
+ RoundedDuration* result) {
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(duration.time));
+ MOZ_ASSERT_IF(unit > TemporalUnit::Day, IsValidDuration(duration.date));
+
+ // The remainder is only needed when called from |Duration_total|. And `total`
+ // always passes |increment=1| and |roundingMode=trunc|.
+ MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
+ increment == Increment{1});
+ MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
+ roundingMode == TemporalRoundingMode::Trunc);
+
+ // Steps 1-5. (Not applicable.)
+
+ // Step 6.
+ if (unit <= TemporalUnit::Week) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
+ "relativeTo");
return false;
}
- // Step 21.
- *result = {resultDuration, total};
+ // TODO: We could directly return here if unit=nanoseconds and increment=1,
+ // because in that case this operation is a no-op. This case happens for
+ // example when calling Temporal.PlainTime.prototype.{since,until} without an
+ // options object.
+ //
+ // But maybe this can be even more efficiently handled in the callers. For
+ // example when Temporal.PlainTime.prototype.{since,until} is called without
+ // an options object, we can not only skip the RoundDuration call, but also
+ // the following BalanceTimeDuration call.
+
+ // Step 7. (Moved below.)
+
+ // Step 8. (Not applicable.)
+
+ // Steps 9-11. (Not applicable.)
+
+ // Step 12.
+ if (unit == TemporalUnit::Day) {
+ // Step 7.
+ auto timeAndDays = NormalizedTimeDurationToDays(duration.time);
+ auto fractionalDays = FractionalDays{duration.date.days, timeAndDays};
+
+ return RoundDurationDay(cx, duration, fractionalDays, increment,
+ roundingMode, computeRemainder, result);
+ }
+
+ MOZ_ASSERT(TemporalUnit::Hour <= unit && unit <= TemporalUnit::Nanosecond);
+
+ // Steps 13-18.
+ auto time = duration.time;
+ double total = 0;
+ if (computeRemainder == ComputeRemainder::No) {
+ if (!RoundNormalizedTimeDurationToIncrement(cx, time, unit, increment,
+ roundingMode, &time)) {
+ return false;
+ }
+ } else {
+ MOZ_ASSERT(increment == Increment{1});
+ MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
+
+ total = TotalNormalizedTimeDuration(duration.time, unit);
+ }
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(time));
+
+ // Step 19.
+ MOZ_ASSERT(IsValidDuration(duration.date));
+ *result = {{duration.date, time}, total};
return true;
}
/**
- * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
- * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
- * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
- * precalculatedPlainDateTime ] ] ] ] ] )
+ * RoundDuration ( years, months, weeks, days, norm, increment, unit,
+ * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
+ * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
*/
static bool RoundDuration(
- JSContext* cx, const Duration& duration, Increment increment,
+ JSContext* cx, const NormalizedDuration& duration, Increment increment,
TemporalUnit unit, TemporalRoundingMode roundingMode,
Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
@@ -4982,9 +4013,11 @@ static bool RoundDuration(
ComputeRemainder computeRemainder, RoundedDuration* result) {
// Note: |duration.days| can have a different sign than the other date
// components. The date and time components can have different signs, too.
- MOZ_ASSERT(
- IsValidDuration({duration.years, duration.months, duration.weeks}));
- MOZ_ASSERT(IsValidDuration(duration.time()));
+ MOZ_ASSERT(IsValidDuration(Duration{double(duration.date.years),
+ double(duration.date.months),
+ double(duration.date.weeks)}));
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(duration.time));
+ MOZ_ASSERT_IF(unit > TemporalUnit::Day, IsValidDuration(duration.date));
MOZ_ASSERT(plainRelativeTo || zonedRelativeTo,
"Use RoundDuration without relativeTo when plainRelativeTo and "
@@ -5040,69 +4073,61 @@ static bool RoundDuration(
MOZ_ASSERT(TemporalUnit::Year <= unit && unit <= TemporalUnit::Day);
// Steps 7.a-c.
- Rooted<temporal::NanosecondsAndDays> nanosAndDays(cx);
+ FractionalDays fractionalDays;
if (zonedRelativeTo) {
- // Step 7.b.i. (Reordered)
+ // Step 7.a.i.
Rooted<ZonedDateTime> intermediate(cx);
if (!MoveRelativeZonedDateTime(cx, zonedRelativeTo, calendar, timeZone,
- duration.date(), precalculatedPlainDateTime,
+ duration.date, precalculatedPlainDateTime,
&intermediate)) {
return false;
}
- // Steps 7.a and 7.b.ii.
- if (!NanosecondsToDays(cx, duration, intermediate, timeZone,
- &nanosAndDays)) {
+ // Steps 7.a.ii.
+ NormalizedTimeAndDays timeAndDays;
+ if (!NormalizedTimeDurationToDays(cx, duration.time, intermediate, timeZone,
+ &timeAndDays)) {
return false;
}
- // Step 7.b.iii. (Not applicable in our implementation.)
+ // Step 7.a.iii.
+ fractionalDays = FractionalDays{duration.date.days, timeAndDays};
} else {
- // Steps 7.a and 7.c.
- if (!::NanosecondsToDays(cx, duration, &nanosAndDays)) {
- return false;
- }
+ // Step 7.b.
+ auto timeAndDays = NormalizedTimeDurationToDays(duration.time);
+ fractionalDays = FractionalDays{duration.date.days, timeAndDays};
}
- // NanosecondsToDays guarantees that |abs(nanosAndDays.nanoseconds)| is less
- // than |abs(nanosAndDays.dayLength)|.
- MOZ_ASSERT(nanosAndDays.nanoseconds().abs() < nanosAndDays.dayLength());
-
- // Step 7.d. (Moved below)
-
- // Step 7.e. (Implicit)
+ // Step 7.c. (Moved below)
// Step 8. (Not applicable)
- // Step 9.
- // FIXME: spec issue - `total` doesn't need be initialised.
-
- // Steps 10-21.
+ // Steps 9-19.
switch (unit) {
- // Steps 10 and 20-21.
+ // Steps 9 and 19.
case TemporalUnit::Year:
- return RoundDurationYear(cx, duration, nanosAndDays, increment,
+ return RoundDurationYear(cx, duration, fractionalDays, increment,
roundingMode, plainRelativeTo, calendar,
computeRemainder, result);
- // Steps 11 and 20-21.
+ // Steps 10 and 19.
case TemporalUnit::Month:
- return RoundDurationMonth(cx, duration, nanosAndDays, increment,
+ return RoundDurationMonth(cx, duration, fractionalDays, increment,
roundingMode, plainRelativeTo, calendar,
computeRemainder, result);
- // Steps 12 and 20-21.
+ // Steps 11 and 19.
case TemporalUnit::Week:
- return RoundDurationWeek(cx, duration, nanosAndDays, increment,
+ return RoundDurationWeek(cx, duration, fractionalDays, increment,
roundingMode, plainRelativeTo, calendar,
computeRemainder, result);
- // Steps 13 and 20-21.
+ // Steps 12 and 19.
case TemporalUnit::Day:
- return RoundDurationDay(cx, duration, nanosAndDays, increment,
+ return RoundDurationDay(cx, duration, fractionalDays, increment,
roundingMode, computeRemainder, result);
- // Steps 14-19. (Handled elsewhere)
+ // Steps 13-18. (Handled elsewhere)
case TemporalUnit::Auto:
case TemporalUnit::Hour:
case TemporalUnit::Minute:
@@ -5117,55 +4142,51 @@ static bool RoundDuration(
}
/**
- * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
- * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
- * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
- * precalculatedPlainDateTime ] ] ] ] ] )
+ * RoundDuration ( years, months, weeks, days, norm, increment, unit,
+ * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
+ * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
*/
-static bool RoundDuration(
- JSContext* cx, const Duration& duration, Increment increment,
+bool js::temporal::RoundDuration(
+ JSContext* cx, const NormalizedDuration& duration, Increment increment,
TemporalUnit unit, TemporalRoundingMode roundingMode,
Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
- Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
- Handle<TimeZoneRecord> timeZone,
- mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
- double* result) {
- // Only called from |Duration_total|, which always passes |increment=1| and
- // |roundingMode=trunc|.
- MOZ_ASSERT(increment == Increment{1});
- MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
+ Handle<CalendarRecord> calendar, NormalizedDuration* result) {
+ MOZ_ASSERT(IsValidDuration(duration));
+ Rooted<ZonedDateTime> zonedRelativeTo(cx, ZonedDateTime{});
+ Rooted<TimeZoneRecord> timeZone(cx, TimeZoneRecord{});
+ mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
RoundedDuration rounded;
if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
plainRelativeTo, calendar, zonedRelativeTo, timeZone,
- precalculatedPlainDateTime, ComputeRemainder::Yes,
+ precalculatedPlainDateTime, ComputeRemainder::No,
&rounded)) {
return false;
}
- *result = rounded.total;
+ *result = rounded.duration;
return true;
}
/**
- * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
- * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
- * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
- * precalculatedPlainDateTime ] ] ] ] ] )
+ * RoundDuration ( years, months, weeks, days, norm, increment, unit,
+ * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
+ * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
*/
-static bool RoundDuration(
- JSContext* cx, const Duration& duration, Increment increment,
+bool js::temporal::RoundDuration(
+ JSContext* cx, const NormalizedDuration& duration, Increment increment,
TemporalUnit unit, TemporalRoundingMode roundingMode,
- Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
- Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
- Handle<TimeZoneRecord> timeZone,
- mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
- Duration* result) {
+ Handle<PlainDateObject*> plainRelativeTo, Handle<CalendarRecord> calendar,
+ Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone,
+ const PlainDateTime& precalculatedPlainDateTime,
+ NormalizedDuration* result) {
+ MOZ_ASSERT(IsValidDuration(duration));
+
RoundedDuration rounded;
if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
plainRelativeTo, calendar, zonedRelativeTo, timeZone,
- precalculatedPlainDateTime, ComputeRemainder::No,
- &rounded)) {
+ mozilla::SomeRef(precalculatedPlainDateTime),
+ ComputeRemainder::No, &rounded)) {
return false;
}
@@ -5173,46 +4194,6 @@ static bool RoundDuration(
return true;
}
-/**
- * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
- * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
- * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
- * precalculatedPlainDateTime ] ] ] ] ] )
- */
-bool js::temporal::RoundDuration(
- JSContext* cx, const Duration& duration, Increment increment,
- TemporalUnit unit, TemporalRoundingMode roundingMode,
- Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
- Handle<CalendarRecord> calendar, Duration* result) {
- MOZ_ASSERT(IsValidDuration(duration));
-
- Rooted<ZonedDateTime> zonedRelativeTo(cx, ZonedDateTime{});
- Rooted<TimeZoneRecord> timeZone(cx, TimeZoneRecord{});
- mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
- return ::RoundDuration(cx, duration, increment, unit, roundingMode,
- plainRelativeTo, calendar, zonedRelativeTo, timeZone,
- precalculatedPlainDateTime, result);
-}
-
-/**
- * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
- * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
- * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
- * precalculatedPlainDateTime ] ] ] ] ] )
- */
-bool js::temporal::RoundDuration(
- JSContext* cx, const Duration& duration, Increment increment,
- TemporalUnit unit, TemporalRoundingMode roundingMode,
- Handle<PlainDateObject*> plainRelativeTo, Handle<CalendarRecord> calendar,
- Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone,
- const PlainDateTime& precalculatedPlainDateTime, Duration* result) {
- MOZ_ASSERT(IsValidDuration(duration));
-
- return ::RoundDuration(cx, duration, increment, unit, roundingMode,
- plainRelativeTo, calendar, zonedRelativeTo, timeZone,
- mozilla::SomeRef(precalculatedPlainDateTime), result);
-}
-
enum class DurationOperation { Add, Subtract };
/**
@@ -5441,40 +4422,32 @@ static bool Duration_compare(JSContext* cx, unsigned argc, Value* vp) {
return false;
}
- Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
- Rooted<ZonedDateTime> zonedRelativeTo(cx);
- Rooted<TimeZoneRecord> timeZone(cx);
+ // Step 3.
+ Rooted<JSObject*> options(cx);
if (args.hasDefined(2)) {
- // Step 3.
- Rooted<JSObject*> options(
- cx, RequireObjectArg(cx, "options", "compare", args[2]));
+ options = RequireObjectArg(cx, "options", "compare", args[2]);
if (!options) {
return false;
}
+ }
- // Step 4.
- if (one == two) {
- args.rval().setInt32(0);
- return true;
- }
+ // Step 4.
+ if (one == two) {
+ args.rval().setInt32(0);
+ return true;
+ }
- // Steps 5-8.
+ // Steps 5-8.
+ Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
+ Rooted<ZonedDateTime> zonedRelativeTo(cx);
+ Rooted<TimeZoneRecord> timeZone(cx);
+ if (options) {
if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
&zonedRelativeTo, &timeZone)) {
return false;
}
MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
- } else {
- // Step 3. (Not applicable in our implementation.)
-
- // Step 4.
- if (one == two) {
- args.rval().setInt32(0);
- return true;
- }
-
- // Steps 5-8. (Not applicable in our implementation.)
}
// Steps 9-10.
@@ -5498,7 +4471,7 @@ static bool Duration_compare(JSContext* cx, unsigned argc, Value* vp) {
if (zonedRelativeTo &&
(calendarUnitsPresent || one.days != 0 || two.days != 0)) {
// Step 12.a.
- auto instant = zonedRelativeTo.instant();
+ const auto& instant = zonedRelativeTo.instant();
// Step 12.b.
PlainDateTime dateTime;
@@ -5507,59 +4480,65 @@ static bool Duration_compare(JSContext* cx, unsigned argc, Value* vp) {
}
// Step 12.c.
+ auto normalized1 = CreateNormalizedDurationRecord(one);
+
+ // Step 12.d.
+ auto normalized2 = CreateNormalizedDurationRecord(two);
+
+ // Step 12.e.
Instant after1;
- if (!AddZonedDateTime(cx, instant, timeZone, calendar, one, dateTime,
- &after1)) {
+ if (!AddZonedDateTime(cx, instant, timeZone, calendar, normalized1,
+ dateTime, &after1)) {
return false;
}
- // Step 12.d.
+ // Step 12.f.
Instant after2;
- if (!AddZonedDateTime(cx, instant, timeZone, calendar, two, dateTime,
- &after2)) {
+ if (!AddZonedDateTime(cx, instant, timeZone, calendar, normalized2,
+ dateTime, &after2)) {
return false;
}
- // Steps 12.e-g.
+ // Steps 12.g-i.
args.rval().setInt32(after1 < after2 ? -1 : after1 > after2 ? 1 : 0);
return true;
}
// Steps 13-14.
- double days1, days2;
+ int64_t days1, days2;
if (calendarUnitsPresent) {
// FIXME: spec issue - directly throw an error if plainRelativeTo is undef.
// Step 13.a.
DateDuration unbalanceResult1;
if (plainRelativeTo) {
- if (!UnbalanceDateDurationRelative(cx, one, TemporalUnit::Day,
- plainRelativeTo, calendar,
- &unbalanceResult1)) {
+ if (!UnbalanceDateDurationRelative(cx, one.toDateDuration(),
+ TemporalUnit::Day, plainRelativeTo,
+ calendar, &unbalanceResult1)) {
return false;
}
} else {
- if (!UnbalanceDateDurationRelative(cx, one, TemporalUnit::Day,
- &unbalanceResult1)) {
+ if (!UnbalanceDateDurationRelative(
+ cx, one.toDateDuration(), TemporalUnit::Day, &unbalanceResult1)) {
return false;
}
- MOZ_ASSERT(one.date() == unbalanceResult1.toDuration());
+ MOZ_ASSERT(one.toDateDuration() == unbalanceResult1);
}
// Step 13.b.
DateDuration unbalanceResult2;
if (plainRelativeTo) {
- if (!UnbalanceDateDurationRelative(cx, two, TemporalUnit::Day,
- plainRelativeTo, calendar,
- &unbalanceResult2)) {
+ if (!UnbalanceDateDurationRelative(cx, two.toDateDuration(),
+ TemporalUnit::Day, plainRelativeTo,
+ calendar, &unbalanceResult2)) {
return false;
}
} else {
- if (!UnbalanceDateDurationRelative(cx, two, TemporalUnit::Day,
- &unbalanceResult2)) {
+ if (!UnbalanceDateDurationRelative(
+ cx, two.toDateDuration(), TemporalUnit::Day, &unbalanceResult2)) {
return false;
}
- MOZ_ASSERT(two.date() == unbalanceResult2.toDuration());
+ MOZ_ASSERT(two.toDateDuration() == unbalanceResult2);
}
// Step 13.c.
@@ -5569,74 +4548,32 @@ static bool Duration_compare(JSContext* cx, unsigned argc, Value* vp) {
days2 = unbalanceResult2.days;
} else {
// Step 14.a.
- days1 = one.days;
+ days1 = mozilla::AssertedCast<int64_t>(one.days);
// Step 14.b.
- days2 = two.days;
+ days2 = mozilla::AssertedCast<int64_t>(two.days);
}
- // Note: duration units can be arbitrary doubles, so we need to use BigInts
- // Test case:
- //
- // Temporal.Duration.compare({
- // milliseconds: 10000000000000, microseconds: 4, nanoseconds: 95
- // }, {
- // nanoseconds:10000000000000004000
- // })
- //
- // This must return -1, but would return 0 when |double| is used.
- //
- // Note: BigInt(10000000000000004000) is 10000000000000004096n
-
- Duration oneTotal = {
- 0,
- 0,
- 0,
- days1,
- one.hours,
- one.minutes,
- one.seconds,
- one.milliseconds,
- one.microseconds,
- one.nanoseconds,
- };
- Duration twoTotal = {
- 0,
- 0,
- 0,
- days2,
- two.hours,
- two.minutes,
- two.seconds,
- two.milliseconds,
- two.microseconds,
- two.nanoseconds,
- };
-
- // Steps 15-21.
- //
- // Fast path when the total duration amount fits into an int64.
- if (auto ns1 = TotalDurationNanoseconds(oneTotal)) {
- if (auto ns2 = TotalDurationNanoseconds(twoTotal)) {
- args.rval().setInt32(*ns1 < *ns2 ? -1 : *ns1 > *ns2 ? 1 : 0);
- return true;
- }
- }
+ // Step 15.
+ auto normalized1 = NormalizeTimeDuration(one);
- // Steps 15 and 17.
- Rooted<BigInt*> ns1(cx, TotalDurationNanosecondsSlow(cx, oneTotal));
- if (!ns1) {
+ // Step 16.
+ if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized1, days1,
+ &normalized1)) {
return false;
}
- // Steps 16 and 18.
- auto* ns2 = TotalDurationNanosecondsSlow(cx, twoTotal);
- if (!ns2) {
+ // Step 17.
+ auto normalized2 = NormalizeTimeDuration(two);
+
+ // Step 18.
+ if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized2, days2,
+ &normalized2)) {
return false;
}
- // Step 19-21.
- args.rval().setInt32(BigInt::compare(ns1, ns2));
+ // Step 19.
+ args.rval().setInt32(CompareNormalizedTimeDuration(normalized1, normalized2));
return true;
}
@@ -5834,10 +4771,10 @@ static bool Duration_nanoseconds(JSContext* cx, unsigned argc, Value* vp) {
* get Temporal.Duration.prototype.sign
*/
static bool Duration_sign(JSContext* cx, const CallArgs& args) {
+ auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
+
// Step 3.
- auto* duration = &args.thisv().toObject().as<DurationObject>();
- int32_t sign = DurationSign(ToDuration(duration));
- args.rval().setInt32(sign);
+ args.rval().setInt32(DurationSign(duration));
return true;
}
@@ -5854,12 +4791,10 @@ static bool Duration_sign(JSContext* cx, unsigned argc, Value* vp) {
* get Temporal.Duration.prototype.blank
*/
static bool Duration_blank(JSContext* cx, const CallArgs& args) {
- // Step 3.
- auto* duration = &args.thisv().toObject().as<DurationObject>();
- int32_t sign = DurationSign(ToDuration(duration));
+ auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
- // Steps 4-5.
- args.rval().setBoolean(sign == 0);
+ // Steps 3-5.
+ args.rval().setBoolean(duration == Duration{});
return true;
}
@@ -5878,10 +4813,8 @@ static bool Duration_blank(JSContext* cx, unsigned argc, Value* vp) {
* ToPartialDuration ( temporalDurationLike )
*/
static bool Duration_with(JSContext* cx, const CallArgs& args) {
- auto* durationObj = &args.thisv().toObject().as<DurationObject>();
-
// Absent values default to the corresponding values of |this| object.
- auto duration = ToDuration(durationObj);
+ auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
// Steps 3-23.
Rooted<JSObject*> temporalDurationLike(
@@ -5916,8 +4849,7 @@ static bool Duration_with(JSContext* cx, unsigned argc, Value* vp) {
* Temporal.Duration.prototype.negated ( )
*/
static bool Duration_negated(JSContext* cx, const CallArgs& args) {
- auto* durationObj = &args.thisv().toObject().as<DurationObject>();
- auto duration = ToDuration(durationObj);
+ auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
// Step 3.
auto* result = CreateTemporalDuration(cx, duration.negate());
@@ -5942,8 +4874,7 @@ static bool Duration_negated(JSContext* cx, unsigned argc, Value* vp) {
* Temporal.Duration.prototype.abs ( )
*/
static bool Duration_abs(JSContext* cx, const CallArgs& args) {
- auto* durationObj = &args.thisv().toObject().as<DurationObject>();
- auto duration = ToDuration(durationObj);
+ auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
// Step 3.
auto* result = CreateTemporalDuration(cx, AbsoluteDuration(duration));
@@ -6002,8 +4933,7 @@ static bool Duration_subtract(JSContext* cx, unsigned argc, Value* vp) {
* Temporal.Duration.prototype.round ( roundTo )
*/
static bool Duration_round(JSContext* cx, const CallArgs& args) {
- auto* durationObj = &args.thisv().toObject().as<DurationObject>();
- auto duration = ToDuration(durationObj);
+ auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
// Step 18. (Reordered)
auto existingLargestUnit = DefaultTemporalLargestUnit(duration);
@@ -6208,7 +5138,7 @@ static bool Duration_round(JSContext* cx, const CallArgs& args) {
PlainDateTime relativeToDateTime;
if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) {
// Steps 34.a-b.
- auto instant = zonedRelativeTo.instant();
+ const auto& instant = zonedRelativeTo.instant();
// Step 34.c.
if (!GetPlainDateTimeFor(cx, timeZone, instant, &relativeToDateTime)) {
@@ -6240,46 +5170,48 @@ static bool Duration_round(JSContext* cx, const CallArgs& args) {
// Step 36.
DateDuration unbalanceResult;
if (plainRelativeTo) {
- if (!UnbalanceDateDurationRelative(cx, duration, largestUnit,
- plainRelativeTo, calendar,
+ if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(),
+ largestUnit, plainRelativeTo, calendar,
&unbalanceResult)) {
return false;
}
} else {
- if (!UnbalanceDateDurationRelative(cx, duration, largestUnit,
- &unbalanceResult)) {
+ if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(),
+ largestUnit, &unbalanceResult)) {
return false;
}
- MOZ_ASSERT(duration.date() == unbalanceResult.toDuration());
+ MOZ_ASSERT(duration.toDateDuration() == unbalanceResult);
}
+ MOZ_ASSERT(IsValidDuration(unbalanceResult));
// Steps 37-38.
- Duration roundInput = {
- unbalanceResult.years, unbalanceResult.months, unbalanceResult.weeks,
- unbalanceResult.days, duration.hours, duration.minutes,
- duration.seconds, duration.milliseconds, duration.microseconds,
- duration.nanoseconds,
- };
- Duration roundResult;
+ auto roundInput =
+ NormalizedDuration{unbalanceResult, NormalizeTimeDuration(duration)};
+ RoundedDuration rounded;
if (plainRelativeTo || zonedRelativeTo) {
if (!::RoundDuration(cx, roundInput, roundingIncrement, smallestUnit,
roundingMode, plainRelativeTo, calendar,
zonedRelativeTo, timeZone, precalculatedPlainDateTime,
- &roundResult)) {
+ ComputeRemainder::No, &rounded)) {
return false;
}
} else {
+ MOZ_ASSERT(IsValidDuration(roundInput));
+
if (!::RoundDuration(cx, roundInput, roundingIncrement, smallestUnit,
- roundingMode, &roundResult)) {
+ roundingMode, ComputeRemainder::No, &rounded)) {
return false;
}
}
- // Steps 39-40.
+ // Step 39.
+ auto roundResult = rounded.duration;
+
+ // Steps 40-41.
TimeDuration balanceResult;
if (zonedRelativeTo) {
- // Step 39.a.
- Duration adjustResult;
+ // Step 40.a.
+ NormalizedDuration adjustResult;
if (!AdjustRoundedDurationDays(cx, roundResult, roundingIncrement,
smallestUnit, roundingMode, zonedRelativeTo,
calendar, timeZone,
@@ -6288,46 +5220,51 @@ static bool Duration_round(JSContext* cx, const CallArgs& args) {
}
roundResult = adjustResult;
- // Step 39.b.
+ // Step 40.b.
if (!BalanceTimeDurationRelative(
cx, roundResult, largestUnit, zonedRelativeTo, timeZone,
precalculatedPlainDateTime, &balanceResult)) {
return false;
}
} else {
- // Step 40.a.
- if (!BalanceTimeDuration(cx, roundResult, largestUnit, &balanceResult)) {
+ // Step 41.a.
+ NormalizedTimeDuration withDays;
+ if (!Add24HourDaysToNormalizedTimeDuration(
+ cx, roundResult.time, roundResult.date.days, &withDays)) {
+ return false;
+ }
+
+ // Step 41.b.
+ if (!temporal::BalanceTimeDuration(cx, withDays, largestUnit,
+ &balanceResult)) {
return false;
}
}
- // Step 41.
- Duration balanceInput = {
- roundResult.years,
- roundResult.months,
- roundResult.weeks,
+ // Step 42.
+ auto balanceInput = DateDuration{
+ roundResult.date.years,
+ roundResult.date.months,
+ roundResult.date.weeks,
balanceResult.days,
};
- DateDuration result;
+ DateDuration dateResult;
if (!::BalanceDateDurationRelative(cx, balanceInput, largestUnit,
smallestUnit, plainRelativeTo, calendar,
- &result)) {
+ &dateResult)) {
return false;
}
- // Step 42.
- auto* obj = CreateTemporalDuration(cx, {
- result.years,
- result.months,
- result.weeks,
- result.days,
- balanceResult.hours,
- balanceResult.minutes,
- balanceResult.seconds,
- balanceResult.milliseconds,
- balanceResult.microseconds,
- balanceResult.nanoseconds,
- });
+ // Step 43.
+ auto result = Duration{
+ double(dateResult.years), double(dateResult.months),
+ double(dateResult.weeks), double(dateResult.days),
+ double(balanceResult.hours), double(balanceResult.minutes),
+ double(balanceResult.seconds), double(balanceResult.milliseconds),
+ balanceResult.microseconds, balanceResult.nanoseconds,
+ };
+
+ auto* obj = CreateTemporalDuration(cx, result);
if (!obj) {
return false;
}
@@ -6404,14 +5341,13 @@ static bool Duration_total(JSContext* cx, const CallArgs& args) {
// Step 13.
bool plainDateTimeOrRelativeToWillBeUsed =
- unit <= TemporalUnit::Day || duration.years != 0 ||
- duration.months != 0 || duration.weeks != 0 || duration.days != 0;
+ unit <= TemporalUnit::Day || duration.toDateDuration() != DateDuration{};
// Step 14.
PlainDateTime relativeToDateTime;
if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) {
// Steps 14.a-b.
- auto instant = zonedRelativeTo.instant();
+ const auto& instant = zonedRelativeTo.instant();
// Step 14.c.
if (!GetPlainDateTimeFor(cx, timeZone, instant, &relativeToDateTime)) {
@@ -6443,34 +5379,27 @@ static bool Duration_total(JSContext* cx, const CallArgs& args) {
// Step 16.
DateDuration unbalanceResult;
if (plainRelativeTo) {
- if (!UnbalanceDateDurationRelative(cx, duration, unit, plainRelativeTo,
- calendar, &unbalanceResult)) {
+ if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(), unit,
+ plainRelativeTo, calendar,
+ &unbalanceResult)) {
return false;
}
} else {
- if (!UnbalanceDateDurationRelative(cx, duration, unit, &unbalanceResult)) {
+ if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(), unit,
+ &unbalanceResult)) {
return false;
}
- MOZ_ASSERT(duration.date() == unbalanceResult.toDuration());
+ MOZ_ASSERT(duration.toDateDuration() == unbalanceResult);
}
- Duration balanceInput = {
- 0,
- 0,
- 0,
- unbalanceResult.days,
- duration.hours,
- duration.minutes,
- duration.seconds,
- duration.milliseconds,
- duration.microseconds,
- duration.nanoseconds,
- };
+ // Step 17.
+ int64_t unbalancedDays = unbalanceResult.days;
- // Steps 17-18.
- TimeDuration balanceResult;
+ // Steps 18-19.
+ int64_t days;
+ NormalizedTimeDuration normTime;
if (zonedRelativeTo) {
- // Step 17.a
+ // Step 18.a
Rooted<ZonedDateTime> intermediate(cx);
if (!MoveRelativeZonedDateTime(
cx, zonedRelativeTo, calendar, timeZone,
@@ -6480,63 +5409,131 @@ static bool Duration_total(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 17.b.
- if (!BalancePossiblyInfiniteTimeDurationRelative(
- cx, balanceInput, unit, intermediate, timeZone, &balanceResult)) {
+ // Step 18.b.
+ auto timeDuration = NormalizeTimeDuration(duration);
+
+ // Step 18.c
+ const auto& startNs = intermediate.instant();
+
+ // Step 18.d.
+ const auto& startInstant = startNs;
+
+ // Step 18.e.
+ mozilla::Maybe<PlainDateTime> startDateTime{};
+
+ // Steps 18.f-g.
+ Instant intermediateNs;
+ if (unbalancedDays != 0) {
+ // Step 18.f.i.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &dateTime)) {
+ return false;
+ }
+ startDateTime = mozilla::Some(dateTime);
+
+ // Step 18.f.ii.
+ Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601));
+ Instant addResult;
+ if (!AddDaysToZonedDateTime(cx, startInstant, dateTime, timeZone,
+ isoCalendar, unbalancedDays, &addResult)) {
+ return false;
+ }
+
+ // Step 18.f.iii.
+ intermediateNs = addResult;
+ } else {
+ // Step 18.g.
+ intermediateNs = startNs;
+ }
+
+ // Step 18.h.
+ Instant endNs;
+ if (!AddInstant(cx, intermediateNs, timeDuration, &endNs)) {
return false;
}
+
+ // Step 18.i.
+ auto difference =
+ NormalizedTimeDurationFromEpochNanosecondsDifference(endNs, startNs);
+
+ // Steps 18.j-k.
+ //
+ // Avoid calling NormalizedTimeDurationToDays for a zero time difference.
+ if (TemporalUnit::Year <= unit && unit <= TemporalUnit::Day &&
+ difference != NormalizedTimeDuration{}) {
+ // Step 18.j.i.
+ if (!startDateTime) {
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &dateTime)) {
+ return false;
+ }
+ startDateTime = mozilla::Some(dateTime);
+ }
+
+ // Step 18.j.ii.
+ NormalizedTimeAndDays timeAndDays;
+ if (!NormalizedTimeDurationToDays(cx, difference, intermediate, timeZone,
+ *startDateTime, &timeAndDays)) {
+ return false;
+ }
+
+ // Step 18.j.iii.
+ normTime = NormalizedTimeDuration::fromNanoseconds(timeAndDays.time);
+
+ // Step 18.j.iv.
+ days = timeAndDays.days;
+ } else {
+ // Step 18.k.i.
+ normTime = difference;
+ days = 0;
+ }
} else {
- // Step 18.
- if (!BalancePossiblyInfiniteTimeDuration(cx, balanceInput, unit,
- &balanceResult)) {
+ // Step 19.a.
+ auto timeDuration = NormalizeTimeDuration(duration);
+
+ // Step 19.b.
+ if (!Add24HourDaysToNormalizedTimeDuration(cx, timeDuration, unbalancedDays,
+ &normTime)) {
return false;
}
- }
- // Steps 19-20.
- for (double v : {
- balanceResult.days,
- balanceResult.hours,
- balanceResult.minutes,
- balanceResult.seconds,
- balanceResult.milliseconds,
- balanceResult.microseconds,
- balanceResult.nanoseconds,
- }) {
- if (std::isinf(v)) {
- args.rval().setDouble(v);
- return true;
- }
+ // Step 19.c.
+ days = 0;
}
- MOZ_ASSERT(IsValidDuration(balanceResult.toDuration()));
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(normTime));
- // Step 21. (Not applicable in our implementation.)
-
- // Step 22.
- Duration roundInput = {
- unbalanceResult.years, unbalanceResult.months,
- unbalanceResult.weeks, balanceResult.days,
- balanceResult.hours, balanceResult.minutes,
- balanceResult.seconds, balanceResult.milliseconds,
- balanceResult.microseconds, balanceResult.nanoseconds,
+ // Step 20.
+ auto roundInput = NormalizedDuration{
+ {
+ unbalanceResult.years,
+ unbalanceResult.months,
+ unbalanceResult.weeks,
+ days,
+ },
+ normTime,
};
- double total;
+ MOZ_ASSERT_IF(unit > TemporalUnit::Day, IsValidDuration(roundInput.date));
+
+ RoundedDuration rounded;
if (plainRelativeTo || zonedRelativeTo) {
if (!::RoundDuration(cx, roundInput, Increment{1}, unit,
TemporalRoundingMode::Trunc, plainRelativeTo, calendar,
zonedRelativeTo, timeZone, precalculatedPlainDateTime,
- &total)) {
+ ComputeRemainder::Yes, &rounded)) {
return false;
}
} else {
+ MOZ_ASSERT(IsValidDuration(roundInput));
+
if (!::RoundDuration(cx, roundInput, Increment{1}, unit,
- TemporalRoundingMode::Trunc, &total)) {
+ TemporalRoundingMode::Trunc, ComputeRemainder::Yes,
+ &rounded)) {
return false;
}
}
- // Step 23.
- args.rval().setNumber(total);
+ // Step 21.
+ args.rval().setNumber(rounded.total);
return true;
}
@@ -6605,64 +5602,37 @@ static bool Duration_toString(JSContext* cx, const CallArgs& args) {
if (precision.unit != TemporalUnit::Nanosecond ||
precision.increment != Increment{1}) {
// Step 10.a.
- auto largestUnit = DefaultTemporalLargestUnit(duration);
+ auto timeDuration = NormalizeTimeDuration(duration);
- // Steps 10.b-c.
- auto toRound = Duration{
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- duration.seconds,
- duration.milliseconds,
- duration.microseconds,
- duration.nanoseconds,
- };
- Duration roundResult;
- if (!temporal::RoundDuration(cx, toRound, precision.increment,
- precision.unit, roundingMode, &roundResult)) {
- return false;
- }
+ // Step 10.b.
+ auto largestUnit = DefaultTemporalLargestUnit(duration);
- // Step 10.d.
- auto toBalance = Duration{
- 0,
- 0,
- 0,
- duration.days,
- duration.hours,
- duration.minutes,
- roundResult.seconds,
- roundResult.milliseconds,
- roundResult.microseconds,
- roundResult.nanoseconds,
- };
- TimeDuration balanceResult;
- if (!BalanceTimeDuration(cx, toBalance, largestUnit, &balanceResult)) {
+ // Steps 10.c-d.
+ NormalizedTimeDuration rounded;
+ if (!RoundDuration(cx, timeDuration, precision.increment, precision.unit,
+ roundingMode, &rounded)) {
return false;
}
// Step 10.e.
+ auto balanced = BalanceTimeDuration(
+ rounded, std::min(largestUnit, TemporalUnit::Second));
+
+ // Step 10.f.
result = {
- duration.years,
- duration.months,
- duration.weeks,
- balanceResult.days,
- balanceResult.hours,
- balanceResult.minutes,
- balanceResult.seconds,
- balanceResult.milliseconds,
- balanceResult.microseconds,
- balanceResult.nanoseconds,
+ duration.years, duration.months,
+ duration.weeks, duration.days + double(balanced.days),
+ double(balanced.hours), double(balanced.minutes),
+ double(balanced.seconds), double(balanced.milliseconds),
+ balanced.microseconds, balanced.nanoseconds,
};
+ MOZ_ASSERT(IsValidDuration(duration));
} else {
// Step 11.
result = duration;
}
- // Step 12.
+ // Steps 12-13.
JSString* str = TemporalDurationToString(cx, result, precision.precision);
if (!str) {
return false;
@@ -6682,14 +5652,13 @@ static bool Duration_toString(JSContext* cx, unsigned argc, Value* vp) {
}
/**
- * Temporal.Duration.prototype.toJSON ( )
+ * Temporal.Duration.prototype.toJSON ( )
*/
static bool Duration_toJSON(JSContext* cx, const CallArgs& args) {
- auto* duration = &args.thisv().toObject().as<DurationObject>();
+ auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
- // Step 3.
- JSString* str =
- TemporalDurationToString(cx, ToDuration(duration), Precision::Auto());
+ // Steps 3-4.
+ JSString* str = TemporalDurationToString(cx, duration, Precision::Auto());
if (!str) {
return false;
}
@@ -6699,7 +5668,7 @@ static bool Duration_toJSON(JSContext* cx, const CallArgs& args) {
}
/**
- * Temporal.Duration.prototype.toJSON ( )
+ * Temporal.Duration.prototype.toJSON ( )
*/
static bool Duration_toJSON(JSContext* cx, unsigned argc, Value* vp) {
// Steps 1-2.
@@ -6711,11 +5680,10 @@ static bool Duration_toJSON(JSContext* cx, unsigned argc, Value* vp) {
* Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
*/
static bool Duration_toLocaleString(JSContext* cx, const CallArgs& args) {
- auto* duration = &args.thisv().toObject().as<DurationObject>();
+ auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
- // Step 3.
- JSString* str =
- TemporalDurationToString(cx, ToDuration(duration), Precision::Auto());
+ // Steps 3-4.
+ JSString* str = TemporalDurationToString(cx, duration, Precision::Auto());
if (!str) {
return false;
}