summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/temporal
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/builtin/temporal')
-rw-r--r--js/src/builtin/temporal/Calendar.cpp559
-rw-r--r--js/src/builtin/temporal/Calendar.h35
-rw-r--r--js/src/builtin/temporal/Duration.cpp5034
-rw-r--r--js/src/builtin/temporal/Duration.h225
-rw-r--r--js/src/builtin/temporal/Instant.cpp403
-rw-r--r--js/src/builtin/temporal/Instant.h42
-rw-r--r--js/src/builtin/temporal/Int128.cpp161
-rw-r--r--js/src/builtin/temporal/Int128.h742
-rw-r--r--js/src/builtin/temporal/Int96.cpp6
-rw-r--r--js/src/builtin/temporal/Int96.h2
-rw-r--r--js/src/builtin/temporal/PlainDate.cpp476
-rw-r--r--js/src/builtin/temporal/PlainDate.h63
-rw-r--r--js/src/builtin/temporal/PlainDateTime.cpp358
-rw-r--r--js/src/builtin/temporal/PlainDateTime.h26
-rw-r--r--js/src/builtin/temporal/PlainMonthDay.cpp48
-rw-r--r--js/src/builtin/temporal/PlainTime.cpp911
-rw-r--r--js/src/builtin/temporal/PlainTime.h31
-rw-r--r--js/src/builtin/temporal/PlainYearMonth.cpp216
-rw-r--r--js/src/builtin/temporal/Temporal.cpp811
-rw-r--r--js/src/builtin/temporal/Temporal.h42
-rw-r--r--js/src/builtin/temporal/TemporalFields.cpp8
-rw-r--r--js/src/builtin/temporal/TemporalFields.h3
-rw-r--r--js/src/builtin/temporal/TemporalNow.cpp4
-rw-r--r--js/src/builtin/temporal/TemporalParser.cpp284
-rw-r--r--js/src/builtin/temporal/TemporalParser.h7
-rw-r--r--js/src/builtin/temporal/TemporalRoundingMode.h282
-rw-r--r--js/src/builtin/temporal/TemporalTypes.h240
-rw-r--r--js/src/builtin/temporal/TemporalUnit.h48
-rw-r--r--js/src/builtin/temporal/TimeZone.cpp171
-rw-r--r--js/src/builtin/temporal/TimeZone.h5
-rw-r--r--js/src/builtin/temporal/ToString.cpp13
-rw-r--r--js/src/builtin/temporal/ZonedDateTime.cpp1003
-rw-r--r--js/src/builtin/temporal/ZonedDateTime.h108
-rw-r--r--js/src/builtin/temporal/moz.build1
34 files changed, 5925 insertions, 6443 deletions
diff --git a/js/src/builtin/temporal/Calendar.cpp b/js/src/builtin/temporal/Calendar.cpp
index 8a06877093..573a1740d2 100644
--- a/js/src/builtin/temporal/Calendar.cpp
+++ b/js/src/builtin/temporal/Calendar.cpp
@@ -137,18 +137,22 @@ static bool IterableToListOfStrings(JSContext* cx, Handle<Value> items,
// Step 1. (Not applicable in our implementation.)
- // Steps 2-3.
+ // Step 2.
Rooted<Value> nextValue(cx);
Rooted<PropertyKey> value(cx);
while (true) {
+ // Step 2.a.
bool done;
if (!iterator.next(&nextValue, &done)) {
return false;
}
+
+ // Step 2.b.
if (done) {
- break;
+ return true;
}
+ // Step 2.d. (Reordered)
if (nextValue.isString()) {
if (!PrimitiveValueToId<CanGC>(cx, nextValue, &value)) {
return false;
@@ -159,15 +163,14 @@ static bool IterableToListOfStrings(JSContext* cx, Handle<Value> items,
continue;
}
+ // Step 2.c.1.
ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, nextValue,
nullptr, "not a string");
+ // Step 2.c.2.
iterator.closeThrow();
return false;
}
-
- // Step 4.
- return true;
}
/**
@@ -312,7 +315,7 @@ int32_t js::temporal::ToISODayOfYear(const PlainDate& date) {
MOZ_ASSERT(ISODateTimeWithinLimits(date));
// Steps 1-5.
- auto& [year, month, day] = date;
+ const auto& [year, month, day] = date;
return ::ToISODayOfYear(year, month, day);
}
@@ -423,7 +426,7 @@ struct YearWeek final {
static YearWeek ToISOWeekOfYear(const PlainDate& date) {
MOZ_ASSERT(ISODateTimeWithinLimits(date));
- auto& [year, month, day] = date;
+ const auto& [year, month, day] = date;
// TODO: https://en.wikipedia.org/wiki/Week#The_ISO_week_date_system
// TODO: https://en.wikipedia.org/wiki/ISO_week_date#Algorithms
@@ -1054,6 +1057,88 @@ static bool BuiltinCalendarFields(
return true;
}
+/**
+ * Temporal.Calendar.prototype.fields ( fields )
+ */
+static bool BuiltinCalendarFields(JSContext* cx, Handle<Value> fields,
+ MutableHandle<Value> result) {
+ // Step 4.
+ JS::ForOfIterator iterator(cx);
+ if (!iterator.init(fields)) {
+ return false;
+ }
+
+ // Step 5.
+ JS::RootedVector<Value> fieldNames(cx);
+ mozilla::EnumSet<CalendarField> seen;
+
+ // Step 6.
+ Rooted<Value> nextValue(cx);
+ Rooted<JSLinearString*> linear(cx);
+ while (true) {
+ // Step 6.a.
+ bool done;
+ if (!iterator.next(&nextValue, &done)) {
+ return false;
+ }
+
+ // Step 6.b.
+ if (done) {
+ auto* array =
+ NewDenseCopiedArray(cx, fieldNames.length(), fieldNames.begin());
+ if (!array) {
+ return false;
+ }
+
+ result.setObject(*array);
+ return true;
+ }
+
+ // Step 6.c.
+ if (!nextValue.isString()) {
+ // Step 6.c.1.
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, nextValue,
+ nullptr, "not a string");
+
+ // Step 6.c.2.
+ iterator.closeThrow();
+ return false;
+ }
+
+ linear = nextValue.toString()->ensureLinear(cx);
+ if (!linear) {
+ return false;
+ }
+
+ // Step 6.e. (Reordered)
+ CalendarField field;
+ if (!ToCalendarField(cx, linear, &field)) {
+ iterator.closeThrow();
+ return false;
+ }
+
+ // Step 6.d.
+ if (seen.contains(field)) {
+ // Step 6.d.1.
+ if (auto chars = QuoteString(cx, linear, '"')) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_CALENDAR_DUPLICATE_FIELD,
+ chars.get());
+ }
+
+ // Step 6.d.2.
+ iterator.closeThrow();
+ return false;
+ }
+
+ // Step 6.f.
+ if (!fieldNames.append(nextValue)) {
+ return false;
+ }
+ seen += field;
+ }
+}
+
#ifdef DEBUG
static bool IsSorted(std::initializer_list<CalendarField> fieldNames) {
return std::is_sorted(fieldNames.begin(), fieldNames.end(),
@@ -1075,15 +1160,12 @@ bool js::temporal::CalendarFields(
MOZ_ASSERT(
CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::Fields));
- // FIXME: spec issue - the input is already sorted, let's assert this, too.
+ // Step 1.
MOZ_ASSERT(IsSorted(fieldNames));
-
- // FIXME: spec issue - the input shouldn't have duplicate elements. Let's
- // assert this, too.
MOZ_ASSERT(std::adjacent_find(fieldNames.begin(), fieldNames.end()) ==
fieldNames.end());
- // Step 1.
+ // Step 2.
auto fields = calendar.fields();
if (!fields) {
bool arrayIterationSane;
@@ -1098,16 +1180,14 @@ bool js::temporal::CalendarFields(
}
if (arrayIterationSane) {
- // Steps 1.a-b. (Not applicable in our implementation.)
-
- // Step 1.c.
+ // Steps 2.a-b.
return BuiltinCalendarFields(cx, fieldNames, result.get());
- // Steps 1.d-f. (Not applicable in our implementation.)
+ // Steps 2.c-e. (Not applicable in our implementation.)
}
}
- // Step 2. (Inlined call to CalendarMethodsRecordCall.)
+ // Step 3. (Inlined call to CalendarMethodsRecordCall.)
auto* array = NewDenseFullyAllocatedArray(cx, fieldNames.size());
if (!array) {
@@ -1120,15 +1200,22 @@ bool js::temporal::CalendarFields(
array->initDenseElement(i, StringValue(name));
}
- Rooted<Value> fieldsFn(cx, ObjectValue(*fields));
- auto thisv = calendar.receiver().toValue();
Rooted<Value> fieldsArray(cx, ObjectValue(*array));
- if (!Call(cx, fieldsFn, thisv, fieldsArray, &fieldsArray)) {
- return false;
+ Rooted<Value> calendarFieldNames(cx);
+ if (fields) {
+ Rooted<Value> fieldsFn(cx, ObjectValue(*fields));
+ auto thisv = calendar.receiver().toValue();
+ if (!Call(cx, fieldsFn, thisv, fieldsArray, &calendarFieldNames)) {
+ return false;
+ }
+ } else {
+ if (!BuiltinCalendarFields(cx, fieldsArray, &calendarFieldNames)) {
+ return false;
+ }
}
- // Steps 3-4.
- if (!IterableToListOfStrings(cx, fieldsArray, result)) {
+ // Steps 4-5.
+ if (!IterableToListOfStrings(cx, calendarFieldNames, result)) {
return false;
}
@@ -1184,6 +1271,26 @@ static bool RequireIntegralPositiveNumber(JSContext* cx, Handle<Value> value,
return true;
}
+static bool RequireIntegralNumberOrUndefined(JSContext* cx, Handle<Value> value,
+ Handle<PropertyName*> name,
+ MutableHandle<Value> result) {
+ if (value.isUndefined()) {
+ result.setUndefined();
+ return true;
+ }
+ return RequireIntegralNumber(cx, value, name, result);
+}
+
+static bool RequireIntegralPositiveNumberOrUndefined(
+ JSContext* cx, Handle<Value> value, Handle<PropertyName*> name,
+ MutableHandle<Value> result) {
+ if (value.isUndefined()) {
+ result.setUndefined();
+ return true;
+ }
+ return RequireIntegralPositiveNumber(cx, value, name, result);
+}
+
static bool RequireString(JSContext* cx, Handle<Value> value,
Handle<PropertyName*> name,
MutableHandle<Value> result) {
@@ -1756,7 +1863,7 @@ static bool CalendarWeekOfYear(JSContext* cx, Handle<CalendarValue> calendar,
MutableHandle<Value> result) {
// Steps 1-6.
return CallCalendarMethod<BuiltinCalendarWeekOfYear,
- RequireIntegralPositiveNumber>(
+ RequireIntegralPositiveNumberOrUndefined>(
cx, cx->names().weekOfYear, Calendar_weekOfYear, calendar, dateLike, date,
result);
}
@@ -1821,7 +1928,8 @@ static bool CalendarYearOfWeek(JSContext* cx, Handle<CalendarValue> calendar,
const PlainDate& date,
MutableHandle<Value> result) {
// Steps 1-5.
- return CallCalendarMethod<BuiltinCalendarYearOfWeek, RequireIntegralNumber>(
+ return CallCalendarMethod<BuiltinCalendarYearOfWeek,
+ RequireIntegralNumberOrUndefined>(
cx, cx->names().yearOfWeek, Calendar_yearOfWeek, calendar, dateLike, date,
result);
}
@@ -2482,8 +2590,8 @@ Wrapped<PlainDateObject*> js::temporal::CalendarDateFromFields(
}
struct RegulatedISOYearMonth final {
- double year;
- int32_t month;
+ double year = 0;
+ int32_t month = 0;
};
/**
@@ -2492,33 +2600,30 @@ struct RegulatedISOYearMonth final {
static bool RegulateISOYearMonth(JSContext* cx, double year, double month,
TemporalOverflow overflow,
RegulatedISOYearMonth* result) {
- // Step 1.
MOZ_ASSERT(IsInteger(year));
MOZ_ASSERT(IsInteger(month));
- // Step 2. (Not applicable in our implementation.)
-
- // Step 3.
+ // Step 1.
if (overflow == TemporalOverflow::Constrain) {
- // Step 3.a.
+ // Step 1.a.
month = std::clamp(month, 1.0, 12.0);
- // Step 3.b.
+ // Step 1.b.
*result = {year, int32_t(month)};
return true;
}
- // Step 4.a.
+ // Step 2.a.
MOZ_ASSERT(overflow == TemporalOverflow::Reject);
- // Step 4.b.
+ // Step 2.b.
if (month < 1 || month > 12) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_PLAIN_YEAR_MONTH_INVALID);
return false;
}
- // Step 4.c.
+ // Step 2.c.
*result = {year, int32_t(month)};
return true;
}
@@ -2850,7 +2955,7 @@ static bool ISOFieldKeysToIgnore(JSContext* cx, const PropertyVector& keys,
// Step 2.
bool seenMonthOrMonthCode = false;
- for (auto& key : keys) {
+ for (const auto& key : keys) {
// Reorder the substeps in order to use |putNew| instead of |put|, because
// the former is slightly faster.
@@ -2874,7 +2979,7 @@ static bool ISOFieldKeysToIgnore(JSContext* cx, const PropertyVector& keys,
}
}
- // Step 3.
+ // Steps 3-4.
return true;
}
@@ -3060,7 +3165,7 @@ JSObject* js::temporal::CalendarMergeFields(
* Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )
*/
static bool BuiltinCalendarAdd(JSContext* cx, const PlainDate& date,
- const Duration& duration,
+ const NormalizedDuration& duration,
Handle<JSObject*> options, PlainDate* result) {
MOZ_ASSERT(IsValidISODate(date));
MOZ_ASSERT(IsValidDuration(duration));
@@ -3076,32 +3181,71 @@ static bool BuiltinCalendarAdd(JSContext* cx, const PlainDate& date,
}
// Step 8.
- TimeDuration balanceResult;
- if (!BalanceTimeDuration(cx, duration, TemporalUnit::Day, &balanceResult)) {
- return false;
- }
+ const auto& timeDuration = duration.time;
// Step 9.
- Duration addDuration = {duration.years, duration.months, duration.weeks,
- balanceResult.days};
+ auto balanceResult = BalanceTimeDuration(timeDuration, TemporalUnit::Day);
+
+ // Step 10.
+ auto addDuration = DateDuration{
+ duration.date.years,
+ duration.date.months,
+ duration.date.weeks,
+ duration.date.days + balanceResult.days,
+ };
return AddISODate(cx, date, addDuration, overflow, result);
}
/**
* Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )
*/
+static bool BuiltinCalendarAdd(JSContext* cx, const PlainDate& date,
+ const DateDuration& duration,
+ Handle<JSObject*> options, PlainDate* result) {
+ // Steps 1-6. (Not applicable)
+
+ // Step 8. (Reordered)
+ auto normalized = CreateNormalizedDurationRecord(duration, {});
+
+ // Steps 7-10.
+ return BuiltinCalendarAdd(cx, date, normalized, options, result);
+}
+
+/**
+ * Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )
+ */
+static PlainDateObject* BuiltinCalendarAdd(JSContext* cx, const PlainDate& date,
+ const DateDuration& duration,
+ Handle<JSObject*> options) {
+ // Steps 1-10.
+ PlainDate result;
+ if (!BuiltinCalendarAdd(cx, date, duration, options, &result)) {
+ return nullptr;
+ }
+
+ // Step 11.
+ Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
+ return CreateTemporalDate(cx, result, calendar);
+}
+
+/**
+ * Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )
+ */
static PlainDateObject* BuiltinCalendarAdd(JSContext* cx, const PlainDate& date,
const Duration& duration,
Handle<JSObject*> options) {
// Steps 1-6. (Not applicable)
- // Steps 7-9.
+ // Step 8. (Reordered)
+ auto normalized = CreateNormalizedDurationRecord(duration);
+
+ // Steps 7-10.
PlainDate result;
- if (!BuiltinCalendarAdd(cx, date, duration, options, &result)) {
+ if (!BuiltinCalendarAdd(cx, date, normalized, options, &result)) {
return nullptr;
}
- // Step 10.
+ // Step 11.
Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
return CreateTemporalDate(cx, result, calendar);
}
@@ -3188,6 +3332,38 @@ static Wrapped<PlainDateObject*> CalendarDateAdd(
*/
static Wrapped<PlainDateObject*> CalendarDateAdd(
JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> date, const DateDuration& duration,
+ Handle<JSObject*> options) {
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
+
+ // Step 1. (Not applicable).
+
+ // Step 3. (Reordered)
+ if (!calendar.dateAdd()) {
+ auto* unwrappedDate = date.unwrap(cx);
+ if (!unwrappedDate) {
+ return nullptr;
+ }
+ auto date = ToPlainDate(unwrappedDate);
+
+ return BuiltinCalendarAdd(cx, date, duration, options);
+ }
+
+ // Steps 2 and 4-5.
+ Rooted<DurationObject*> durationObj(
+ cx, CreateTemporalDuration(cx, duration.toDuration()));
+ if (!durationObj) {
+ return nullptr;
+ }
+ return CalendarDateAddSlow(cx, calendar, date, durationObj, options);
+}
+
+/**
+ * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
+ */
+static Wrapped<PlainDateObject*> CalendarDateAdd(
+ JSContext* cx, Handle<CalendarRecord> calendar,
Handle<Wrapped<PlainDateObject*>> date,
Handle<Wrapped<DurationObject*>> duration, Handle<JSObject*> options) {
MOZ_ASSERT(
@@ -3221,8 +3397,8 @@ static Wrapped<PlainDateObject*> CalendarDateAdd(
*/
static bool CalendarDateAdd(JSContext* cx, Handle<CalendarRecord> calendar,
Handle<Wrapped<PlainDateObject*>> date,
- const Duration& duration, Handle<JSObject*> options,
- PlainDate* result) {
+ const DateDuration& duration,
+ Handle<JSObject*> options, PlainDate* result) {
MOZ_ASSERT(
CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
@@ -3241,7 +3417,8 @@ static bool CalendarDateAdd(JSContext* cx, Handle<CalendarRecord> calendar,
// Steps 2 and 4-5.
- Rooted<DurationObject*> durationObj(cx, CreateTemporalDuration(cx, duration));
+ Rooted<DurationObject*> durationObj(
+ cx, CreateTemporalDuration(cx, duration.toDuration()));
if (!durationObj) {
return false;
}
@@ -3259,7 +3436,7 @@ static bool CalendarDateAdd(JSContext* cx, Handle<CalendarRecord> calendar,
* CalendarDateAdd ( calendarRec, date, duration [ , options ] )
*/
static bool CalendarDateAdd(JSContext* cx, Handle<CalendarRecord> calendar,
- const PlainDate& date, const Duration& duration,
+ const PlainDate& date, const DateDuration& duration,
Handle<JSObject*> options, PlainDate* result) {
MOZ_ASSERT(
CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
@@ -3279,7 +3456,8 @@ static bool CalendarDateAdd(JSContext* cx, Handle<CalendarRecord> calendar,
return false;
}
- Rooted<DurationObject*> durationObj(cx, CreateTemporalDuration(cx, duration));
+ Rooted<DurationObject*> durationObj(
+ cx, CreateTemporalDuration(cx, duration.toDuration()));
if (!durationObj) {
return false;
}
@@ -3311,7 +3489,7 @@ Wrapped<PlainDateObject*> js::temporal::CalendarDateAdd(
*/
Wrapped<PlainDateObject*> js::temporal::CalendarDateAdd(
JSContext* cx, Handle<CalendarRecord> calendar,
- Handle<Wrapped<PlainDateObject*>> date, const Duration& duration) {
+ Handle<Wrapped<PlainDateObject*>> date, const DateDuration& duration) {
// Step 1.
Handle<JSObject*> options = nullptr;
@@ -3352,7 +3530,7 @@ Wrapped<PlainDateObject*> js::temporal::CalendarDateAdd(
bool js::temporal::CalendarDateAdd(JSContext* cx,
Handle<CalendarRecord> calendar,
const PlainDate& date,
- const Duration& duration,
+ const DateDuration& duration,
PlainDate* result) {
// Step 1.
Handle<JSObject*> options = nullptr;
@@ -3364,9 +3542,12 @@ bool js::temporal::CalendarDateAdd(JSContext* cx,
/**
* CalendarDateAdd ( calendarRec, date, duration [ , options ] )
*/
-bool js::temporal::CalendarDateAdd(
- JSContext* cx, Handle<CalendarRecord> calendar, const PlainDate& date,
- const Duration& duration, Handle<JSObject*> options, PlainDate* result) {
+bool js::temporal::CalendarDateAdd(JSContext* cx,
+ Handle<CalendarRecord> calendar,
+ const PlainDate& date,
+ const DateDuration& duration,
+ Handle<JSObject*> options,
+ PlainDate* result) {
// Step 1. (Not applicable)
// Steps 2-5.
@@ -3379,7 +3560,7 @@ bool js::temporal::CalendarDateAdd(
bool js::temporal::CalendarDateAdd(JSContext* cx,
Handle<CalendarRecord> calendar,
Handle<Wrapped<PlainDateObject*>> date,
- const Duration& duration,
+ const DateDuration& duration,
PlainDate* result) {
// Step 1.
Handle<JSObject*> options = nullptr;
@@ -3391,18 +3572,15 @@ bool js::temporal::CalendarDateAdd(JSContext* cx,
/**
* Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )
*/
-static Duration BuiltinCalendarDateUntil(const PlainDate& one,
- const PlainDate& two,
- TemporalUnit largestUnit) {
+static DateDuration BuiltinCalendarDateUntil(const PlainDate& one,
+ const PlainDate& two,
+ TemporalUnit largestUnit) {
// Steps 1-3. (Not applicable)
// Steps 4-8. (Not applicable)
- // Step 9.
- auto difference = DifferenceISODate(one, two, largestUnit);
-
- // Step 10.
- return difference.toDuration();
+ // Steps 9-10.
+ return DifferenceISODate(one, two, largestUnit);
}
/**
@@ -3412,7 +3590,7 @@ static bool BuiltinCalendarDateUntil(JSContext* cx,
Handle<Wrapped<PlainDateObject*>> one,
Handle<Wrapped<PlainDateObject*>> two,
TemporalUnit largestUnit,
- Duration* result) {
+ DateDuration* result) {
MOZ_ASSERT(largestUnit <= TemporalUnit::Day);
auto* unwrappedOne = one.unwrap(cx);
@@ -3432,37 +3610,31 @@ static bool BuiltinCalendarDateUntil(JSContext* cx,
return true;
}
-/**
- * Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )
- */
-static bool BuiltinCalendarDateUntil(JSContext* cx,
- Handle<Wrapped<PlainDateObject*>> one,
- Handle<Wrapped<PlainDateObject*>> two,
- Handle<JSObject*> options,
- Duration* result) {
- // Steps 1-6. (Not applicable)
-
- // Steps 7-8.
- auto largestUnit = TemporalUnit::Day;
- if (!GetTemporalUnit(cx, options, TemporalUnitKey::LargestUnit,
- TemporalUnitGroup::Date, &largestUnit)) {
- return false;
- }
-
- // Steps 9-10.
- return BuiltinCalendarDateUntil(cx, one, two, largestUnit, result);
-}
-
static bool CalendarDateUntilSlow(JSContext* cx,
Handle<CalendarRecord> calendar,
Handle<Wrapped<PlainDateObject*>> one,
Handle<Wrapped<PlainDateObject*>> two,
- Handle<JSObject*> options, Duration* result) {
+ TemporalUnit largestUnit,
+ Handle<JSObject*> maybeOptions,
+ DateDuration* result) {
MOZ_ASSERT(
CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
MOZ_ASSERT(calendar.receiver().isObject());
MOZ_ASSERT(calendar.dateUntil());
+ Rooted<JSObject*> options(cx, maybeOptions);
+ if (!options) {
+ options = NewPlainObjectWithProto(cx, nullptr);
+ if (!options) {
+ return false;
+ }
+ }
+
+ Rooted<Value> value(cx, StringValue(TemporalUnitToString(cx, largestUnit)));
+ if (!DefineDataProperty(cx, options, cx->names().largestUnit, value)) {
+ return false;
+ }
+
// Step 1. (Inlined call to CalendarMethodsRecordCall.)
Rooted<JS::Value> dateUntil(cx, ObjectValue(*calendar.dateUntil()));
auto thisv = calendar.receiver().toValue();
@@ -3488,29 +3660,90 @@ static bool CalendarDateUntilSlow(JSContext* cx,
}
// Step 4.
- *result = ToDuration(&rval.toObject().unwrapAs<DurationObject>());
+ auto duration = ToDuration(&rval.toObject().unwrapAs<DurationObject>());
+ *result = duration.toDateDuration();
return true;
}
+static bool CalendarDateUntilSlow(JSContext* cx,
+ Handle<CalendarRecord> calendar,
+ const PlainDate& one, const PlainDate& two,
+ TemporalUnit largestUnit,
+ Handle<JSObject*> maybeOptions,
+ DateDuration* result) {
+ Rooted<PlainDateObject*> date1(
+ cx, CreateTemporalDate(cx, one, calendar.receiver()));
+ if (!date1) {
+ return false;
+ }
+
+ Rooted<PlainDateObject*> date2(
+ cx, CreateTemporalDate(cx, two, calendar.receiver()));
+ if (!date2) {
+ return false;
+ }
+
+ return CalendarDateUntilSlow(cx, calendar, date1, date2, largestUnit,
+ maybeOptions, result);
+}
+
/**
* CalendarDateUntil ( calendarRec, one, two, options )
*/
bool js::temporal::CalendarDateUntil(JSContext* cx,
Handle<CalendarRecord> calendar,
- Handle<Wrapped<PlainDateObject*>> one,
- Handle<Wrapped<PlainDateObject*>> two,
+ const PlainDate& one, const PlainDate& two,
+ TemporalUnit largestUnit,
+ DateDuration* result) {
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
+ MOZ_ASSERT(largestUnit <= TemporalUnit::Day);
+
+ // Step 2. (Reordered)
+ if (!calendar.dateUntil()) {
+ *result = BuiltinCalendarDateUntil(one, two, largestUnit);
+ return true;
+ }
+
+ // Steps 1 and 3-4.
+ return CalendarDateUntilSlow(cx, calendar, one, two, largestUnit, nullptr,
+ result);
+}
+
+/**
+ * CalendarDateUntil ( calendarRec, one, two, options )
+ */
+bool js::temporal::CalendarDateUntil(JSContext* cx,
+ Handle<CalendarRecord> calendar,
+ const PlainDate& one, const PlainDate& two,
+ TemporalUnit largestUnit,
Handle<PlainObject*> options,
- Duration* result) {
+ DateDuration* result) {
MOZ_ASSERT(
CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
+ // As an optimization, our implementation only adds |largestUnit| to the
+ // options object when taking the slow-path.
+#ifdef DEBUG
+ // The object must be extensible, otherwise we'd need to throw an error when
+ // attempting to add the "largestUnit" property to a non-extensible object.
+ MOZ_ASSERT(options->isExtensible());
+
+ // Similarily, if there's an existing "largestUnit" property, this property
+ // must be configurable.
+ auto largestUnitProp = options->lookupPure(cx->names().largestUnit);
+ MOZ_ASSERT_IF(largestUnitProp, largestUnitProp->configurable());
+#endif
+
// Step 2. (Reordered)
if (!calendar.dateUntil()) {
- return BuiltinCalendarDateUntil(cx, one, two, options, result);
+ *result = BuiltinCalendarDateUntil(one, two, largestUnit);
+ return true;
}
// Steps 1 and 3-4.
- return CalendarDateUntilSlow(cx, calendar, one, two, options, result);
+ return CalendarDateUntilSlow(cx, calendar, one, two, largestUnit, options,
+ result);
}
/**
@@ -3521,7 +3754,7 @@ bool js::temporal::CalendarDateUntil(JSContext* cx,
Handle<Wrapped<PlainDateObject*>> one,
Handle<Wrapped<PlainDateObject*>> two,
TemporalUnit largestUnit,
- Duration* result) {
+ DateDuration* result) {
MOZ_ASSERT(
CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
MOZ_ASSERT(largestUnit <= TemporalUnit::Day);
@@ -3531,18 +3764,45 @@ bool js::temporal::CalendarDateUntil(JSContext* cx,
return BuiltinCalendarDateUntil(cx, one, two, largestUnit, result);
}
- Rooted<PlainObject*> untilOptions(cx, NewPlainObjectWithProto(cx, nullptr));
- if (!untilOptions) {
- return false;
- }
+ // Steps 1 and 3-4.
+ return CalendarDateUntilSlow(cx, calendar, one, two, largestUnit, nullptr,
+ result);
+}
- Rooted<Value> value(cx, StringValue(TemporalUnitToString(cx, largestUnit)));
- if (!DefineDataProperty(cx, untilOptions, cx->names().largestUnit, value)) {
- return false;
+/**
+ * CalendarDateUntil ( calendarRec, one, two, options )
+ */
+bool js::temporal::CalendarDateUntil(JSContext* cx,
+ Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> one,
+ Handle<Wrapped<PlainDateObject*>> two,
+ TemporalUnit largestUnit,
+ Handle<PlainObject*> options,
+ DateDuration* result) {
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
+
+ // As an optimization, our implementation only adds |largestUnit| to the
+ // options object when taking the slow-path.
+#ifdef DEBUG
+ // The object must be extensible, otherwise we'd need to throw an error when
+ // attempting to add the "largestUnit" property to a non-extensible object.
+ MOZ_ASSERT(options->isExtensible());
+
+ // Similarily, if there's an existing "largestUnit" property, this property
+ // must be configurable.
+ auto largestUnitProp = options->lookupPure(cx->names().largestUnit);
+ MOZ_ASSERT_IF(largestUnitProp, largestUnitProp->configurable());
+#endif
+
+ // Step 2. (Reordered)
+ if (!calendar.dateUntil()) {
+ return BuiltinCalendarDateUntil(cx, one, two, largestUnit, result);
}
// Steps 1 and 3-4.
- return CalendarDateUntilSlow(cx, calendar, one, two, untilOptions, result);
+ return CalendarDateUntilSlow(cx, calendar, one, two, largestUnit, options,
+ result);
}
/**
@@ -3925,7 +4185,7 @@ static bool Calendar_dateAdd(JSContext* cx, const CallArgs& args) {
}
}
- // Steps 7-10.
+ // Steps 7-11.
auto* obj = BuiltinCalendarAdd(cx, date, duration, options);
if (!obj) {
return false;
@@ -3979,10 +4239,11 @@ static bool Calendar_dateUntil(JSContext* cx, const CallArgs& args) {
}
}
- // Steps 9-10.
+ // Step 9.
auto duration = BuiltinCalendarDateUntil(one, two, largestUnit);
- auto* obj = CreateTemporalDuration(cx, duration);
+ // Step 10.
+ auto* obj = CreateTemporalDuration(cx, duration.toDuration());
if (!obj) {
return false;
}
@@ -4363,76 +4624,8 @@ static bool Calendar_fields(JSContext* cx, const CallArgs& args) {
// Step 3.
MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
- // Step 4.
- JS::ForOfIterator iterator(cx);
- if (!iterator.init(args.get(0))) {
- return false;
- }
-
- // Step 5.
- JS::RootedVector<Value> fieldNames(cx);
- mozilla::EnumSet<CalendarField> seen;
-
- // Steps 6-7.
- Rooted<Value> nextValue(cx);
- Rooted<JSLinearString*> linear(cx);
- while (true) {
- // Steps 7.a and 7.b.i.
- bool done;
- if (!iterator.next(&nextValue, &done)) {
- return false;
- }
- if (done) {
- break;
- }
-
- // Step 7.b.ii.
- if (!nextValue.isString()) {
- ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, nextValue,
- nullptr, "not a string");
- iterator.closeThrow();
- return false;
- }
-
- linear = nextValue.toString()->ensureLinear(cx);
- if (!linear) {
- return false;
- }
-
- // Step 7.b.iv. (Reordered)
- CalendarField field;
- if (!ToCalendarField(cx, linear, &field)) {
- iterator.closeThrow();
- return false;
- }
-
- // Step 7.b.iii.
- if (seen.contains(field)) {
- if (auto chars = QuoteString(cx, linear, '"')) {
- JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
- JSMSG_TEMPORAL_CALENDAR_DUPLICATE_FIELD,
- chars.get());
- }
- iterator.closeThrow();
- return false;
- }
-
- // Step 7.b.v.
- if (!fieldNames.append(nextValue)) {
- return false;
- }
- seen += field;
- }
-
- // Step 8.
- auto* array =
- NewDenseCopiedArray(cx, fieldNames.length(), fieldNames.begin());
- if (!array) {
- return false;
- }
-
- args.rval().setObject(*array);
- return true;
+ // Steps 4-6.
+ return BuiltinCalendarFields(cx, args.get(0), args.rval());
}
/**
@@ -4512,18 +4705,14 @@ static bool Calendar_mergeFields(JSContext* cx, const CallArgs& args) {
for (size_t i = 0; i < fieldsKeys.length(); i++) {
Handle<PropertyKey> key = fieldsKeys[i];
- // Step 12.a.
- // FIXME: spec issue - unnecessary initialisation
- // https://github.com/tc39/proposal-temporal/issues/2549
-
- // Steps 12.b-c.
+ // Steps 12.a-b.
if (overriddenKeys.has(key)) {
if (!GetProperty(cx, additionalFieldsCopy, additionalFieldsCopy, key,
&propValue)) {
return false;
}
- // Step 12.d. (Reordered)
+ // Step 12.c. (Reordered)
if (propValue.isUndefined()) {
// The property can be undefined if the key is "month" or "monthCode".
MOZ_ASSERT(key.isAtom(cx->names().month) ||
@@ -4540,7 +4729,7 @@ static bool Calendar_mergeFields(JSContext* cx, const CallArgs& args) {
MOZ_ASSERT(!propValue.isUndefined());
}
- // Step 12.d.
+ // Step 12.c.
if (!DefineDataProperty(cx, merged, key, propValue)) {
return false;
}
diff --git a/js/src/builtin/temporal/Calendar.h b/js/src/builtin/temporal/Calendar.h
index f80f528d83..dd06c11923 100644
--- a/js/src/builtin/temporal/Calendar.h
+++ b/js/src/builtin/temporal/Calendar.h
@@ -8,6 +8,7 @@
#define builtin_temporal_Calendar_h
#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
#include "mozilla/EnumSet.h"
#include <initializer_list>
@@ -49,7 +50,7 @@ class CalendarObject : public NativeObject {
* Calendar value, which is either a string containing a canonical calendar
* identifier or an object.
*/
-class CalendarValue final {
+class MOZ_STACK_CLASS CalendarValue final {
JS::Value value_{};
public:
@@ -126,7 +127,7 @@ enum class CalendarMethod {
YearMonthFromFields,
};
-class CalendarRecord {
+class MOZ_STACK_CLASS CalendarRecord final {
CalendarValue receiver_;
// Null unless non-builtin calendar methods are used.
@@ -190,6 +191,7 @@ class CalendarRecord {
void trace(JSTracer* trc);
};
+struct DateDuration;
struct Duration;
struct PlainDate;
struct PlainDateTime;
@@ -199,6 +201,7 @@ class PlainDateTimeObject;
class PlainMonthDayObject;
class PlainYearMonthObject;
enum class CalendarOption;
+enum class TemporalOverflow;
enum class TemporalUnit;
/**
@@ -305,7 +308,7 @@ JSObject* CalendarMergeFields(JSContext* cx,
*/
Wrapped<PlainDateObject*> CalendarDateAdd(
JSContext* cx, JS::Handle<CalendarRecord> calendar,
- JS::Handle<Wrapped<PlainDateObject*>> date, const Duration& duration);
+ JS::Handle<Wrapped<PlainDateObject*>> date, const DateDuration& duration);
/**
* CalendarDateAdd ( calendarRec, date, duration [ , options ] )
@@ -336,14 +339,14 @@ Wrapped<PlainDateObject*> CalendarDateAdd(
* CalendarDateAdd ( calendarRec, date, duration [ , options ] )
*/
bool CalendarDateAdd(JSContext* cx, JS::Handle<CalendarRecord> calendar,
- const PlainDate& date, const Duration& duration,
+ const PlainDate& date, const DateDuration& duration,
PlainDate* result);
/**
* CalendarDateAdd ( calendarRec, date, duration [ , options ] )
*/
bool CalendarDateAdd(JSContext* cx, JS::Handle<CalendarRecord> calendar,
- const PlainDate& date, const Duration& duration,
+ const PlainDate& date, const DateDuration& duration,
JS::Handle<JSObject*> options, PlainDate* result);
/**
@@ -351,7 +354,22 @@ bool CalendarDateAdd(JSContext* cx, JS::Handle<CalendarRecord> calendar,
*/
bool CalendarDateAdd(JSContext* cx, JS::Handle<CalendarRecord> calendar,
JS::Handle<Wrapped<PlainDateObject*>> date,
- const Duration& duration, PlainDate* result);
+ const DateDuration& duration, PlainDate* result);
+
+/**
+ * CalendarDateUntil ( calendarRec, one, two, options )
+ */
+bool CalendarDateUntil(JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ const PlainDate& one, const PlainDate& two,
+ TemporalUnit largestUnit, DateDuration* result);
+
+/**
+ * CalendarDateUntil ( calendarRec, one, two, options )
+ */
+bool CalendarDateUntil(JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ const PlainDate& one, const PlainDate& two,
+ TemporalUnit largestUnit,
+ JS::Handle<PlainObject*> options, DateDuration* result);
/**
* CalendarDateUntil ( calendarRec, one, two, options )
@@ -359,7 +377,7 @@ bool CalendarDateAdd(JSContext* cx, JS::Handle<CalendarRecord> calendar,
bool CalendarDateUntil(JSContext* cx, JS::Handle<CalendarRecord> calendar,
JS::Handle<Wrapped<PlainDateObject*>> one,
JS::Handle<Wrapped<PlainDateObject*>> two,
- JS::Handle<PlainObject*> options, Duration* result);
+ TemporalUnit largestUnit, DateDuration* result);
/**
* CalendarDateUntil ( calendarRec, one, two, options )
@@ -367,7 +385,8 @@ bool CalendarDateUntil(JSContext* cx, JS::Handle<CalendarRecord> calendar,
bool CalendarDateUntil(JSContext* cx, JS::Handle<CalendarRecord> calendar,
JS::Handle<Wrapped<PlainDateObject*>> one,
JS::Handle<Wrapped<PlainDateObject*>> two,
- TemporalUnit largestUnit, Duration* result);
+ TemporalUnit largestUnit,
+ JS::Handle<PlainObject*> options, DateDuration* result);
/**
* CalendarYear ( calendar, dateLike )
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;
}
diff --git a/js/src/builtin/temporal/Duration.h b/js/src/builtin/temporal/Duration.h
index 47708458f4..e1aea4d1d4 100644
--- a/js/src/builtin/temporal/Duration.h
+++ b/js/src/builtin/temporal/Duration.h
@@ -89,11 +89,31 @@ enum class TemporalUnit;
int32_t DurationSign(const Duration& duration);
/**
+ * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds )
+ */
+int32_t DurationSign(const DateDuration& duration);
+
+/**
* IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
* milliseconds, microseconds, nanoseconds )
*/
bool IsValidDuration(const Duration& duration);
+#ifdef DEBUG
+/**
+ * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds )
+ */
+bool IsValidDuration(const DateDuration& duration);
+
+/**
+ * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds )
+ */
+bool IsValidDuration(const NormalizedDuration& duration);
+#endif
+
/**
* IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
* milliseconds, microseconds, nanoseconds )
@@ -101,6 +121,135 @@ bool IsValidDuration(const Duration& duration);
bool ThrowIfInvalidDuration(JSContext* cx, const Duration& duration);
/**
+ * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds )
+ */
+bool ThrowIfInvalidDuration(JSContext* cx, const DateDuration& duration);
+
+/**
+ * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds )
+ */
+inline bool IsValidNormalizedTimeDuration(
+ const NormalizedTimeDuration& duration) {
+ MOZ_ASSERT(0 <= duration.nanoseconds && duration.nanoseconds <= 999'999'999);
+
+ // Step 4.
+ //
+ // The absolute value of the seconds part of normalized time duration must be
+ // less-or-equal to `2**53 - 1` and the nanoseconds part must be less or equal
+ // to `999'999'999`.
+ return NormalizedTimeDuration::min() <= duration &&
+ duration <= NormalizedTimeDuration::max();
+}
+
+/**
+ * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
+ * nanoseconds )
+ */
+NormalizedTimeDuration NormalizeTimeDuration(int32_t hours, int32_t minutes,
+ int32_t seconds,
+ int32_t milliseconds,
+ int32_t microseconds,
+ int32_t nanoseconds);
+
+/**
+ * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
+ * nanoseconds )
+ */
+NormalizedTimeDuration NormalizeTimeDuration(const Duration& duration);
+
+/**
+ * CompareNormalizedTimeDuration ( one, two )
+ */
+inline int32_t CompareNormalizedTimeDuration(
+ const NormalizedTimeDuration& one, const NormalizedTimeDuration& two) {
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(one));
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(two));
+
+ // Step 1.
+ if (one > two) {
+ return 1;
+ }
+
+ // Step 2.
+ if (one < two) {
+ return -1;
+ }
+
+ // Step 3.
+ return 0;
+}
+
+/**
+ * NormalizedTimeDurationSign ( d )
+ */
+inline int32_t NormalizedTimeDurationSign(const NormalizedTimeDuration& d) {
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(d));
+
+ // Steps 1-3.
+ return CompareNormalizedTimeDuration(d, NormalizedTimeDuration{});
+}
+
+/**
+ * Add24HourDaysToNormalizedTimeDuration ( d, days )
+ */
+bool Add24HourDaysToNormalizedTimeDuration(JSContext* cx,
+ const NormalizedTimeDuration& d,
+ int64_t days,
+ NormalizedTimeDuration* result);
+
+/**
+ * CreateNormalizedDurationRecord ( years, months, weeks, days, norm )
+ */
+inline NormalizedDuration CreateNormalizedDurationRecord(
+ const DateDuration& date, const NormalizedTimeDuration& time) {
+ MOZ_ASSERT(IsValidDuration(date));
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(time));
+#ifdef DEBUG
+ int64_t dateValues = date.years | date.months | date.weeks | date.days;
+ int32_t dateSign = dateValues ? dateValues < 0 ? -1 : 1 : 0;
+ int32_t timeSign = NormalizedTimeDurationSign(time);
+ MOZ_ASSERT((dateSign * timeSign) >= 0);
+#endif
+
+ return {date, time};
+}
+
+/**
+ * CreateNormalizedDurationRecord ( years, months, weeks, days, norm )
+ */
+inline NormalizedDuration CreateNormalizedDurationRecord(
+ const Duration& duration) {
+ return CreateNormalizedDurationRecord(duration.toDateDuration(),
+ NormalizeTimeDuration(duration));
+}
+
+/**
+ * CombineDateAndNormalizedTimeDuration ( dateDurationRecord, norm )
+ */
+bool CombineDateAndNormalizedTimeDuration(JSContext* cx,
+ const DateDuration& date,
+ const NormalizedTimeDuration& time,
+ NormalizedDuration* result);
+
+/**
+ * CreateNormalizedDurationRecord ( years, months, weeks, days, norm )
+ */
+inline bool CreateNormalizedDurationRecord(JSContext* cx,
+ const DateDuration& date,
+ const NormalizedTimeDuration& time,
+ NormalizedDuration* result) {
+ return CombineDateAndNormalizedTimeDuration(cx, date, time, result);
+}
+
+/**
+ * NormalizedTimeDurationFromEpochNanosecondsDifference ( one, two )
+ */
+NormalizedTimeDuration NormalizedTimeDurationFromEpochNanosecondsDifference(
+ const Instant& one, const Instant& two);
+
+/**
* CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
* milliseconds, microseconds, nanoseconds [ , newTarget ] )
*/
@@ -126,17 +275,15 @@ bool ToTemporalDurationRecord(JSContext* cx,
Duration* result);
/**
- * BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds,
- * microseconds, nanoseconds, largestUnit )
+ * BalanceTimeDuration ( norm, largestUnit )
*/
-bool BalanceTimeDuration(JSContext* cx, const Duration& duration,
- TemporalUnit largestUnit, TimeDuration* result);
+TimeDuration BalanceTimeDuration(const NormalizedTimeDuration& duration,
+ TemporalUnit largestUnit);
/**
- * BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds,
- * microseconds, nanoseconds, largestUnit )
+ * BalanceTimeDuration ( norm, largestUnit )
*/
-bool BalanceTimeDuration(JSContext* cx, const InstantSpan& nanoseconds,
+bool BalanceTimeDuration(JSContext* cx, const NormalizedTimeDuration& duration,
TemporalUnit largestUnit, TimeDuration* result);
/**
@@ -144,61 +291,71 @@ bool BalanceTimeDuration(JSContext* cx, const InstantSpan& nanoseconds,
* smallestUnit, plainRelativeTo, calendarRec )
*/
bool BalanceDateDurationRelative(
- JSContext* cx, const Duration& duration, TemporalUnit largestUnit,
+ JSContext* cx, const DateDuration& duration, TemporalUnit largestUnit,
TemporalUnit smallestUnit,
JS::Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
JS::Handle<CalendarRecord> calendar, DateDuration* 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 AdjustRoundedDurationDays(JSContext* cx, const Duration& duration,
+bool AdjustRoundedDurationDays(JSContext* cx,
+ const NormalizedDuration& duration,
Increment increment, TemporalUnit unit,
TemporalRoundingMode roundingMode,
JS::Handle<ZonedDateTime> relativeTo,
JS::Handle<CalendarRecord> calendar,
JS::Handle<TimeZoneRecord> timeZone,
const PlainDateTime& precalculatedPlainDateTime,
- Duration* result);
+ NormalizedDuration* result);
/**
- * 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 ] ] ] ] ] )
*/
-bool RoundDuration(JSContext* cx, const Duration& duration, Increment increment,
- TemporalUnit unit, TemporalRoundingMode roundingMode,
- Duration* result);
+NormalizedTimeDuration RoundDuration(const NormalizedTimeDuration& duration,
+ Increment increment, TemporalUnit unit,
+ TemporalRoundingMode roundingMode);
/**
- * 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 ] ] ] ] ] )
*/
-bool RoundDuration(JSContext* cx, const Duration& duration, Increment increment,
- TemporalUnit unit, TemporalRoundingMode roundingMode,
+bool RoundDuration(JSContext* cx, const NormalizedTimeDuration& duration,
+ Increment increment, TemporalUnit unit,
+ TemporalRoundingMode roundingMode,
+ NormalizedTimeDuration* result);
+
+/**
+ * RoundDuration ( years, months, weeks, days, norm, increment, unit,
+ * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
+ * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
+ */
+bool RoundDuration(JSContext* cx, const NormalizedDuration& duration,
+ Increment increment, TemporalUnit unit,
+ TemporalRoundingMode roundingMode,
JS::Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
- JS::Handle<CalendarRecord> calendar, Duration* result);
+ JS::Handle<CalendarRecord> calendar,
+ NormalizedDuration* result);
/**
- * 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 ] ] ] ] ] )
*/
-bool RoundDuration(JSContext* cx, const Duration& duration, Increment increment,
- TemporalUnit unit, TemporalRoundingMode roundingMode,
+bool RoundDuration(JSContext* cx, const NormalizedDuration& duration,
+ Increment increment, TemporalUnit unit,
+ TemporalRoundingMode roundingMode,
JS::Handle<PlainDateObject*> plainRelativeTo,
JS::Handle<CalendarRecord> calendar,
JS::Handle<ZonedDateTime> zonedRelativeTo,
JS::Handle<TimeZoneRecord> timeZone,
const PlainDateTime& precalculatedPlainDateTime,
- Duration* result);
+ NormalizedDuration* result);
/**
* DaysUntil ( earlier, later )
diff --git a/js/src/builtin/temporal/Instant.cpp b/js/src/builtin/temporal/Instant.cpp
index 78fc15f313..8e38f3ad51 100644
--- a/js/src/builtin/temporal/Instant.cpp
+++ b/js/src/builtin/temporal/Instant.cpp
@@ -7,6 +7,7 @@
#include "builtin/temporal/Instant.h"
#include "mozilla/Assertions.h"
+#include "mozilla/Casting.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Maybe.h"
@@ -164,43 +165,18 @@ bool js::temporal::IsValidEpochInstant(const Instant& instant) {
return Instant::min() <= instant && instant <= Instant::max();
}
-static constexpr auto NanosecondsMaxInstantSpan() {
- static_assert(BigInt::DigitBits == 64 || BigInt::DigitBits == 32);
-
- // ±8.64 × 10^21 is the nanoseconds from epoch limit.
- // 2 × 8.64 × 10^21 is 172_80000_00000_00000_00000 or 0x3a8_c02c5ea2_de000000.
- // Return the BigInt digits of that number for fast BigInt comparisons.
- if constexpr (BigInt::DigitBits == 64) {
- return std::array{
- BigInt::Digit(0x3a8),
- BigInt::Digit(0xc02c'5ea2'de00'0000),
- };
- } else {
- return std::array{
- BigInt::Digit(0x3a8),
- BigInt::Digit(0xc02c'5ea2),
- BigInt::Digit(0xde00'0000),
- };
- }
-}
-
+#ifdef DEBUG
/**
* Validates a nanoseconds amount is at most as large as the difference
* between two valid nanoseconds from the epoch instants.
- *
- * Useful when we want to ensure a BigInt doesn't exceed a certain limit.
*/
-bool js::temporal::IsValidInstantSpan(const BigInt* nanoseconds) {
- static constexpr auto spanLimit = NanosecondsMaxInstantSpan();
- return AbsoluteValueIsLessOrEqual<spanLimit>(nanoseconds);
-}
-
bool js::temporal::IsValidInstantSpan(const InstantSpan& span) {
MOZ_ASSERT(0 <= span.nanoseconds && span.nanoseconds <= 999'999'999);
// Steps 1-3.
return InstantSpan::min() <= span && span <= InstantSpan::max();
}
+#endif
/**
* Return the BigInt as a 96-bit integer. The BigInt digits must not consist of
@@ -258,14 +234,6 @@ Instant js::temporal::ToInstant(const BigInt* epochNanoseconds) {
return {seconds, nanos};
}
-InstantSpan js::temporal::ToInstantSpan(const BigInt* nanoseconds) {
- MOZ_ASSERT(IsValidInstantSpan(nanoseconds));
-
- auto [seconds, nanos] =
- ToInt96(nanoseconds) / ToNanoseconds(TemporalUnit::Second);
- return {seconds, nanos};
-}
-
static BigInt* CreateBigInt(JSContext* cx,
const std::array<uint32_t, 3>& digits,
bool negative) {
@@ -300,9 +268,7 @@ static BigInt* CreateBigInt(JSContext* cx,
}
}
-static BigInt* ToEpochBigInt(JSContext* cx, const InstantSpan& instant) {
- MOZ_ASSERT(IsValidInstantSpan(instant));
-
+static auto ToBigIntDigits(uint64_t seconds, uint32_t nanoseconds) {
// Multiplies two uint32_t values and returns the lower 32-bits. The higher
// 32-bits are stored in |high|.
auto digitMul = [](uint32_t a, uint32_t b, uint32_t* high) {
@@ -321,20 +287,6 @@ static BigInt* ToEpochBigInt(JSContext* cx, const InstantSpan& instant) {
constexpr uint32_t secToNanos = ToNanoseconds(TemporalUnit::Second);
- uint64_t seconds = std::abs(instant.seconds);
- uint32_t nanoseconds = instant.nanoseconds;
-
- // 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 from the epoch value |-1n| is represented as
- // the instant {seconds: -1, nanoseconds: 999'999'999}.
- if (instant.seconds < 0 && nanoseconds != 0) {
- nanoseconds = secToNanos - nanoseconds;
- seconds -= 1;
- }
-
// uint32_t digits stored in the same order as BigInt digits, i.e. the least
// significant digit is stored at index zero.
std::array<uint32_t, 2> multiplicand = {uint32_t(seconds),
@@ -362,93 +314,34 @@ static BigInt* ToEpochBigInt(JSContext* cx, const InstantSpan& instant) {
MOZ_ASSERT(newCarry == 0);
}
- return CreateBigInt(cx, accumulator, instant.seconds < 0);
-}
-
-BigInt* js::temporal::ToEpochNanoseconds(JSContext* cx,
- const Instant& instant) {
- MOZ_ASSERT(IsValidEpochInstant(instant));
- return ::ToEpochBigInt(cx, InstantSpan{instant.seconds, instant.nanoseconds});
-}
-
-BigInt* js::temporal::ToEpochNanoseconds(JSContext* cx,
- const InstantSpan& instant) {
- MOZ_ASSERT(IsValidInstantSpan(instant));
- return ::ToEpochBigInt(cx, instant);
-}
-
-/**
- * Return an Instant for the input nanoseconds if the input is less-or-equal to
- * the maximum instant span. Otherwise returns nothing.
- */
-static mozilla::Maybe<InstantSpan> NanosecondsToInstantSpan(
- double nanoseconds) {
- MOZ_ASSERT(IsInteger(nanoseconds));
-
- if (auto int96 = Int96::fromInteger(nanoseconds)) {
- constexpr auto maximum = Int96{InstantSpan::max().toSeconds()} *
- ToNanoseconds(TemporalUnit::Second);
-
- // Accept if the value is less-or-equal to the maximum instant span.
- if (int96->abs() <= maximum) {
- // Split into seconds and nanoseconds.
- auto [seconds, nanos] = *int96 / ToNanoseconds(TemporalUnit::Second);
-
- auto result = InstantSpan{seconds, nanos};
- MOZ_ASSERT(IsValidInstantSpan(result));
- return mozilla::Some(result);
- }
- }
- return mozilla::Nothing();
+ return accumulator;
}
-/**
- * Return an Instant for the input microseconds if the input is less-or-equal to
- * the maximum instant span. Otherwise returns nothing.
- */
-static mozilla::Maybe<InstantSpan> MicrosecondsToInstantSpan(
- double microseconds) {
- MOZ_ASSERT(IsInteger(microseconds));
+template <typename T>
+static BigInt* ToBigInt(JSContext* cx,
+ const SecondsAndNanoseconds<T>& secondsAndNanoseconds) {
+ uint64_t seconds = std::abs(secondsAndNanoseconds.seconds);
+ uint32_t nanoseconds = secondsAndNanoseconds.nanoseconds;
- constexpr int64_t spanLimit = InstantSpan::max().toSeconds();
- constexpr int64_t secToMicros = ToNanoseconds(TemporalUnit::Second) /
- ToNanoseconds(TemporalUnit::Microsecond);
- constexpr int32_t microToNanos = ToNanoseconds(TemporalUnit::Microsecond);
-
- // Fast path for the common case.
- if (microseconds == 0) {
- return mozilla::Some(InstantSpan{});
- }
-
- // Reject if the value is larger than the maximum instant span.
- if (std::abs(microseconds) > double(spanLimit) * double(secToMicros)) {
- return mozilla::Nothing();
+ // 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 from the epoch value |-1n| is represented as
+ // the instant {seconds: -1, nanoseconds: 999'999'999}.
+ if (secondsAndNanoseconds.seconds < 0 && nanoseconds != 0) {
+ nanoseconds = ToNanoseconds(TemporalUnit::Second) - nanoseconds;
+ seconds -= 1;
}
- // |spanLimit| in microseconds is below UINT64_MAX, so we can use uint64 in
- // the following computations.
- static_assert(double(spanLimit) * double(secToMicros) <= double(UINT64_MAX));
-
- // Use the absolute value and convert it then into uint64_t.
- uint64_t absMicros = uint64_t(std::abs(microseconds));
-
- // Seconds and remainder are small enough to fit into int64_t resp. int32_t.
- int64_t seconds = absMicros / uint64_t(secToMicros);
- int32_t remainder = absMicros % uint64_t(secToMicros);
-
- // Correct the sign of |seconds| and |remainder|, and then constrain
- // |remainder| to the range [0, 999'999].
- if (microseconds < 0) {
- seconds *= -1;
- if (remainder != 0) {
- seconds -= 1;
- remainder = secToMicros - remainder;
- }
- }
+ auto digits = ToBigIntDigits(seconds, nanoseconds);
+ return CreateBigInt(cx, digits, secondsAndNanoseconds.seconds < 0);
+}
- InstantSpan result = {seconds, remainder * microToNanos};
- MOZ_ASSERT(IsValidInstantSpan(result));
- return mozilla::Some(result);
+BigInt* js::temporal::ToEpochNanoseconds(JSContext* cx,
+ const Instant& instant) {
+ MOZ_ASSERT(IsValidEpochInstant(instant));
+ return ::ToBigInt(cx, instant);
}
/**
@@ -456,7 +349,7 @@ static mozilla::Maybe<InstantSpan> MicrosecondsToInstantSpan(
* microsecond, nanosecond [ , offsetNanoseconds ] )
*/
Instant js::temporal::GetUTCEpochNanoseconds(const PlainDateTime& dateTime) {
- auto& [date, time] = dateTime;
+ const auto& [date, time] = dateTime;
// Step 1.
MOZ_ASSERT(IsValidISODateTime(dateTime));
@@ -579,13 +472,13 @@ Wrapped<InstantObject*> js::temporal::ToTemporalInstant(JSContext* cx,
}
}
- // Steps 1.b-d and 3-6
+ // Steps 1.b-d and 3-7
Instant epochNanoseconds;
if (!ToTemporalInstant(cx, item, &epochNanoseconds)) {
return nullptr;
}
- // Step 7.
+ // Step 8.
return CreateTemporalInstant(cx, epochNanoseconds);
}
@@ -635,25 +528,25 @@ bool js::temporal::ToTemporalInstant(JSContext* cx, Handle<Value> item,
}
MOZ_ASSERT(std::abs(offset) < ToNanoseconds(TemporalUnit::Day));
- // Step 6. (Reordered)
+ // Steps 5-6. (Reordered)
if (!ISODateTimeWithinLimits(dateTime)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_INSTANT_INVALID);
return false;
}
- // Step 5.
+ // Step 4.
auto epochNanoseconds =
GetUTCEpochNanoseconds(dateTime, InstantSpan::fromNanoseconds(offset));
- // Step 6.
+ // Step 7.
if (!IsValidEpochInstant(epochNanoseconds)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_INSTANT_INVALID);
return false;
}
- // Step 7.
+ // Step 8.
*result = epochNanoseconds;
return true;
}
@@ -663,181 +556,75 @@ bool js::temporal::ToTemporalInstant(JSContext* cx, Handle<Value> item,
* microseconds, nanoseconds )
*/
bool js::temporal::AddInstant(JSContext* cx, const Instant& instant,
- const Duration& duration, Instant* result) {
+ const NormalizedTimeDuration& duration,
+ Instant* result) {
MOZ_ASSERT(IsValidEpochInstant(instant));
- MOZ_ASSERT(IsValidDuration(duration));
- MOZ_ASSERT(duration.years == 0);
- MOZ_ASSERT(duration.months == 0);
- MOZ_ASSERT(duration.weeks == 0);
- MOZ_ASSERT(duration.days == 0);
-
- do {
- auto nanoseconds = NanosecondsToInstantSpan(duration.nanoseconds);
- if (!nanoseconds) {
- break;
- }
- MOZ_ASSERT(IsValidInstantSpan(*nanoseconds));
-
- auto microseconds = MicrosecondsToInstantSpan(duration.microseconds);
- if (!microseconds) {
- break;
- }
- MOZ_ASSERT(IsValidInstantSpan(*microseconds));
-
- // Overflows for millis/seconds/minutes/hours always result in an invalid
- // instant.
-
- int64_t milliseconds;
- if (!mozilla::NumberEqualsInt64(duration.milliseconds, &milliseconds)) {
- break;
- }
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
- int64_t seconds;
- if (!mozilla::NumberEqualsInt64(duration.seconds, &seconds)) {
- break;
- }
+ // Step 1. (Inlined AddNormalizedTimeDurationToEpochNanoseconds)
+ auto r = instant + duration.to<InstantSpan>();
- int64_t minutes;
- if (!mozilla::NumberEqualsInt64(duration.minutes, &minutes)) {
- break;
- }
-
- int64_t hours;
- if (!mozilla::NumberEqualsInt64(duration.hours, &hours)) {
- break;
- }
-
- // Compute the overall amount of milliseconds to add.
- mozilla::CheckedInt64 millis = hours;
- millis *= 60;
- millis += minutes;
- millis *= 60;
- millis += seconds;
- millis *= 1000;
- millis += milliseconds;
- if (!millis.isValid()) {
- break;
- }
-
- auto milli = InstantSpan::fromMilliseconds(millis.value());
- if (!IsValidInstantSpan(milli)) {
- break;
- }
-
- // Compute the overall instant span.
- auto span = milli + *microseconds + *nanoseconds;
- if (!IsValidInstantSpan(span)) {
- break;
- }
-
- *result = instant + span;
- if (IsValidEpochInstant(*result)) {
- return true;
- }
- } while (false);
+ // Step 2.
+ if (!IsValidEpochInstant(r)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INSTANT_INVALID);
+ return false;
+ }
- JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
- JSMSG_TEMPORAL_INSTANT_INVALID);
- return false;
+ // Step 3.
+ *result = r;
+ return true;
}
/**
- * DifferenceInstant ( ns1, ns2, roundingIncrement, smallestUnit, largestUnit,
- * roundingMode )
+ * DifferenceInstant ( ns1, ns2, roundingIncrement, smallestUnit, roundingMode )
*/
-bool js::temporal::DifferenceInstant(JSContext* cx, const Instant& ns1,
- const Instant& ns2,
- Increment roundingIncrement,
- TemporalUnit smallestUnit,
- TemporalUnit largestUnit,
- TemporalRoundingMode roundingMode,
- Duration* result) {
+NormalizedTimeDuration js::temporal::DifferenceInstant(
+ const Instant& ns1, const Instant& ns2, Increment roundingIncrement,
+ TemporalUnit smallestUnit, TemporalRoundingMode roundingMode) {
MOZ_ASSERT(IsValidEpochInstant(ns1));
MOZ_ASSERT(IsValidEpochInstant(ns2));
- MOZ_ASSERT(largestUnit > TemporalUnit::Day);
- MOZ_ASSERT(largestUnit <= smallestUnit);
+ MOZ_ASSERT(smallestUnit > TemporalUnit::Day);
MOZ_ASSERT(roundingIncrement <=
MaximumTemporalDurationRoundingIncrement(smallestUnit));
// Step 1.
- auto diff = ns2 - ns1;
- MOZ_ASSERT(IsValidInstantSpan(diff));
-
- // Negative nanoseconds are represented as the difference to 1'000'000'000.
- auto [seconds, nanoseconds] = diff;
- if (seconds < 0 && nanoseconds != 0) {
- seconds += 1;
- nanoseconds -= ToNanoseconds(TemporalUnit::Second);
- }
-
- // Steps 2-5.
- Duration duration = {
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- double(seconds),
- double((nanoseconds / 1000'000) % 1000),
- double((nanoseconds / 1000) % 1000),
- double(nanoseconds % 1000),
- };
- MOZ_ASSERT(IsValidDuration(duration));
+ auto diff = NormalizedTimeDurationFromEpochNanosecondsDifference(ns2, ns1);
+ MOZ_ASSERT(IsValidInstantSpan(diff.to<InstantSpan>()));
- // Step 6.
+ // Step 2.
if (smallestUnit == TemporalUnit::Nanosecond &&
roundingIncrement == Increment{1}) {
- TimeDuration balanced;
- if (!BalanceTimeDuration(cx, duration, largestUnit, &balanced)) {
- return false;
- }
- MOZ_ASSERT(balanced.days == 0);
-
- *result = balanced.toDuration().time();
- return true;
- }
-
- // Steps 7-8.
- Duration roundResult;
- if (!temporal::RoundDuration(cx, duration, roundingIncrement, smallestUnit,
- roundingMode, &roundResult)) {
- return false;
- }
-
- // Step 9.
- MOZ_ASSERT(roundResult.days == 0);
-
- // Step 10.
- TimeDuration balanced;
- if (!BalanceTimeDuration(cx, roundResult, largestUnit, &balanced)) {
- return false;
+ return diff;
}
- MOZ_ASSERT(balanced.days == 0);
- *result = balanced.toDuration().time();
- return true;
+ // Steps 3-4.
+ return RoundDuration(diff, roundingIncrement, smallestUnit, roundingMode);
}
/**
* RoundNumberToIncrementAsIfPositive ( x, increment, roundingMode )
*/
-static bool RoundNumberToIncrementAsIfPositive(
- JSContext* cx, const Instant& x, int64_t increment,
- TemporalRoundingMode roundingMode, Instant* result) {
+static Instant RoundNumberToIncrementAsIfPositive(
+ const Instant& x, int64_t increment, TemporalRoundingMode roundingMode) {
+ MOZ_ASSERT(IsValidEpochInstant(x));
+ MOZ_ASSERT(increment > 0);
+ MOZ_ASSERT(increment <= ToNanoseconds(TemporalUnit::Day));
+
// This operation is equivalent to adjusting the rounding mode through
// |ToPositiveRoundingMode| and then calling |RoundNumberToIncrement|.
- return RoundNumberToIncrement(cx, x, increment,
- ToPositiveRoundingMode(roundingMode), result);
+ auto rounded = RoundNumberToIncrement(x.toNanoseconds(), Int128{increment},
+ ToPositiveRoundingMode(roundingMode));
+ return Instant::fromNanoseconds(rounded);
}
/**
* RoundTemporalInstant ( ns, increment, unit, roundingMode )
*/
-bool js::temporal::RoundTemporalInstant(JSContext* cx, const Instant& ns,
- Increment increment, TemporalUnit unit,
- TemporalRoundingMode roundingMode,
- Instant* result) {
+Instant js::temporal::RoundTemporalInstant(const Instant& ns,
+ Increment increment,
+ TemporalUnit unit,
+ TemporalRoundingMode roundingMode) {
MOZ_ASSERT(IsValidEpochInstant(ns));
MOZ_ASSERT(increment >= Increment::min());
MOZ_ASSERT(uint64_t(increment.value()) <= ToNanoseconds(TemporalUnit::Day));
@@ -851,7 +638,7 @@ bool js::temporal::RoundTemporalInstant(JSContext* cx, const Instant& ns,
// Step 7.
return RoundNumberToIncrementAsIfPositive(
- cx, ns, increment.value() * toNanoseconds, roundingMode, result);
+ ns, increment.value() * toNanoseconds, roundingMode);
}
/**
@@ -903,19 +690,23 @@ static bool DifferenceTemporalInstant(JSContext* cx,
}
// Step 5.
- Duration difference;
- if (!DifferenceInstant(cx, instant, other, settings.roundingIncrement,
- settings.smallestUnit, settings.largestUnit,
- settings.roundingMode, &difference)) {
+ auto difference =
+ DifferenceInstant(instant, other, settings.roundingIncrement,
+ settings.smallestUnit, settings.roundingMode);
+
+ // Step 6.
+ TimeDuration balanced;
+ if (!BalanceTimeDuration(cx, difference, settings.largestUnit, &balanced)) {
return false;
}
- // Step 6.
+ // Step 7.
+ auto duration = balanced.toDuration();
if (operation == TemporalDifference::Since) {
- difference = difference.negate();
+ duration = duration.negate();
}
- auto* obj = CreateTemporalDuration(cx, difference);
+ auto* obj = CreateTemporalDuration(cx, duration);
if (!obj) {
return false;
}
@@ -959,13 +750,15 @@ static bool AddDurationToOrSubtractDurationFromInstant(
if (operation == InstantDuration::Subtract) {
duration = duration.negate();
}
+ auto timeDuration = NormalizeTimeDuration(duration);
+ // Step 8.
Instant ns;
- if (!AddInstant(cx, epochNanoseconds, duration, &ns)) {
+ if (!AddInstant(cx, epochNanoseconds, timeDuration, &ns)) {
return false;
}
- // Step 8.
+ // Step 9.
auto* result = CreateTemporalInstant(cx, ns);
if (!result) {
return false;
@@ -1063,7 +856,8 @@ static bool Instant_fromEpochSeconds(JSContext* cx, unsigned argc, Value* vp) {
}
// Step 5.
- auto* result = CreateTemporalInstant(cx, Instant::fromSeconds(epochSeconds));
+ int64_t seconds = mozilla::AssertedCast<int64_t>(epochSeconds);
+ auto* result = CreateTemporalInstant(cx, Instant::fromSeconds(seconds));
if (!result) {
return false;
}
@@ -1106,8 +900,9 @@ static bool Instant_fromEpochMilliseconds(JSContext* cx, unsigned argc,
}
// Step 5.
+ int64_t milliseconds = mozilla::AssertedCast<int64_t>(epochMilliseconds);
auto* result =
- CreateTemporalInstant(cx, Instant::fromMilliseconds(epochMilliseconds));
+ CreateTemporalInstant(cx, Instant::fromMilliseconds(milliseconds));
if (!result) {
return false;
}
@@ -1413,7 +1208,7 @@ static bool Instant_round(JSContext* cx, const CallArgs& args) {
}
// Steps 10-15.
- uint64_t maximum = UnitsPerDay(smallestUnit);
+ int64_t maximum = UnitsPerDay(smallestUnit);
// Step 16.
if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
@@ -1423,11 +1218,8 @@ static bool Instant_round(JSContext* cx, const CallArgs& args) {
}
// Step 17.
- Instant roundedNs;
- if (!RoundTemporalInstant(cx, instant, roundingIncrement, smallestUnit,
- roundingMode, &roundedNs)) {
- return false;
- }
+ auto roundedNs = RoundTemporalInstant(instant, roundingIncrement,
+ smallestUnit, roundingMode);
// Step 18.
auto* result = CreateTemporalInstant(cx, roundedNs);
@@ -1535,11 +1327,8 @@ static bool Instant_toString(JSContext* cx, const CallArgs& args) {
}
// Step 12.
- Instant ns;
- if (!RoundTemporalInstant(cx, instant, precision.increment, precision.unit,
- roundingMode, &ns)) {
- return false;
- }
+ auto ns = RoundTemporalInstant(instant, precision.increment, precision.unit,
+ roundingMode);
// Step 13.
Rooted<InstantObject*> roundedInstant(cx, CreateTemporalInstant(cx, ns));
diff --git a/js/src/builtin/temporal/Instant.h b/js/src/builtin/temporal/Instant.h
index edce677d10..9ad7f159db 100644
--- a/js/src/builtin/temporal/Instant.h
+++ b/js/src/builtin/temporal/Instant.h
@@ -69,15 +69,12 @@ bool IsValidEpochNanoseconds(const JS::BigInt* epochNanoseconds);
*/
bool IsValidEpochInstant(const Instant& instant);
+#ifdef DEBUG
/**
* Return true if the input is within the valid instant span limits.
*/
bool IsValidInstantSpan(const InstantSpan& span);
-
-/**
- * Return true if the input is within the valid instant span limits.
- */
-bool IsValidInstantSpan(const JS::BigInt* nanoseconds);
+#endif
/**
* Convert a BigInt to an instant. The input must be a valid epoch nanoseconds
@@ -86,22 +83,11 @@ bool IsValidInstantSpan(const JS::BigInt* nanoseconds);
Instant ToInstant(const JS::BigInt* epochNanoseconds);
/**
- * Convert a BigInt to an instant span. The input must be a valid epoch
- * nanoseconds span value.
- */
-InstantSpan ToInstantSpan(const JS::BigInt* nanoseconds);
-
-/**
* Convert an instant to a BigInt. The input must be a valid epoch instant.
*/
JS::BigInt* ToEpochNanoseconds(JSContext* cx, const Instant& instant);
/**
- * Convert an instant span to a BigInt. The input must be a valid instant span.
- */
-JS::BigInt* ToEpochNanoseconds(JSContext* cx, const InstantSpan& instant);
-
-/**
* ToTemporalInstant ( item )
*/
Wrapped<InstantObject*> ToTemporalInstant(JSContext* cx,
@@ -134,25 +120,23 @@ Instant GetUTCEpochNanoseconds(const PlainDateTime& dateTime,
/**
* RoundTemporalInstant ( ns, increment, unit, roundingMode )
*/
-bool RoundTemporalInstant(JSContext* cx, const Instant& ns, Increment increment,
- TemporalUnit unit, TemporalRoundingMode roundingMode,
- Instant* result);
+Instant RoundTemporalInstant(const Instant& ns, Increment increment,
+ TemporalUnit unit,
+ TemporalRoundingMode roundingMode);
/**
- * AddInstant ( epochNanoseconds, hours, minutes, seconds, milliseconds,
- * microseconds, nanoseconds )
+ * AddInstant ( epochNanoseconds, norm )
*/
-bool AddInstant(JSContext* cx, const Instant& instant, const Duration& duration,
- Instant* result);
+bool AddInstant(JSContext* cx, const Instant& instant,
+ const NormalizedTimeDuration& duration, Instant* result);
/**
- * DifferenceInstant ( ns1, ns2, roundingIncrement, smallestUnit, largestUnit,
- * roundingMode )
+ * DifferenceInstant ( ns1, ns2, roundingIncrement, smallestUnit, roundingMode )
*/
-bool DifferenceInstant(JSContext* cx, const Instant& ns1, const Instant& ns2,
- Increment roundingIncrement, TemporalUnit smallestUnit,
- TemporalUnit largestUnit,
- TemporalRoundingMode roundingMode, Duration* result);
+NormalizedTimeDuration DifferenceInstant(const Instant& ns1, const Instant& ns2,
+ Increment roundingIncrement,
+ TemporalUnit smallestUnit,
+ TemporalRoundingMode roundingMode);
} /* namespace js::temporal */
diff --git a/js/src/builtin/temporal/Int128.cpp b/js/src/builtin/temporal/Int128.cpp
new file mode 100644
index 0000000000..e712a64be6
--- /dev/null
+++ b/js/src/builtin/temporal/Int128.cpp
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "builtin/temporal/Int128.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Casting.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/MathAlgorithms.h"
+
+#include <stdint.h>
+
+using namespace js;
+using namespace js::temporal;
+
+double Uint128::toDouble(const Uint128& x, bool negative) {
+ // Simplified version of |BigInt::numberValue()| for DigitBits=64. See the
+ // comments in |BigInt::numberValue()| for how this code works.
+
+ using Double = mozilla::FloatingPoint<double>;
+ constexpr uint8_t ExponentShift = Double::kExponentShift;
+ constexpr uint8_t SignificandWidth = Double::kSignificandWidth;
+ constexpr unsigned ExponentBias = Double::kExponentBias;
+ constexpr uint8_t SignShift = Double::kExponentWidth + SignificandWidth;
+
+ constexpr uint64_t MaxIntegralPrecisionDouble = uint64_t(1)
+ << (SignificandWidth + 1);
+
+ // We compute the final mantissa of the result, shifted upward to the top of
+ // the `uint64_t` space -- plus an extra bit to detect potential rounding.
+ constexpr uint8_t BitsNeededForShiftedMantissa = SignificandWidth + 1;
+
+ uint64_t shiftedMantissa = 0;
+ uint64_t exponent = 0;
+
+ // If the extra bit is set, correctly rounding the result may require
+ // examining all lower-order bits. Also compute 1) the index of the Digit
+ // storing the extra bit, and 2) whether bits beneath the extra bit in that
+ // Digit are nonzero so we can round if needed.
+ uint64_t bitsBeneathExtraBitInDigitContainingExtraBit = 0;
+
+ if (x.high == 0) {
+ uint64_t msd = x.low;
+
+ // Fast path for the likely-common case of up to a uint64_t of magnitude not
+ // exceeding integral precision in IEEE-754.
+ if (msd <= MaxIntegralPrecisionDouble) {
+ return negative ? -double(msd) : +double(msd);
+ }
+
+ const uint8_t msdLeadingZeroes = mozilla::CountLeadingZeroes64(msd);
+ MOZ_ASSERT(0 <= msdLeadingZeroes && msdLeadingZeroes <= 10,
+ "leading zeroes is at most 10 when the fast path isn't taken");
+
+ exponent = 64 - msdLeadingZeroes - 1;
+
+ // Omit the most significant bit: the IEEE-754 format includes this bit
+ // implicitly for all double-precision integers.
+ const uint8_t msdIgnoredBits = msdLeadingZeroes + 1;
+ MOZ_ASSERT(1 <= msdIgnoredBits && msdIgnoredBits <= 11);
+
+ const uint8_t msdIncludedBits = 64 - msdIgnoredBits;
+ MOZ_ASSERT(53 <= msdIncludedBits && msdIncludedBits <= 63);
+ MOZ_ASSERT(msdIncludedBits >= BitsNeededForShiftedMantissa);
+
+ // Munge the most significant bits of the number into proper
+ // position in an IEEE-754 double and go to town.
+
+ // Shift `msd`'s contributed bits upward to remove high-order zeroes and the
+ // highest set bit (which is implicit in IEEE-754 integral values so must be
+ // removed) and to add low-order zeroes. (Lower-order garbage bits are
+ // discarded when `shiftedMantissa` is converted to a real mantissa.)
+ shiftedMantissa = msd << (64 - msdIncludedBits);
+
+ // Add shifted bits to `shiftedMantissa` until we have a complete mantissa
+ // and an extra bit.
+ const uint8_t countOfBitsInDigitBelowExtraBit =
+ 64 - BitsNeededForShiftedMantissa - msdIgnoredBits;
+ bitsBeneathExtraBitInDigitContainingExtraBit =
+ msd & ((uint64_t(1) << countOfBitsInDigitBelowExtraBit) - 1);
+ } else {
+ uint64_t msd = x.high;
+ uint64_t second = x.low;
+
+ uint8_t msdLeadingZeroes = mozilla::CountLeadingZeroes64(msd);
+
+ exponent = 2 * 64 - msdLeadingZeroes - 1;
+
+ // Munge the most significant bits of the number into proper
+ // position in an IEEE-754 double and go to town.
+
+ // Omit the most significant bit: the IEEE-754 format includes this bit
+ // implicitly for all double-precision integers.
+ const uint8_t msdIgnoredBits = msdLeadingZeroes + 1;
+ const uint8_t msdIncludedBits = 64 - msdIgnoredBits;
+
+ // Shift `msd`'s contributed bits upward to remove high-order zeroes and the
+ // highest set bit (which is implicit in IEEE-754 integral values so must be
+ // removed) and to add low-order zeroes. (Lower-order garbage bits are
+ // discarded when `shiftedMantissa` is converted to a real mantissa.)
+ shiftedMantissa = msdIncludedBits == 0 ? 0 : msd << (64 - msdIncludedBits);
+
+ // Add shifted bits to `shiftedMantissa` until we have a complete mantissa
+ // and an extra bit.
+ if (msdIncludedBits >= BitsNeededForShiftedMantissa) {
+ const uint8_t countOfBitsInDigitBelowExtraBit =
+ 64 - BitsNeededForShiftedMantissa - msdIgnoredBits;
+ bitsBeneathExtraBitInDigitContainingExtraBit =
+ msd & ((uint64_t(1) << countOfBitsInDigitBelowExtraBit) - 1);
+
+ if (bitsBeneathExtraBitInDigitContainingExtraBit == 0) {
+ bitsBeneathExtraBitInDigitContainingExtraBit = second;
+ }
+ } else {
+ shiftedMantissa |= second >> msdIncludedBits;
+
+ const uint8_t countOfBitsInSecondDigitBelowExtraBit =
+ (msdIncludedBits + 64) - BitsNeededForShiftedMantissa;
+ bitsBeneathExtraBitInDigitContainingExtraBit =
+ second << (64 - countOfBitsInSecondDigitBelowExtraBit);
+ }
+ }
+
+ constexpr uint64_t LeastSignificantBit = uint64_t(1)
+ << (64 - SignificandWidth);
+ constexpr uint64_t ExtraBit = LeastSignificantBit >> 1;
+
+ // The extra bit must be set for rounding to change the mantissa.
+ if ((shiftedMantissa & ExtraBit) != 0) {
+ bool shouldRoundUp;
+ if (shiftedMantissa & LeastSignificantBit) {
+ // If the lowest mantissa bit is set, it doesn't matter what lower bits
+ // are: nearest-even rounds up regardless.
+ shouldRoundUp = true;
+ } else {
+ // If the lowest mantissa bit is unset, *all* lower bits are relevant.
+ // All-zero bits below the extra bit situates `x` halfway between two
+ // values, and the nearest *even* value lies downward. But if any bit
+ // below the extra bit is set, `x` is closer to the rounded-up value.
+ shouldRoundUp = bitsBeneathExtraBitInDigitContainingExtraBit != 0;
+ }
+
+ if (shouldRoundUp) {
+ // Add one to the significand bits. If they overflow, the exponent must
+ // also be increased. If *that* overflows, return the correct infinity.
+ uint64_t before = shiftedMantissa;
+ shiftedMantissa += ExtraBit;
+ if (shiftedMantissa < before) {
+ exponent++;
+ }
+ }
+ }
+
+ uint64_t significandBits = shiftedMantissa >> (64 - SignificandWidth);
+ uint64_t signBit = uint64_t(negative ? 1 : 0) << SignShift;
+ uint64_t exponentBits = (exponent + ExponentBias) << ExponentShift;
+ return mozilla::BitwiseCast<double>(signBit | exponentBits | significandBits);
+}
diff --git a/js/src/builtin/temporal/Int128.h b/js/src/builtin/temporal/Int128.h
new file mode 100644
index 0000000000..b32edb4da5
--- /dev/null
+++ b/js/src/builtin/temporal/Int128.h
@@ -0,0 +1,742 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef builtin_temporal_Int128_h
+#define builtin_temporal_Int128_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/MathAlgorithms.h"
+
+#include <climits>
+#include <limits>
+#include <stdint.h>
+#include <utility>
+
+namespace js::temporal {
+
+class Int128;
+class Uint128;
+
+/**
+ * Unsigned 128-bit integer, implemented as a pair of unsigned 64-bit integers.
+ *
+ * Supports all basic arithmetic operators.
+ */
+class alignas(16) Uint128 final {
+#if MOZ_LITTLE_ENDIAN()
+ uint64_t low = 0;
+ uint64_t high = 0;
+#else
+ uint64_t high = 0;
+ uint64_t low = 0;
+#endif
+
+ friend class Int128;
+
+ constexpr Uint128(uint64_t low, uint64_t high) : low(low), high(high) {}
+
+ /**
+ * Return the high double-word of the multiplication of `u * v`.
+ *
+ * Based on "Multiply high unsigned" from Hacker's Delight, 2nd edition,
+ * figure 8-2.
+ */
+ static constexpr uint64_t mulhu(uint64_t u, uint64_t v) {
+ uint64_t u0 = u & 0xffff'ffff;
+ uint64_t u1 = u >> 32;
+
+ uint64_t v0 = v & 0xffff'ffff;
+ uint64_t v1 = v >> 32;
+
+ uint64_t w0 = u0 * v0;
+ uint64_t t = u1 * v0 + (w0 >> 32);
+ uint64_t w1 = t & 0xffff'ffff;
+ uint64_t w2 = t >> 32;
+ w1 = u0 * v1 + w1;
+ return u1 * v1 + w2 + (w1 >> 32);
+ }
+
+ /**
+ * Based on "Unsigned doubleword division from long division" from
+ * Hacker's Delight, 2nd edition, figure 9-5.
+ */
+ static constexpr std::pair<Uint128, Uint128> udivdi(const Uint128& u,
+ const Uint128& v) {
+ MOZ_ASSERT(v != Uint128{});
+
+ // If v < 2**64
+ if (v.high == 0) {
+ // If u < 2**64
+ if (u.high == 0) {
+ // Prefer built-in division if possible.
+ return {Uint128{u.low / v.low, 0}, Uint128{u.low % v.low, 0}};
+ }
+
+ // If u/v cannot overflow, just do one division.
+ if (Uint128{u.high, 0} < v) {
+ auto [q, r] = divlu(u.high, u.low, v.low);
+ return {Uint128{q, 0}, Uint128{r, 0}};
+ }
+
+ // If u/v would overflow: Break u up into two halves.
+
+ // First quotient digit and first remainder, < v.
+ auto [q1, r1] = divlu(0, u.high, v.low);
+
+ // Second quotient digit.
+ auto [q0, r0] = divlu(r1, u.low, v.low);
+
+ // Return quotient and remainder.
+ return {Uint128{q0, q1}, Uint128{r0, 0}};
+ }
+
+ // Here v >= 2**64.
+
+ // 0 <= n <= 63
+ auto n = mozilla::CountLeadingZeroes64(v.high);
+
+ // Normalize the divisor so its MSB is 1.
+ auto v1 = (v << n).high;
+
+ // To ensure no overflow.
+ auto u1 = u >> 1;
+
+ // Get quotient from divide unsigned instruction.
+ auto [q1, r1] = divlu(u1.high, u1.low, v1);
+
+ // Undo normalization and division of u by 2.
+ auto q0 = (Uint128{q1, 0} << n) >> 63;
+
+ // Make q0 correct or too small by 1.
+ if (q0 != Uint128{0}) {
+ q0 -= Uint128{1};
+ }
+
+ // Now q0 is correct.
+ auto r0 = u - q0 * v;
+ if (r0 >= v) {
+ q0 += Uint128{1};
+ r0 -= v;
+ }
+
+ // Return quotient and remainder.
+ return {q0, r0};
+ }
+
+ /**
+ * Based on "Divide long unsigned, using fullword division instructions" from
+ * Hacker's Delight, 2nd edition, figure 9-3.
+ */
+ static constexpr std::pair<uint64_t, uint64_t> divlu(uint64_t u1, uint64_t u0,
+ uint64_t v) {
+ // Number base (32 bits).
+ constexpr uint64_t base = 4294967296;
+
+ // If overflow, set the remainder to an impossible value and return the
+ // largest possible quotient.
+ if (u1 >= v) {
+ return {UINT64_MAX, UINT64_MAX};
+ }
+
+ // Shift amount for normalization. (0 <= s <= 63)
+ int64_t s = mozilla::CountLeadingZeroes64(v);
+
+ // Normalize the divisor.
+ v = v << s;
+
+ // Normalized divisor digits.
+ //
+ // Break divisor up into two 32-bit digits.
+ uint64_t vn1 = v >> 32;
+ uint64_t vn0 = uint32_t(v);
+
+ // Dividend digit pairs.
+ //
+ // Shift dividend left.
+ uint64_t un32 = (u1 << s) | ((u0 >> ((64 - s) & 63)) & (-s >> 63));
+ uint64_t un10 = u0 << s;
+
+ // Normalized dividend least significant digits.
+ //
+ // Break right half of dividend into two digits.
+ uint64_t un1 = un10 >> 32;
+ uint64_t un0 = uint32_t(un10);
+
+ // Compute the first quotient digit and its remainder.
+ uint64_t q1 = un32 / vn1;
+ uint64_t rhat = un32 - q1 * vn1;
+ while (q1 >= base || q1 * vn0 > base * rhat + un1) {
+ q1 -= 1;
+ rhat += vn1;
+ if (rhat >= base) {
+ break;
+ }
+ }
+
+ // Multiply and subtract.
+ uint64_t un21 = un32 * base + un1 - q1 * v;
+
+ // Compute the second quotient digit and its remainder.
+ uint64_t q0 = un21 / vn1;
+ rhat = un21 - q0 * vn1;
+ while (q0 >= base || q0 * vn0 > base * rhat + un0) {
+ q0 -= 1;
+ rhat += vn1;
+ if (rhat >= base) {
+ break;
+ }
+ }
+
+ // Return the quotient and remainder.
+ uint64_t q = q1 * base + q0;
+ uint64_t r = (un21 * base + un0 - q0 * v) >> s;
+ return {q, r};
+ }
+
+ static double toDouble(const Uint128& x, bool negative);
+
+ public:
+ constexpr Uint128() = default;
+ constexpr Uint128(const Uint128&) = default;
+
+ explicit constexpr Uint128(uint64_t value)
+ : Uint128(uint64_t(value), uint64_t(0)) {}
+
+ constexpr bool operator==(const Uint128& other) const {
+ return low == other.low && high == other.high;
+ }
+
+ constexpr bool operator<(const Uint128& other) const {
+ if (high == other.high) {
+ return low < other.low;
+ }
+ return high < other.high;
+ }
+
+ // Other operators are implemented in terms of operator== and operator<.
+ constexpr bool operator!=(const Uint128& other) const {
+ return !(*this == other);
+ }
+ constexpr bool operator>(const Uint128& other) const { return other < *this; }
+ constexpr bool operator<=(const Uint128& other) const {
+ return !(other < *this);
+ }
+ constexpr bool operator>=(const Uint128& other) const {
+ return !(*this < other);
+ }
+
+ explicit constexpr operator bool() const { return !(*this == Uint128{}); }
+
+ explicit constexpr operator int8_t() const { return int8_t(low); }
+ explicit constexpr operator int16_t() const { return int16_t(low); }
+ explicit constexpr operator int32_t() const { return int32_t(low); }
+ explicit constexpr operator int64_t() const { return int64_t(low); }
+
+ explicit constexpr operator uint8_t() const { return uint8_t(low); }
+ explicit constexpr operator uint16_t() const { return uint16_t(low); }
+ explicit constexpr operator uint32_t() const { return uint32_t(low); }
+ explicit constexpr operator uint64_t() const { return uint64_t(low); }
+
+ explicit constexpr operator Int128() const;
+
+ explicit operator double() const { return toDouble(*this, false); }
+
+ constexpr Uint128 operator+(const Uint128& other) const {
+ // "§2-16 Double-Length Add/Subtract" from Hacker's Delight, 2nd edition.
+ Uint128 result;
+ result.low = low + other.low;
+ result.high = high + other.high + static_cast<uint64_t>(result.low < low);
+ return result;
+ }
+
+ constexpr Uint128 operator-(const Uint128& other) const {
+ // "§2-16 Double-Length Add/Subtract" from Hacker's Delight, 2nd edition.
+ Uint128 result;
+ result.low = low - other.low;
+ result.high = high - other.high - static_cast<uint64_t>(low < other.low);
+ return result;
+ }
+
+ constexpr Uint128 operator*(const Uint128& other) const {
+ uint64_t w01 = low * other.high;
+ uint64_t w10 = high * other.low;
+ uint64_t w00 = mulhu(low, other.low);
+
+ uint64_t w1 = w00 + w10 + w01;
+ uint64_t w0 = low * other.low;
+
+ return Uint128{w0, w1};
+ }
+
+ /**
+ * Return the quotient and remainder of the division.
+ */
+ constexpr std::pair<Uint128, Uint128> divrem(const Uint128& divisor) const {
+ return udivdi(*this, divisor);
+ }
+
+ constexpr Uint128 operator/(const Uint128& other) const {
+ auto [quot, rem] = divrem(other);
+ return quot;
+ }
+
+ constexpr Uint128 operator%(const Uint128& other) const {
+ auto [quot, rem] = divrem(other);
+ return rem;
+ }
+
+ constexpr Uint128 operator<<(int shift) const {
+ MOZ_ASSERT(0 <= shift && shift <= 127, "undefined shift amount");
+
+ // Ensure the shift amount is in range.
+ shift &= 127;
+
+ // "§2-17 Double-Length Shifts" from Hacker's Delight, 2nd edition.
+ if (shift >= 64) {
+ uint64_t y0 = 0;
+ uint64_t y1 = low << (shift - 64);
+ return Uint128{y0, y1};
+ }
+ uint64_t y0 = low << shift;
+ uint64_t y1 = (high << shift) | (low >> (64 - shift));
+ return Uint128{y0, y1};
+ }
+
+ constexpr Uint128 operator>>(int shift) const {
+ MOZ_ASSERT(0 <= shift && shift <= 127, "undefined shift amount");
+
+ // Ensure the shift amount is in range.
+ shift &= 127;
+
+ // "§2-17 Double-Length Shifts" from Hacker's Delight, 2nd edition.
+ if (shift >= 64) {
+ uint64_t y0 = high >> (shift - 64);
+ uint64_t y1 = 0;
+ return Uint128{y0, y1};
+ }
+ uint64_t y0 = low >> shift | high << (64 - shift);
+ uint64_t y1 = high >> shift;
+ return Uint128{y0, y1};
+ }
+
+ constexpr Uint128 operator&(const Uint128& other) const {
+ return Uint128{low & other.low, high & other.high};
+ }
+
+ constexpr Uint128 operator|(const Uint128& other) const {
+ return Uint128{low | other.low, high | other.high};
+ }
+
+ constexpr Uint128 operator^(const Uint128& other) const {
+ return Uint128{low ^ other.low, high ^ other.high};
+ }
+
+ constexpr Uint128 operator~() const { return Uint128{~low, ~high}; }
+
+ constexpr Uint128 operator-() const { return Uint128{} - *this; }
+
+ constexpr Uint128 operator+() const { return *this; }
+
+ constexpr Uint128& operator++() {
+ *this = *this + Uint128{1, 0};
+ return *this;
+ }
+
+ constexpr Uint128 operator++(int) {
+ auto result = *this;
+ ++*this;
+ return result;
+ }
+
+ constexpr Uint128& operator--() {
+ *this = *this - Uint128{1, 0};
+ return *this;
+ }
+
+ constexpr Uint128 operator--(int) {
+ auto result = *this;
+ --*this;
+ return result;
+ }
+
+ constexpr Uint128 operator+=(const Uint128& other) {
+ *this = *this + other;
+ return *this;
+ }
+
+ constexpr Uint128 operator-=(const Uint128& other) {
+ *this = *this - other;
+ return *this;
+ }
+
+ constexpr Uint128 operator*=(const Uint128& other) {
+ *this = *this * other;
+ return *this;
+ }
+
+ constexpr Uint128 operator/=(const Uint128& other) {
+ *this = *this / other;
+ return *this;
+ }
+
+ constexpr Uint128 operator%=(const Uint128& other) {
+ *this = *this % other;
+ return *this;
+ }
+
+ constexpr Uint128 operator&=(const Uint128& other) {
+ *this = *this & other;
+ return *this;
+ }
+
+ constexpr Uint128 operator|=(const Uint128& other) {
+ *this = *this | other;
+ return *this;
+ }
+
+ constexpr Uint128 operator^=(const Uint128& other) {
+ *this = *this ^ other;
+ return *this;
+ }
+
+ constexpr Uint128 operator<<=(int shift) {
+ *this = *this << shift;
+ return *this;
+ }
+
+ constexpr Uint128 operator>>=(int shift) {
+ *this = *this >> shift;
+ return *this;
+ }
+};
+
+/**
+ * Signed 128-bit integer, implemented as a pair of unsigned 64-bit integers.
+ *
+ * Supports all basic arithmetic operators.
+ */
+class alignas(16) Int128 final {
+#if MOZ_LITTLE_ENDIAN()
+ uint64_t low = 0;
+ uint64_t high = 0;
+#else
+ uint64_t high = 0;
+ uint64_t low = 0;
+#endif
+
+ friend class Uint128;
+
+ constexpr Int128(uint64_t low, uint64_t high) : low(low), high(high) {}
+
+ /**
+ * Based on "Signed doubleword division from unsigned doubleword division"
+ * from Hacker's Delight, 2nd edition, figure 9-6.
+ */
+ static constexpr std::pair<Int128, Int128> divdi(const Int128& u,
+ const Int128& v) {
+ auto [q, r] = Uint128::udivdi(u.abs(), v.abs());
+
+ // If u and v have different signs, negate q.
+ // If is negative, negate r.
+ auto t = static_cast<Uint128>((u ^ v) >> 127);
+ auto s = static_cast<Uint128>(u >> 127);
+ return {static_cast<Int128>((q ^ t) - t), static_cast<Int128>((r ^ s) - s)};
+ }
+
+ public:
+ constexpr Int128() = default;
+ constexpr Int128(const Int128&) = default;
+
+ explicit constexpr Int128(int64_t value)
+ : Int128(uint64_t(value), uint64_t(value >> 63)) {}
+
+ /**
+ * Return the quotient and remainder of the division.
+ */
+ constexpr std::pair<Int128, Int128> divrem(const Int128& divisor) const {
+ return divdi(*this, divisor);
+ }
+
+ /**
+ * Return the absolute value of this integer.
+ */
+ constexpr Uint128 abs() const {
+ if (*this >= Int128{}) {
+ return Uint128{low, high};
+ }
+ auto neg = -*this;
+ return Uint128{neg.low, neg.high};
+ }
+
+ constexpr bool operator==(const Int128& other) const {
+ return low == other.low && high == other.high;
+ }
+
+ constexpr bool operator<(const Int128& other) const {
+ if (high == other.high) {
+ return low < other.low;
+ }
+ return int64_t(high) < int64_t(other.high);
+ }
+
+ // Other operators are implemented in terms of operator== and operator<.
+ constexpr bool operator!=(const Int128& other) const {
+ return !(*this == other);
+ }
+ constexpr bool operator>(const Int128& other) const { return other < *this; }
+ constexpr bool operator<=(const Int128& other) const {
+ return !(other < *this);
+ }
+ constexpr bool operator>=(const Int128& other) const {
+ return !(*this < other);
+ }
+
+ explicit constexpr operator bool() const { return !(*this == Int128{}); }
+
+ explicit constexpr operator int8_t() const { return int8_t(low); }
+ explicit constexpr operator int16_t() const { return int16_t(low); }
+ explicit constexpr operator int32_t() const { return int32_t(low); }
+ explicit constexpr operator int64_t() const { return int64_t(low); }
+
+ explicit constexpr operator uint8_t() const { return uint8_t(low); }
+ explicit constexpr operator uint16_t() const { return uint16_t(low); }
+ explicit constexpr operator uint32_t() const { return uint32_t(low); }
+ explicit constexpr operator uint64_t() const { return uint64_t(low); }
+
+ explicit constexpr operator Uint128() const { return Uint128{low, high}; }
+
+ explicit operator double() const {
+ return Uint128::toDouble(abs(), *this < Int128{0});
+ }
+
+ constexpr Int128 operator+(const Int128& other) const {
+ return Int128{Uint128{*this} + Uint128{other}};
+ }
+
+ constexpr Int128 operator-(const Int128& other) const {
+ return Int128{Uint128{*this} - Uint128{other}};
+ }
+
+ constexpr Int128 operator*(const Int128& other) const {
+ return Int128{Uint128{*this} * Uint128{other}};
+ }
+
+ constexpr Int128 operator/(const Int128& other) const {
+ auto [quot, rem] = divrem(other);
+ return quot;
+ }
+
+ constexpr Int128 operator%(const Int128& other) const {
+ auto [quot, rem] = divrem(other);
+ return rem;
+ }
+
+ constexpr Int128 operator<<(int shift) const {
+ return Int128{Uint128{*this} << shift};
+ }
+
+ constexpr Int128 operator>>(int shift) const {
+ MOZ_ASSERT(0 <= shift && shift <= 127, "undefined shift amount");
+
+ // Ensure the shift amount is in range.
+ shift &= 127;
+
+ // "§2-17 Double-Length Shifts" from Hacker's Delight, 2nd edition.
+ if (shift >= 64) {
+ uint64_t y0 = uint64_t(int64_t(high) >> (shift - 64));
+ uint64_t y1 = uint64_t(int64_t(high) >> 63);
+ return Int128{y0, y1};
+ }
+ uint64_t y0 = low >> shift | high << (64 - shift);
+ uint64_t y1 = uint64_t(int64_t(high) >> shift);
+ return Int128{y0, y1};
+ }
+
+ constexpr Int128 operator&(const Int128& other) const {
+ return Int128{low & other.low, high & other.high};
+ }
+
+ constexpr Int128 operator|(const Int128& other) const {
+ return Int128{low | other.low, high | other.high};
+ }
+
+ constexpr Int128 operator^(const Int128& other) const {
+ return Int128{low ^ other.low, high ^ other.high};
+ }
+
+ constexpr Int128 operator~() const { return Int128{~low, ~high}; }
+
+ constexpr Int128 operator-() const { return Int128{} - *this; }
+
+ constexpr Int128 operator+() const { return *this; }
+
+ constexpr Int128& operator++() {
+ *this = *this + Int128{1, 0};
+ return *this;
+ }
+
+ constexpr Int128 operator++(int) {
+ auto result = *this;
+ ++*this;
+ return result;
+ }
+
+ constexpr Int128& operator--() {
+ *this = *this - Int128{1, 0};
+ return *this;
+ }
+
+ constexpr Int128 operator--(int) {
+ auto result = *this;
+ --*this;
+ return result;
+ }
+
+ constexpr Int128 operator+=(const Int128& other) {
+ *this = *this + other;
+ return *this;
+ }
+
+ constexpr Int128 operator-=(const Int128& other) {
+ *this = *this - other;
+ return *this;
+ }
+
+ constexpr Int128 operator*=(const Int128& other) {
+ *this = *this * other;
+ return *this;
+ }
+
+ constexpr Int128 operator/=(const Int128& other) {
+ *this = *this / other;
+ return *this;
+ }
+
+ constexpr Int128 operator%=(const Int128& other) {
+ *this = *this % other;
+ return *this;
+ }
+
+ constexpr Int128 operator&=(const Int128& other) {
+ *this = *this & other;
+ return *this;
+ }
+
+ constexpr Int128 operator|=(const Int128& other) {
+ *this = *this | other;
+ return *this;
+ }
+
+ constexpr Int128 operator^=(const Int128& other) {
+ *this = *this ^ other;
+ return *this;
+ }
+
+ constexpr Int128 operator<<=(int shift) {
+ *this = *this << shift;
+ return *this;
+ }
+
+ constexpr Int128 operator>>=(int shift) {
+ *this = *this >> shift;
+ return *this;
+ }
+};
+
+constexpr Uint128::operator Int128() const { return Int128{low, high}; }
+
+} /* namespace js::temporal */
+
+template <>
+class std::numeric_limits<js::temporal::Int128> {
+ public:
+ static constexpr bool is_specialized = true;
+ static constexpr bool is_signed = true;
+ static constexpr bool is_integer = true;
+ static constexpr bool is_exact = true;
+ static constexpr bool has_infinity = false;
+ static constexpr bool has_quiet_NaN = false;
+ static constexpr bool has_signaling_NaN = false;
+ static constexpr std::float_denorm_style has_denorm = std::denorm_absent;
+ static constexpr bool has_denorm_loss = false;
+ static constexpr std::float_round_style round_style = std::round_toward_zero;
+ static constexpr bool is_iec559 = false;
+ static constexpr bool is_bounded = true;
+ static constexpr bool is_modulo = true;
+ static constexpr int digits = CHAR_BIT * sizeof(js::temporal::Int128) - 1;
+ static constexpr int digits10 = int(digits * /* std::log10(2) */ 0.30102999);
+ static constexpr int max_digits10 = 0;
+ static constexpr int radix = 2;
+ static constexpr int min_exponent = 0;
+ static constexpr int min_exponent10 = 0;
+ static constexpr int max_exponent = 0;
+ static constexpr int max_exponent10 = 0;
+ static constexpr bool traps = true;
+ static constexpr bool tinyness_before = false;
+
+ static constexpr auto min() noexcept {
+ return js::temporal::Int128{1} << 127;
+ }
+ static constexpr auto lowest() noexcept { return min(); }
+ static constexpr auto max() noexcept { return ~min(); }
+ static constexpr auto epsilon() noexcept { return js::temporal::Int128{}; }
+ static constexpr auto round_error() noexcept {
+ return js::temporal::Int128{};
+ }
+ static constexpr auto infinity() noexcept { return js::temporal::Int128{}; }
+ static constexpr auto quiet_NaN() noexcept { return js::temporal::Int128{}; }
+ static constexpr auto signaling_NaN() noexcept {
+ return js::temporal::Int128{};
+ }
+ static constexpr auto denorm_min() noexcept { return js::temporal::Int128{}; }
+};
+
+template <>
+class std::numeric_limits<js::temporal::Uint128> {
+ public:
+ static constexpr bool is_specialized = true;
+ static constexpr bool is_signed = false;
+ static constexpr bool is_integer = true;
+ static constexpr bool is_exact = true;
+ static constexpr bool has_infinity = false;
+ static constexpr bool has_quiet_NaN = false;
+ static constexpr bool has_signaling_NaN = false;
+ static constexpr std::float_denorm_style has_denorm = std::denorm_absent;
+ static constexpr bool has_denorm_loss = false;
+ static constexpr std::float_round_style round_style = std::round_toward_zero;
+ static constexpr bool is_iec559 = false;
+ static constexpr bool is_bounded = true;
+ static constexpr bool is_modulo = true;
+ static constexpr int digits = CHAR_BIT * sizeof(js::temporal::Uint128);
+ static constexpr int digits10 = int(digits * /* std::log10(2) */ 0.30102999);
+ static constexpr int max_digits10 = 0;
+ static constexpr int radix = 2;
+ static constexpr int min_exponent = 0;
+ static constexpr int min_exponent10 = 0;
+ static constexpr int max_exponent = 0;
+ static constexpr int max_exponent10 = 0;
+ static constexpr bool traps = true;
+ static constexpr bool tinyness_before = false;
+
+ static constexpr auto min() noexcept { return js::temporal::Uint128{}; }
+ static constexpr auto lowest() noexcept { return min(); }
+ static constexpr auto max() noexcept { return ~js::temporal::Uint128{}; }
+ static constexpr auto epsilon() noexcept { return js::temporal::Uint128{}; }
+ static constexpr auto round_error() noexcept {
+ return js::temporal::Uint128{};
+ }
+ static constexpr auto infinity() noexcept { return js::temporal::Uint128{}; }
+ static constexpr auto quiet_NaN() noexcept { return js::temporal::Uint128{}; }
+ static constexpr auto signaling_NaN() noexcept {
+ return js::temporal::Uint128{};
+ }
+ static constexpr auto denorm_min() noexcept {
+ return js::temporal::Uint128{};
+ }
+};
+
+#endif /* builtin_temporal_Int128_h */
diff --git a/js/src/builtin/temporal/Int96.cpp b/js/src/builtin/temporal/Int96.cpp
index 73ea5ce90e..984271566a 100644
--- a/js/src/builtin/temporal/Int96.cpp
+++ b/js/src/builtin/temporal/Int96.cpp
@@ -39,12 +39,12 @@ mozilla::Maybe<Int96> Int96::fromInteger(double value) {
// Inlined version of |BigInt::createFromDouble()| for DigitBits=32. See the
// comments in |BigInt::createFromDouble()| for how this code works.
- constexpr size_t DigitBits = 32;
+ constexpr int DigitBits = 32;
// The number can't have more than three digits when it's below |maximum|.
Int96::Digits digits = {};
- int exponent = mozilla::ExponentComponent(value);
+ int exponent = int(mozilla::ExponentComponent(value));
MOZ_ASSERT(0 <= exponent && exponent <= 95,
"exponent is lower than exponent of 0x1p+96");
@@ -62,7 +62,7 @@ mozilla::Maybe<Int96> Int96::fromInteger(double value) {
int msdTopBit = exponent % DigitBits;
// First, build the MSD by shifting the mantissa appropriately.
- int remainingMantissaBits = Double::kSignificandWidth - msdTopBit;
+ int remainingMantissaBits = int(Double::kSignificandWidth - msdTopBit);
digits[--length] = mantissa >> remainingMantissaBits;
// Fill in digits containing mantissa contributions.
diff --git a/js/src/builtin/temporal/Int96.h b/js/src/builtin/temporal/Int96.h
index dfb0a5c231..36ed359eca 100644
--- a/js/src/builtin/temporal/Int96.h
+++ b/js/src/builtin/temporal/Int96.h
@@ -133,7 +133,7 @@ class Int96 final {
remainder = n % divisor;
}
- int64_t result = (TwoDigit(quotient[1]) << 32) | quotient[0];
+ int64_t result = int64_t((TwoDigit(quotient[1]) << 32) | quotient[0]);
if (negative) {
result *= -1;
if (remainder != 0) {
diff --git a/js/src/builtin/temporal/PlainDate.cpp b/js/src/builtin/temporal/PlainDate.cpp
index a4ad0e418f..a15d95cdc1 100644
--- a/js/src/builtin/temporal/PlainDate.cpp
+++ b/js/src/builtin/temporal/PlainDate.cpp
@@ -7,6 +7,7 @@
#include "builtin/temporal/PlainDate.h"
#include "mozilla/Assertions.h"
+#include "mozilla/Casting.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Maybe.h"
@@ -99,20 +100,15 @@ static bool IsValidISODate(T year, T month, T day) {
// Step 3.
int32_t daysInMonth = js::temporal::ISODaysInMonth(year, int32_t(month));
- // Step 4.
- if (day < 1 || day > daysInMonth) {
- return false;
- }
-
- // Step 5.
- return true;
+ // Steps 4-5.
+ return 1 <= day && day <= daysInMonth;
}
/**
* IsValidISODate ( year, month, day )
*/
bool js::temporal::IsValidISODate(const PlainDate& date) {
- auto& [year, month, day] = date;
+ const auto& [year, month, day] = date;
return ::IsValidISODate(year, month, day);
}
@@ -170,20 +166,15 @@ static bool ThrowIfInvalidISODate(JSContext* cx, T year, T month, T day) {
// Step 3.
int32_t daysInMonth = js::temporal::ISODaysInMonth(year, int32_t(month));
- // Step 4.
- if (!ThrowIfInvalidDateValue(cx, "day", 1, daysInMonth, day)) {
- return false;
- }
-
- // Step 5.
- return true;
+ // Steps 4-5.
+ return ThrowIfInvalidDateValue(cx, "day", 1, daysInMonth, day);
}
/**
* IsValidISODate ( year, month, day )
*/
bool js::temporal::ThrowIfInvalidISODate(JSContext* cx, const PlainDate& date) {
- auto& [year, month, day] = date;
+ const auto& [year, month, day] = date;
return ::ThrowIfInvalidISODate(cx, year, month, day);
}
@@ -201,7 +192,7 @@ bool js::temporal::ThrowIfInvalidISODate(JSContext* cx, double year,
* With |overflow = "constrain"|.
*/
static PlainDate ConstrainISODate(const PlainDate& date) {
- auto& [year, month, day] = date;
+ const auto& [year, month, day] = date;
// Step 1.a.
int32_t m = std::clamp(month, 1, 12);
@@ -316,13 +307,16 @@ static PlainDateObject* CreateTemporalDate(JSContext* cx, const CallArgs& args,
}
// Step 5.
- object->setFixedSlot(PlainDateObject::ISO_YEAR_SLOT, Int32Value(isoYear));
+ object->setFixedSlot(PlainDateObject::ISO_YEAR_SLOT,
+ Int32Value(int32_t(isoYear)));
// Step 6.
- object->setFixedSlot(PlainDateObject::ISO_MONTH_SLOT, Int32Value(isoMonth));
+ object->setFixedSlot(PlainDateObject::ISO_MONTH_SLOT,
+ Int32Value(int32_t(isoMonth)));
// Step 7.
- object->setFixedSlot(PlainDateObject::ISO_DAY_SLOT, Int32Value(isoDay));
+ object->setFixedSlot(PlainDateObject::ISO_DAY_SLOT,
+ Int32Value(int32_t(isoDay)));
// Step 8.
object->setFixedSlot(PlainDateObject::CALENDAR_SLOT, calendar.toValue());
@@ -336,7 +330,7 @@ static PlainDateObject* CreateTemporalDate(JSContext* cx, const CallArgs& args,
*/
PlainDateObject* js::temporal::CreateTemporalDate(
JSContext* cx, const PlainDate& date, Handle<CalendarValue> calendar) {
- auto& [isoYear, isoMonth, isoDay] = date;
+ const auto& [isoYear, isoMonth, isoDay] = date;
// Step 1.
if (!ThrowIfInvalidISODate(cx, date)) {
@@ -602,30 +596,28 @@ bool js::temporal::ToTemporalDate(JSContext* cx, Handle<Value> item,
/**
* Mathematical Operations, "modulo" notation.
*/
-static int32_t NonNegativeModulo(double x, int32_t y) {
- MOZ_ASSERT(IsInteger(x));
+static int32_t NonNegativeModulo(int64_t x, int32_t y) {
MOZ_ASSERT(y > 0);
- double r = std::fmod(x, y);
-
- int32_t result;
- MOZ_ALWAYS_TRUE(mozilla::NumberEqualsInt32(r, &result));
-
+ int32_t result = mozilla::AssertedCast<int32_t>(x % y);
return (result < 0) ? (result + y) : result;
}
struct BalancedYearMonth final {
- double year = 0;
+ int64_t year = 0;
int32_t month = 0;
};
/**
* BalanceISOYearMonth ( year, month )
*/
-static BalancedYearMonth BalanceISOYearMonth(double year, double month) {
- // Step 1.
- MOZ_ASSERT(IsInteger(year));
- MOZ_ASSERT(IsInteger(month));
+static BalancedYearMonth BalanceISOYearMonth(int64_t year, int64_t month) {
+ MOZ_ASSERT(std::abs(year) < (int64_t(1) << 33),
+ "year is the addition of plain-date year with duration years");
+ MOZ_ASSERT(std::abs(month) < (int64_t(1) << 33),
+ "month is the addition of plain-date month with duration months");
+
+ // Step 1. (Not applicable in our implementation.)
// Note: If either abs(year) or abs(month) is greater than 2^53 (the double
// integral precision limit), the additions resp. subtractions below are
@@ -633,18 +625,17 @@ static BalancedYearMonth BalanceISOYearMonth(double year, double month) {
// function (AddISODate) will throw an error for large values anyway.
// Step 2.
- year = year + std::floor((month - 1) / 12);
- MOZ_ASSERT(IsInteger(year) || std::isinf(year));
+ int64_t balancedYear = year + temporal::FloorDiv(month - 1, 12);
// Step 3.
- int32_t mon = NonNegativeModulo(month - 1, 12) + 1;
- MOZ_ASSERT(1 <= mon && mon <= 12);
+ int32_t balancedMonth = NonNegativeModulo(month - 1, 12) + 1;
+ MOZ_ASSERT(1 <= balancedMonth && balancedMonth <= 12);
// Step 4.
- return {year, mon};
+ return {balancedYear, balancedMonth};
}
-static bool CanBalanceISOYear(double year) {
+static bool CanBalanceISOYear(int64_t year) {
// TODO: Export these values somewhere.
constexpr int32_t minYear = -271821;
constexpr int32_t maxYear = 275760;
@@ -654,7 +645,7 @@ static bool CanBalanceISOYear(double year) {
return minYear <= year && year <= maxYear;
}
-static bool CanBalanceISODay(double day) {
+static bool CanBalanceISODay(int64_t day) {
// The maximum number of seconds from the epoch is 8.64 * 10^12.
constexpr int64_t maxInstantSeconds = 8'640'000'000'000;
@@ -684,10 +675,9 @@ PlainDate js::temporal::BalanceISODateNew(int32_t year, int32_t month,
MOZ_ASSERT(1 <= month && month <= 12);
// Steps 1-3.
- int64_t ms = MakeDate(year, month, day);
+ double ms = double(MakeDate(year, month, day));
- // FIXME: spec issue - |ms| can be non-finite
- // https://github.com/tc39/proposal-temporal/issues/2315
+ // TODO: Add ISODateToEpochDays & friends which handle larger inputs.
// TODO: This approach isn't efficient, because MonthFromTime and DayFromTime
// both recompute YearFromTime.
@@ -700,15 +690,19 @@ PlainDate js::temporal::BalanceISODateNew(int32_t year, int32_t month,
/**
* BalanceISODate ( year, month, day )
*/
-bool js::temporal::BalanceISODate(JSContext* cx, int32_t year, int32_t month,
- int64_t day, PlainDate* result) {
+bool js::temporal::BalanceISODate(JSContext* cx, const PlainDate& date,
+ int64_t days, PlainDate* result) {
+ MOZ_ASSERT(IsValidISODate(date));
+ MOZ_ASSERT(ISODateTimeWithinLimits(date));
+
+ int64_t day = int64_t(date.day) + days;
if (!CanBalanceISODay(day)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
return false;
}
- *result = BalanceISODate(year, month, int32_t(day));
+ *result = BalanceISODate(date.year, date.month, int32_t(day));
return true;
}
@@ -725,9 +719,8 @@ PlainDate js::temporal::BalanceISODate(int32_t year, int32_t month,
MOZ_ASSERT(1 <= month && month <= 12);
MOZ_ASSERT(CanBalanceISODay(day));
- // TODO: BalanceISODate now works using MakeDate
+ // TODO: BalanceISODate now works using ISODateToEpochDays & friends.
// TODO: Can't use JS::MakeDate, because it expects valid month/day values.
- // https://github.com/tc39/proposal-temporal/issues/2315
// Step 1. (Not applicable in our implementation.)
@@ -811,7 +804,7 @@ PlainDate js::temporal::BalanceISODate(int32_t year, int32_t month,
* AddISODate ( year, month, day, years, months, weeks, days, overflow )
*/
bool js::temporal::AddISODate(JSContext* cx, const PlainDate& date,
- const Duration& duration,
+ const DateDuration& duration,
TemporalOverflow overflow, PlainDate* result) {
MOZ_ASSERT(IsValidISODate(date));
MOZ_ASSERT(ISODateTimeWithinLimits(date));
@@ -822,18 +815,11 @@ bool js::temporal::AddISODate(JSContext* cx, const PlainDate& date,
// BalanceISODate.
MOZ_ASSERT(IsValidDuration(duration));
- // Step 1.
- MOZ_ASSERT(IsInteger(duration.years));
- MOZ_ASSERT(IsInteger(duration.months));
- MOZ_ASSERT(IsInteger(duration.weeks));
- MOZ_ASSERT(IsInteger(duration.days));
-
- // Step 2. (Not applicable in our implementation.)
+ // Steps 1-2. (Not applicable in our implementation.)
// Step 3.
auto yearMonth = BalanceISOYearMonth(date.year + duration.years,
date.month + duration.months);
- MOZ_ASSERT(IsInteger(yearMonth.year) || std::isinf(yearMonth.year));
MOZ_ASSERT(1 <= yearMonth.month && yearMonth.month <= 12);
// FIXME: spec issue?
@@ -881,7 +867,7 @@ bool js::temporal::AddISODate(JSContext* cx, const PlainDate& date,
// about imprecise number arithmetic here.
// Steps 5-6.
- double d = regulated.day + (duration.days + duration.weeks * 7);
+ int64_t d = regulated.day + (duration.days + duration.weeks * 7);
// Just as with |yearMonth.year|, also directly throw an error if the |days|
// value is too large.
@@ -952,65 +938,51 @@ static bool HasYearsMonthsOrWeeks(const Duration& duration) {
return duration.years != 0 || duration.months != 0 || duration.weeks != 0;
}
-static bool AddDate(JSContext* cx, const PlainDate& date,
- const Duration& duration, Handle<JSObject*> maybeOptions,
- PlainDate* result) {
- MOZ_ASSERT(!HasYearsMonthsOrWeeks(duration));
+static bool HasYearsMonthsOrWeeks(const DateDuration& duration) {
+ return duration.years != 0 || duration.months != 0 || duration.weeks != 0;
+}
- // Steps 1-3. (Not applicable)
+/**
+ * AddDate ( calendarRec, plainDate, duration [ , options ] )
+ */
+static bool AddDate(JSContext* cx, const PlainDate& date,
+ const NormalizedDuration& duration,
+ TemporalOverflow overflow, PlainDate* result) {
+ MOZ_ASSERT(!HasYearsMonthsOrWeeks(duration.date));
+ MOZ_ASSERT(IsValidDuration(duration));
- // Step 4.
- auto overflow = TemporalOverflow::Constrain;
- if (maybeOptions) {
- if (!ToTemporalOverflow(cx, maybeOptions, &overflow)) {
- return false;
- }
- }
+ // Steps 1-4. (Not applicable)
- // Step 5.
- TimeDuration daysDuration;
- if (!BalanceTimeDuration(cx, duration, TemporalUnit::Day, &daysDuration)) {
- return false;
- }
+ // Step 5. (Not applicable)
+ const auto& timeDuration = duration.time;
// Step 6.
- return AddISODate(cx, date, {0, 0, 0, daysDuration.days}, overflow, result);
+ int64_t balancedDays =
+ BalanceTimeDuration(timeDuration, TemporalUnit::Day).days;
+ int64_t days = duration.date.days + balancedDays;
+
+ // Step 7.
+ return AddISODate(cx, date, {0, 0, 0, days}, overflow, result);
}
static bool AddDate(JSContext* cx, Handle<Wrapped<PlainDateObject*>> date,
- const Duration& duration, Handle<JSObject*> maybeOptions,
- PlainDate* result) {
+ const NormalizedDuration& duration,
+ TemporalOverflow overflow, PlainDate* result) {
auto* unwrappedDate = date.unwrap(cx);
if (!unwrappedDate) {
return false;
}
- return ::AddDate(cx, ToPlainDate(unwrappedDate), duration, maybeOptions,
- result);
-}
-
-static PlainDateObject* AddDate(JSContext* cx, Handle<CalendarRecord> calendar,
- Handle<Wrapped<PlainDateObject*>> date,
- const Duration& duration,
- Handle<JSObject*> maybeOptions) {
- // Steps 1-3. (Not applicable)
-
- // Steps 4-6.
- PlainDate resultDate;
- if (!::AddDate(cx, date, duration, maybeOptions, &resultDate)) {
- return nullptr;
- }
-
- // Step 7.
- return CreateTemporalDate(cx, resultDate, calendar.receiver());
+ return ::AddDate(cx, ToPlainDate(unwrappedDate), duration, overflow, result);
}
/**
* AddDate ( calendarRec, plainDate, duration [ , options ] )
*/
-Wrapped<PlainDateObject*> js::temporal::AddDate(
- JSContext* cx, Handle<CalendarRecord> calendar,
- Handle<Wrapped<PlainDateObject*>> date, const Duration& duration,
- Handle<JSObject*> options) {
+static Wrapped<PlainDateObject*> AddDate(JSContext* cx,
+ Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> date,
+ const Duration& duration,
+ Handle<JSObject*> options) {
// Step 1.
MOZ_ASSERT(
CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
@@ -1022,8 +994,23 @@ Wrapped<PlainDateObject*> js::temporal::AddDate(
return temporal::CalendarDateAdd(cx, calendar, date, duration, options);
}
- // Steps 4-7.
- return ::AddDate(cx, calendar, date, duration, options);
+ // Step 4.
+ auto overflow = TemporalOverflow::Constrain;
+ if (!ToTemporalOverflow(cx, options, &overflow)) {
+ return nullptr;
+ }
+
+ // Step 5.
+ auto normalized = CreateNormalizedDurationRecord(duration);
+
+ // Steps 6-7.
+ PlainDate resultDate;
+ if (!::AddDate(cx, date, normalized, overflow, &resultDate)) {
+ return nullptr;
+ }
+
+ // Step 8.
+ return CreateTemporalDate(cx, resultDate, calendar.receiver());
}
/**
@@ -1031,7 +1018,7 @@ Wrapped<PlainDateObject*> js::temporal::AddDate(
*/
Wrapped<PlainDateObject*> js::temporal::AddDate(
JSContext* cx, Handle<CalendarRecord> calendar,
- Handle<Wrapped<PlainDateObject*>> date, const Duration& duration) {
+ Handle<Wrapped<PlainDateObject*>> date, const DateDuration& duration) {
// Step 1.
MOZ_ASSERT(
CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
@@ -1043,8 +1030,20 @@ Wrapped<PlainDateObject*> js::temporal::AddDate(
return CalendarDateAdd(cx, calendar, date, duration);
}
- // Steps 4-7.
- return ::AddDate(cx, calendar, date, duration, nullptr);
+ // Step 4.
+ auto overflow = TemporalOverflow::Constrain;
+
+ // Step 5.
+ auto normalized = NormalizedDuration{duration};
+
+ // Steps 6-7.
+ PlainDate resultDate;
+ if (!::AddDate(cx, date, normalized, overflow, &resultDate)) {
+ return nullptr;
+ }
+
+ // Step 8.
+ return CreateTemporalDate(cx, resultDate, calendar.receiver());
}
/**
@@ -1052,33 +1051,15 @@ Wrapped<PlainDateObject*> js::temporal::AddDate(
*/
Wrapped<PlainDateObject*> js::temporal::AddDate(
JSContext* cx, Handle<CalendarRecord> calendar,
- Handle<Wrapped<PlainDateObject*>> date,
- Handle<Wrapped<DurationObject*>> durationObj) {
- auto* unwrappedDuration = durationObj.unwrap(cx);
- if (!unwrappedDuration) {
- return nullptr;
- }
- auto duration = ToDuration(unwrappedDuration);
-
- // Step 1.
- MOZ_ASSERT(
- CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
-
- // Step 2. (Not applicable in our implementation.)
-
- // Step 3.
- if (HasYearsMonthsOrWeeks(duration)) {
- return CalendarDateAdd(cx, calendar, date, durationObj);
- }
-
- // Steps 4-7.
- return ::AddDate(cx, calendar, date, duration, nullptr);
+ Handle<Wrapped<PlainDateObject*>> date, const DateDuration& duration,
+ Handle<JSObject*> options) {
+ return ::AddDate(cx, calendar, date, duration.toDuration(), options);
}
/**
* AddDate ( calendarRec, plainDate, duration [ , options ] )
*/
-Wrapped<PlainDateObject*> js::temporal::AddDate(
+static Wrapped<PlainDateObject*> AddDate(
JSContext* cx, Handle<CalendarRecord> calendar,
Handle<Wrapped<PlainDateObject*>> date,
Handle<Wrapped<DurationObject*>> durationObj, Handle<JSObject*> options) {
@@ -1099,15 +1080,30 @@ Wrapped<PlainDateObject*> js::temporal::AddDate(
return temporal::CalendarDateAdd(cx, calendar, date, durationObj, options);
}
- // Steps 4-7.
- return ::AddDate(cx, calendar, date, duration, options);
+ // Step 4.
+ auto overflow = TemporalOverflow::Constrain;
+ if (!ToTemporalOverflow(cx, options, &overflow)) {
+ return nullptr;
+ }
+
+ // Step 5.
+ auto normalized = CreateNormalizedDurationRecord(duration);
+
+ // Steps 6-7.
+ PlainDate resultDate;
+ if (!::AddDate(cx, date, normalized, overflow, &resultDate)) {
+ return nullptr;
+ }
+
+ // Step 8.
+ return CreateTemporalDate(cx, resultDate, calendar.receiver());
}
/**
* AddDate ( calendarRec, plainDate, duration [ , options ] )
*/
bool js::temporal::AddDate(JSContext* cx, Handle<CalendarRecord> calendar,
- const PlainDate& date, const Duration& duration,
+ const PlainDate& date, const DateDuration& duration,
Handle<JSObject*> options, PlainDate* result) {
// Step 1.
MOZ_ASSERT(
@@ -1121,8 +1117,17 @@ bool js::temporal::AddDate(JSContext* cx, Handle<CalendarRecord> calendar,
result);
}
- // Steps 4-7.
- return ::AddDate(cx, date, duration, options, result);
+ // Step 4.
+ auto overflow = TemporalOverflow::Constrain;
+ if (!ToTemporalOverflow(cx, options, &overflow)) {
+ return false;
+ }
+
+ // Step 5.
+ auto normalized = NormalizedDuration{duration};
+
+ // Steps 5-8.
+ return ::AddDate(cx, date, normalized, overflow, result);
}
/**
@@ -1130,7 +1135,7 @@ bool js::temporal::AddDate(JSContext* cx, Handle<CalendarRecord> calendar,
*/
bool js::temporal::AddDate(JSContext* cx, Handle<CalendarRecord> calendar,
Handle<Wrapped<PlainDateObject*>> date,
- const Duration& duration, PlainDate* result) {
+ const DateDuration& duration, PlainDate* result) {
// Step 1.
MOZ_ASSERT(
CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
@@ -1142,8 +1147,14 @@ bool js::temporal::AddDate(JSContext* cx, Handle<CalendarRecord> calendar,
return CalendarDateAdd(cx, calendar, date, duration, result);
}
- // Steps 4-7.
- return ::AddDate(cx, date, duration, nullptr, result);
+ // Step 4.
+ auto overflow = TemporalOverflow::Constrain;
+
+ // Step 5.
+ auto normalized = NormalizedDuration{duration};
+
+ // Steps 6-8.
+ return ::AddDate(cx, date, normalized, overflow, result);
}
/**
@@ -1153,8 +1164,9 @@ bool js::temporal::DifferenceDate(JSContext* cx,
Handle<CalendarRecord> calendar,
Handle<Wrapped<PlainDateObject*>> one,
Handle<Wrapped<PlainDateObject*>> two,
+ TemporalUnit largestUnit,
Handle<PlainObject*> options,
- Duration* result) {
+ DateDuration* result) {
auto* unwrappedOne = one.unwrap(cx);
if (!unwrappedOne) {
return false;
@@ -1172,8 +1184,7 @@ bool js::temporal::DifferenceDate(JSContext* cx,
// Step 3.
MOZ_ASSERT(options->staticPrototype() == nullptr);
- // Step 4.
- MOZ_ASSERT(options->containsPure(cx->names().largestUnit));
+ // Step 4. (Not applicable in our implementation.)
// Step 5.
if (oneDate == twoDate) {
@@ -1182,30 +1193,18 @@ bool js::temporal::DifferenceDate(JSContext* cx,
}
// Step 6.
- Rooted<JS::Value> largestUnit(cx);
- if (!GetProperty(cx, options, options, cx->names().largestUnit,
- &largestUnit)) {
- return false;
- }
-
- if (largestUnit.isString()) {
- bool isDay;
- if (!EqualStrings(cx, largestUnit.toString(), cx->names().day, &isDay)) {
- return false;
- }
-
- if (isDay) {
- // Step 6.a.
- int32_t days = DaysUntil(oneDate, twoDate);
+ if (largestUnit == TemporalUnit::Day) {
+ // Step 6.a.
+ int32_t days = DaysUntil(oneDate, twoDate);
- // Step 6.b.
- *result = {0, 0, 0, double(days)};
- return true;
- }
+ // Step 6.b.
+ *result = {0, 0, 0, days};
+ return true;
}
// Step 7.
- return CalendarDateUntil(cx, calendar, one, two, options, result);
+ return CalendarDateUntil(cx, calendar, one, two, largestUnit, options,
+ result);
}
/**
@@ -1215,7 +1214,8 @@ bool js::temporal::DifferenceDate(JSContext* cx,
Handle<CalendarRecord> calendar,
Handle<Wrapped<PlainDateObject*>> one,
Handle<Wrapped<PlainDateObject*>> two,
- TemporalUnit largestUnit, Duration* result) {
+ TemporalUnit largestUnit,
+ DateDuration* result) {
auto* unwrappedOne = one.unwrap(cx);
if (!unwrappedOne) {
return false;
@@ -1242,7 +1242,69 @@ bool js::temporal::DifferenceDate(JSContext* cx,
int32_t days = DaysUntil(oneDate, twoDate);
// Step 6.b.
- *result = {0, 0, 0, double(days)};
+ *result = {0, 0, 0, days};
+ return true;
+ }
+
+ // Step 7.
+ return CalendarDateUntil(cx, calendar, one, two, largestUnit, result);
+}
+
+/**
+ * DifferenceDate ( calendarRec, one, two, options )
+ */
+bool js::temporal::DifferenceDate(JSContext* cx,
+ Handle<CalendarRecord> calendar,
+ const PlainDate& one, const PlainDate& two,
+ TemporalUnit largestUnit,
+ Handle<PlainObject*> options,
+ DateDuration* result) {
+ // Steps 1-4. (Not applicable in our implementation.)
+
+ // Step 5.
+ if (one == two) {
+ *result = {};
+ return true;
+ }
+
+ // Step 6.
+ if (largestUnit == TemporalUnit::Day) {
+ // Step 6.a.
+ int32_t days = DaysUntil(one, two);
+
+ // Step 6.b.
+ *result = {0, 0, 0, days};
+ return true;
+ }
+
+ // Step 7.
+ return CalendarDateUntil(cx, calendar, one, two, largestUnit, options,
+ result);
+}
+
+/**
+ * DifferenceDate ( calendarRec, one, two, options )
+ */
+bool js::temporal::DifferenceDate(JSContext* cx,
+ Handle<CalendarRecord> calendar,
+ const PlainDate& one, const PlainDate& two,
+ TemporalUnit largestUnit,
+ DateDuration* result) {
+ // Steps 1-4. (Not applicable in our implementation.)
+
+ // Step 5.
+ if (one == two) {
+ *result = {};
+ return true;
+ }
+
+ // Step 6.
+ if (largestUnit == TemporalUnit::Day) {
+ // Step 6.a.
+ int32_t days = DaysUntil(one, two);
+
+ // Step 6.b.
+ *result = {0, 0, 0, days};
return true;
}
@@ -1280,8 +1342,8 @@ int32_t js::temporal::CompareISODate(const PlainDate& one,
static DateDuration CreateDateDurationRecord(int32_t years, int32_t months,
int32_t weeks, int32_t days) {
MOZ_ASSERT(IsValidDuration(
- {double(years), double(months), double(weeks), double(days)}));
- return {double(years), double(months), double(weeks), double(days)};
+ Duration{double(years), double(months), double(weeks), double(days)}));
+ return {years, months, weeks, days};
}
/**
@@ -1577,31 +1639,19 @@ static bool DifferenceTemporalPlainDate(JSContext* cx,
}
// Steps 8-9.
- Duration duration;
+ DateDuration difference;
if (resolvedOptions) {
- // Step 8.
- Rooted<Value> largestUnitValue(
- cx, StringValue(TemporalUnitToString(cx, settings.largestUnit)));
- if (!DefineDataProperty(cx, resolvedOptions, cx->names().largestUnit,
- largestUnitValue)) {
- return false;
- }
-
- // Step 9.
- Duration result;
- if (!DifferenceDate(cx, calendar, temporalDate, other, resolvedOptions,
- &result)) {
+ // Steps 8-9.
+ if (!DifferenceDate(cx, calendar, temporalDate, other, settings.largestUnit,
+ resolvedOptions, &difference)) {
return false;
}
- duration = result.date();
} else {
// Steps 8-9.
- Duration result;
if (!DifferenceDate(cx, calendar, temporalDate, other, settings.largestUnit,
- &result)) {
+ &difference)) {
return false;
}
- duration = result.date();
}
// Step 10.
@@ -1611,8 +1661,8 @@ static bool DifferenceTemporalPlainDate(JSContext* cx,
// Step 11.
if (!roundingGranularityIsNoop) {
// Steps 11.a-b.
- Duration roundResult;
- if (!temporal::RoundDuration(cx, duration.date(),
+ NormalizedDuration roundResult;
+ if (!temporal::RoundDuration(cx, {difference, {}},
settings.roundingIncrement,
settings.smallestUnit, settings.roundingMode,
temporalDate, calendar, &roundResult)) {
@@ -1622,19 +1672,21 @@ static bool DifferenceTemporalPlainDate(JSContext* cx,
// Step 11.c.
DateDuration balanceResult;
if (!temporal::BalanceDateDurationRelative(
- cx, roundResult.date(), settings.largestUnit, settings.smallestUnit,
+ cx, roundResult.date, settings.largestUnit, settings.smallestUnit,
temporalDate, calendar, &balanceResult)) {
return false;
}
- duration = balanceResult.toDuration();
+ difference = balanceResult;
}
// Step 12.
+ auto duration = difference.toDuration();
if (operation == TemporalDifference::Since) {
duration = duration.negate();
}
+ MOZ_ASSERT(IsValidDuration(duration));
- auto* obj = CreateTemporalDuration(cx, duration.date());
+ auto* obj = CreateTemporalDuration(cx, duration);
if (!obj) {
return false;
}
@@ -2235,14 +2287,14 @@ static bool PlainDate_toPlainDateTime(JSContext* cx, const CallArgs& args) {
// Default initialize the time component to all zero.
PlainDateTime dateTime = {ToPlainDate(temporalDate), {}};
- // Step 4. (Reordered)
+ // Step 3. (Inlined ToTemporalTimeOrMidnight)
if (args.hasDefined(0)) {
if (!ToTemporalTime(cx, args[0], &dateTime.time)) {
return false;
}
}
- // Steps 3 and 5.
+ // Step 4.
auto* obj = CreateTemporalDateTime(cx, dateTime, calendar);
if (!obj) {
return false;
@@ -2433,8 +2485,7 @@ static bool PlainDate_subtract(JSContext* cx, const CallArgs& args) {
}
// Step 7.
- auto result =
- temporal::AddDate(cx, calendar, temporalDate, negatedDuration, options);
+ auto result = ::AddDate(cx, calendar, temporalDate, negatedDuration, options);
if (!result) {
return false;
}
@@ -2465,13 +2516,11 @@ static bool PlainDate_with(JSContext* cx, const CallArgs& args) {
if (!temporalDateLike) {
return false;
}
-
- // Step 4.
- if (!RejectTemporalLikeObject(cx, temporalDateLike)) {
+ if (!ThrowIfTemporalLikeObject(cx, temporalDateLike)) {
return false;
}
- // Step 5.
+ // Step 4.
Rooted<PlainObject*> resolvedOptions(cx);
if (args.hasDefined(1)) {
Rooted<JSObject*> options(cx,
@@ -2487,7 +2536,7 @@ static bool PlainDate_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 6.
+ // Step 5.
Rooted<CalendarValue> calendarValue(cx, temporalDate->calendar());
Rooted<CalendarRecord> calendar(cx);
if (!CreateCalendarMethodsRecord(cx, calendarValue,
@@ -2500,7 +2549,7 @@ static bool PlainDate_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 7.
+ // Step 6.
JS::RootedVector<PropertyKey> fieldNames(cx);
if (!CalendarFields(cx, calendar,
{CalendarField::Day, CalendarField::Month,
@@ -2509,34 +2558,34 @@ static bool PlainDate_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 8.
+ // Step 7.
Rooted<PlainObject*> fields(
cx, PrepareTemporalFields(cx, temporalDate, fieldNames));
if (!fields) {
return false;
}
- // Step 9.
+ // Step 8.
Rooted<PlainObject*> partialDate(
cx, PreparePartialTemporalFields(cx, temporalDateLike, fieldNames));
if (!partialDate) {
return false;
}
- // Step 10.
+ // Step 9.
Rooted<JSObject*> mergedFields(
cx, CalendarMergeFields(cx, calendar, fields, partialDate));
if (!mergedFields) {
return false;
}
- // Step 11.
+ // Step 10.
fields = PrepareTemporalFields(cx, mergedFields, fieldNames);
if (!fields) {
return false;
}
- // Step 12.
+ // Step 11.
auto result =
temporal::CalendarDateFromFields(cx, calendar, fields, resolvedOptions);
if (!result) {
@@ -2677,7 +2726,7 @@ static bool PlainDate_toZonedDateTime(JSContext* cx, const CallArgs& args) {
// Steps 3-4
Rooted<TimeZoneValue> timeZone(cx);
- Rooted<Value> temporalTime(cx);
+ PlainTime time = {};
if (args.get(0).isObject()) {
Rooted<JSObject*> item(cx, &args[0].toObject());
@@ -2686,8 +2735,7 @@ static bool PlainDate_toZonedDateTime(JSContext* cx, const CallArgs& args) {
// Step 3.a.i.
timeZone.set(TimeZoneValue(item));
- // Step 3.a.ii.
- temporalTime.setUndefined();
+ // Step 3.a.ii. (Not applicable in our implementation.)
} else {
// Step 3.b.i.
Rooted<Value> timeZoneLike(cx);
@@ -2702,8 +2750,7 @@ static bool PlainDate_toZonedDateTime(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 3.b.ii.2.
- temporalTime.setUndefined();
+ // Step 3.b.ii.2. (Not applicable in our implementation.)
} else {
// Step 3.b.iii.1.
if (!ToTemporalTimeZone(cx, timeZoneLike, &timeZone)) {
@@ -2711,10 +2758,18 @@ static bool PlainDate_toZonedDateTime(JSContext* cx, const CallArgs& args) {
}
// Step 3.b.iii.2.
+ Rooted<Value> temporalTime(cx);
if (!GetProperty(cx, item, item, cx->names().plainTime,
&temporalTime)) {
return false;
}
+
+ // Step 5. (Inlined ToTemporalTimeOrMidnight)
+ if (!temporalTime.isUndefined()) {
+ if (!ToTemporalTime(cx, temporalTime, &time)) {
+ return false;
+ }
+ }
}
}
} else {
@@ -2723,19 +2778,12 @@ static bool PlainDate_toZonedDateTime(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 4.b.
- temporalTime.setUndefined();
+ // Step 4.b. (Not applicable in our implementation.)
}
- // Step 6.a.
- PlainTime time = {};
- if (!temporalTime.isUndefined()) {
- if (!ToTemporalTime(cx, temporalTime, &time)) {
- return false;
- }
- }
+ // Step 5. (Moved next to step 3.b.iii.2.)
- // Steps 5.a and 6.b
+ // Step 6.
Rooted<PlainDateTimeWithCalendar> temporalDateTime(cx);
if (!CreateTemporalDateTime(cx, {date, time}, calendar, &temporalDateTime)) {
return false;
diff --git a/js/src/builtin/temporal/PlainDate.h b/js/src/builtin/temporal/PlainDate.h
index 75a3a3f2a1..f8217eb5e0 100644
--- a/js/src/builtin/temporal/PlainDate.h
+++ b/js/src/builtin/temporal/PlainDate.h
@@ -8,6 +8,7 @@
#define builtin_temporal_PlainDate_h
#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
#include <initializer_list>
#include <stdint.h>
@@ -60,7 +61,7 @@ class PlainDateObject : public NativeObject {
static const ClassSpec classSpec_;
};
-class PlainDateWithCalendar {
+class MOZ_STACK_CLASS PlainDateWithCalendar final {
PlainDate date_;
CalendarValue calendar_;
@@ -150,9 +151,9 @@ bool RegulateISODate(JSContext* cx, const PlainDate& date,
TemporalOverflow overflow, PlainDate* result);
struct RegulatedISODate final {
- double year;
- int32_t month;
- int32_t day;
+ double year = 0;
+ int32_t month = 0;
+ int32_t day = 0;
};
/**
@@ -164,8 +165,9 @@ bool RegulateISODate(JSContext* cx, double year, double month, double day,
/**
* AddISODate ( year, month, day, years, months, weeks, days, overflow )
*/
-bool AddISODate(JSContext* cx, const PlainDate& date, const Duration& duration,
- TemporalOverflow overflow, PlainDate* result);
+bool AddISODate(JSContext* cx, const PlainDate& date,
+ const DateDuration& duration, TemporalOverflow overflow,
+ PlainDate* result);
/**
* AddDate ( calendarRec, plainDate, duration [ , options ] )
@@ -173,8 +175,7 @@ bool AddISODate(JSContext* cx, const PlainDate& date, const Duration& duration,
Wrapped<PlainDateObject*> AddDate(JSContext* cx,
JS::Handle<CalendarRecord> calendar,
JS::Handle<Wrapped<PlainDateObject*>> date,
- const Duration& duration,
- JS::Handle<JSObject*> options);
+ const DateDuration& duration);
/**
* AddDate ( calendarRec, plainDate, duration [ , options ] )
@@ -182,30 +183,14 @@ Wrapped<PlainDateObject*> AddDate(JSContext* cx,
Wrapped<PlainDateObject*> AddDate(JSContext* cx,
JS::Handle<CalendarRecord> calendar,
JS::Handle<Wrapped<PlainDateObject*>> date,
- const Duration& duration);
-
-/**
- * AddDate ( calendarRec, plainDate, duration [ , options ] )
- */
-Wrapped<PlainDateObject*> AddDate(
- JSContext* cx, JS::Handle<CalendarRecord> calendar,
- JS::Handle<Wrapped<PlainDateObject*>> date,
- JS::Handle<Wrapped<DurationObject*>> durationObj,
- JS::Handle<JSObject*> options);
-
-/**
- * AddDate ( calendarRec, plainDate, duration [ , options ] )
- */
-Wrapped<PlainDateObject*> AddDate(
- JSContext* cx, JS::Handle<CalendarRecord> calendar,
- JS::Handle<Wrapped<PlainDateObject*>> date,
- JS::Handle<Wrapped<DurationObject*>> durationObj);
+ const DateDuration& duration,
+ JS::Handle<JSObject*> options);
/**
* AddDate ( calendarRec, plainDate, duration [ , options ] )
*/
bool AddDate(JSContext* cx, JS::Handle<CalendarRecord> calendar,
- const PlainDate& date, const Duration& duration,
+ const PlainDate& date, const DateDuration& duration,
JS::Handle<JSObject*> options, PlainDate* result);
/**
@@ -213,7 +198,7 @@ bool AddDate(JSContext* cx, JS::Handle<CalendarRecord> calendar,
*/
bool AddDate(JSContext* cx, JS::Handle<CalendarRecord> calendar,
JS::Handle<Wrapped<PlainDateObject*>> date,
- const Duration& duration, PlainDate* result);
+ const DateDuration& duration, PlainDate* result);
/**
* DifferenceISODate ( y1, m1, d1, y2, m2, d2, largestUnit )
@@ -227,7 +212,8 @@ DateDuration DifferenceISODate(const PlainDate& start, const PlainDate& end,
bool DifferenceDate(JSContext* cx, JS::Handle<CalendarRecord> calendar,
JS::Handle<Wrapped<PlainDateObject*>> one,
JS::Handle<Wrapped<PlainDateObject*>> two,
- JS::Handle<PlainObject*> options, Duration* result);
+ TemporalUnit largestUnit, JS::Handle<PlainObject*> options,
+ DateDuration* result);
/**
* DifferenceDate ( calendarRec, one, two, options )
@@ -235,7 +221,22 @@ bool DifferenceDate(JSContext* cx, JS::Handle<CalendarRecord> calendar,
bool DifferenceDate(JSContext* cx, JS::Handle<CalendarRecord> calendar,
JS::Handle<Wrapped<PlainDateObject*>> one,
JS::Handle<Wrapped<PlainDateObject*>> two,
- TemporalUnit largestUnit, Duration* result);
+ TemporalUnit largestUnit, DateDuration* result);
+
+/**
+ * DifferenceDate ( calendarRec, one, two, options )
+ */
+bool DifferenceDate(JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ const PlainDate& one, const PlainDate& two,
+ TemporalUnit largestUnit, JS::Handle<PlainObject*> options,
+ DateDuration* result);
+
+/**
+ * DifferenceDate ( calendarRec, one, two, options )
+ */
+bool DifferenceDate(JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ const PlainDate& one, const PlainDate& two,
+ TemporalUnit largestUnit, DateDuration* result);
/**
* CompareISODate ( y1, m1, d1, y2, m2, d2 )
@@ -245,7 +246,7 @@ int32_t CompareISODate(const PlainDate& one, const PlainDate& two);
/**
* BalanceISODate ( year, month, day )
*/
-bool BalanceISODate(JSContext* cx, int32_t year, int32_t month, int64_t day,
+bool BalanceISODate(JSContext* cx, const PlainDate& date, int64_t days,
PlainDate* result);
/**
diff --git a/js/src/builtin/temporal/PlainDateTime.cpp b/js/src/builtin/temporal/PlainDateTime.cpp
index 8f137cfe43..f4b15422b3 100644
--- a/js/src/builtin/temporal/PlainDateTime.cpp
+++ b/js/src/builtin/temporal/PlainDateTime.cpp
@@ -178,11 +178,6 @@ static bool ISODateTimeWithinLimits(T year, T month, T day) {
// components set to zero. That means the maximum value is exclusive, whereas
// the minimum value is inclusive.
- // FIXME: spec bug - GetUTCEpochNanoseconds when called with large |year| may
- // cause MakeDay to return NaN, which makes MakeDate return NaN, which is
- // unexpected in GetUTCEpochNanoseconds, step 4.
- // https://github.com/tc39/proposal-temporal/issues/2315
-
// Definitely in range.
if (minYear < year && year < maxYear) {
return true;
@@ -229,7 +224,7 @@ bool js::temporal::ISODateTimeWithinLimits(double year, double month,
* millisecond, microsecond, nanosecond )
*/
bool js::temporal::ISODateTimeWithinLimits(const PlainDateTime& dateTime) {
- auto& [date, time] = dateTime;
+ const auto& [date, time] = dateTime;
return ::ISODateTimeWithinLimits(date.year, date.month, date.day, time.hour,
time.minute, time.second, time.millisecond,
time.microsecond, time.nanosecond);
@@ -295,37 +290,39 @@ static PlainDateTimeObject* CreateTemporalDateTime(
// Step 6.
dateTime->setFixedSlot(PlainDateTimeObject::ISO_YEAR_SLOT,
- Int32Value(isoYear));
+ Int32Value(int32_t(isoYear)));
// Step 7.
dateTime->setFixedSlot(PlainDateTimeObject::ISO_MONTH_SLOT,
- Int32Value(isoMonth));
+ Int32Value(int32_t(isoMonth)));
// Step 8.
- dateTime->setFixedSlot(PlainDateTimeObject::ISO_DAY_SLOT, Int32Value(isoDay));
+ dateTime->setFixedSlot(PlainDateTimeObject::ISO_DAY_SLOT,
+ Int32Value(int32_t(isoDay)));
// Step 9.
- dateTime->setFixedSlot(PlainDateTimeObject::ISO_HOUR_SLOT, Int32Value(hour));
+ dateTime->setFixedSlot(PlainDateTimeObject::ISO_HOUR_SLOT,
+ Int32Value(int32_t(hour)));
// Step 10.
dateTime->setFixedSlot(PlainDateTimeObject::ISO_MINUTE_SLOT,
- Int32Value(minute));
+ Int32Value(int32_t(minute)));
// Step 11.
dateTime->setFixedSlot(PlainDateTimeObject::ISO_SECOND_SLOT,
- Int32Value(second));
+ Int32Value(int32_t(second)));
// Step 12.
dateTime->setFixedSlot(PlainDateTimeObject::ISO_MILLISECOND_SLOT,
- Int32Value(millisecond));
+ Int32Value(int32_t(millisecond)));
// Step 13.
dateTime->setFixedSlot(PlainDateTimeObject::ISO_MICROSECOND_SLOT,
- Int32Value(microsecond));
+ Int32Value(int32_t(microsecond)));
// Step 14.
dateTime->setFixedSlot(PlainDateTimeObject::ISO_NANOSECOND_SLOT,
- Int32Value(nanosecond));
+ Int32Value(int32_t(nanosecond)));
// Step 15.
dateTime->setFixedSlot(PlainDateTimeObject::CALENDAR_SLOT,
@@ -342,9 +339,10 @@ static PlainDateTimeObject* CreateTemporalDateTime(
PlainDateTimeObject* js::temporal::CreateTemporalDateTime(
JSContext* cx, const PlainDateTime& dateTime,
Handle<CalendarValue> calendar) {
- auto& [date, time] = dateTime;
- auto& [isoYear, isoMonth, isoDay] = date;
- auto& [hour, minute, second, millisecond, microsecond, nanosecond] = time;
+ const auto& [date, time] = dateTime;
+ const auto& [isoYear, isoMonth, isoDay] = date;
+ const auto& [hour, minute, second, millisecond, microsecond, nanosecond] =
+ time;
// Steps 1-2.
if (!ThrowIfInvalidISODateTime(cx, dateTime)) {
@@ -441,7 +439,7 @@ bool js::temporal::InterpretTemporalDateTimeFields(
CalendarMethod::DateFromFields));
// Step 3.
- TimeRecord timeResult;
+ TemporalTimeLike timeResult;
if (!ToTemporalTimeRecord(cx, fields, &timeResult)) {
return false;
}
@@ -698,7 +696,7 @@ bool js::temporal::ToTemporalDateTime(JSContext* cx, Handle<Value> item,
static bool ToTemporalDateTime(
JSContext* cx, Handle<Value> item,
MutableHandle<PlainDateTimeWithCalendar> result) {
- HandleObject options = nullptr;
+ Handle<JSObject*> options = nullptr;
auto* obj = ::ToTemporalDateTime(cx, item, options).unwrapOrNull();
if (!obj) {
@@ -734,42 +732,46 @@ static int32_t CompareISODateTime(const PlainDateTime& one,
/**
* AddDateTime ( year, month, day, hour, minute, second, millisecond,
- * microsecond, nanosecond, calendarRec, years, months, weeks, days, hours,
- * minutes, seconds, milliseconds, microseconds, nanoseconds, options )
+ * microsecond, nanosecond, calendarRec, years, months, weeks, days, norm,
+ * options )
*/
static bool AddDateTime(JSContext* cx, const PlainDateTime& dateTime,
Handle<CalendarRecord> calendar,
- const Duration& duration, Handle<JSObject*> options,
- PlainDateTime* result) {
+ const NormalizedDuration& duration,
+ Handle<JSObject*> options, PlainDateTime* result) {
MOZ_ASSERT(IsValidDuration(duration));
// Step 1.
MOZ_ASSERT(IsValidISODateTime(dateTime));
- MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
// Step 2.
- PlainTime timeResult;
- double daysResult;
- if (!AddTime(cx, dateTime.time, duration, &timeResult, &daysResult)) {
- return false;
- }
+ MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
// Step 3.
- const auto& datePart = dateTime.date;
+ auto timeResult = AddTime(dateTime.time, duration.time);
// Step 4.
- Duration dateDuration = {duration.years, duration.months, duration.weeks,
- daysResult};
- MOZ_ASSERT(IsValidDuration(duration));
+ const auto& datePart = dateTime.date;
// Step 5.
+ auto dateDuration = DateDuration{
+ duration.date.years,
+ duration.date.months,
+ duration.date.weeks,
+ duration.date.days + timeResult.days,
+ };
+ if (!ThrowIfInvalidDuration(cx, dateDuration)) {
+ return false;
+ }
+
+ // Step 6.
PlainDate addedDate;
if (!AddDate(cx, calendar, datePart, dateDuration, options, &addedDate)) {
return false;
}
- // Step 6.
- *result = {addedDate, timeResult};
+ // Step 7.
+ *result = {addedDate, timeResult.time};
return true;
}
@@ -782,7 +784,7 @@ static bool DifferenceISODateTime(JSContext* cx, const PlainDateTime& one,
Handle<CalendarRecord> calendar,
TemporalUnit largestUnit,
Handle<PlainObject*> maybeOptions,
- Duration* result) {
+ NormalizedDuration* result) {
// Steps 1-2.
MOZ_ASSERT(IsValidISODateTime(one));
MOZ_ASSERT(IsValidISODateTime(two));
@@ -795,10 +797,10 @@ static bool DifferenceISODateTime(JSContext* cx, const PlainDateTime& one,
CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
// Step 4.
- auto timeDifference = DifferenceTime(one.time, two.time);
+ auto timeDuration = DifferenceTime(one.time, two.time);
// Step 5.
- int32_t timeSign = DurationSign(timeDifference.toDuration());
+ int32_t timeSign = NormalizedTimeDurationSign(timeDuration);
// Step 6.
int32_t dateSign = CompareISODate(two.date, one.date);
@@ -813,20 +815,8 @@ static bool DifferenceISODateTime(JSContext* cx, const PlainDateTime& one,
adjustedDate.day - timeSign);
// Step 8.b.
- if (!BalanceTimeDuration(cx,
- {
- 0,
- 0,
- 0,
- double(-timeSign),
- timeDifference.hours,
- timeDifference.minutes,
- timeDifference.seconds,
- timeDifference.milliseconds,
- timeDifference.microseconds,
- timeDifference.nanoseconds,
- },
- largestUnit, &timeDifference)) {
+ if (!Add24HourDaysToNormalizedTimeDuration(cx, timeDuration, -timeSign,
+ &timeDuration)) {
return false;
}
}
@@ -834,49 +824,26 @@ static bool DifferenceISODateTime(JSContext* cx, const PlainDateTime& one,
MOZ_ASSERT(IsValidISODate(adjustedDate));
MOZ_ASSERT(ISODateTimeWithinLimits(adjustedDate));
- // TODO: Avoid allocating CreateTemporalDate.
-
// Step 9.
- Rooted<PlainDateObject*> date1(
- cx, CreateTemporalDate(cx, adjustedDate, calendar.receiver()));
- if (!date1) {
- return false;
- }
+ const auto& date1 = adjustedDate;
// Step 10.
- Rooted<PlainDateObject*> date2(
- cx, CreateTemporalDate(cx, two.date, calendar.receiver()));
- if (!date2) {
- return false;
- }
+ const auto& date2 = two.date;
// Step 11.
auto dateLargestUnit = std::min(TemporalUnit::Day, largestUnit);
- Duration dateDifference;
+ DateDuration dateDifference;
if (maybeOptions) {
- // FIXME: spec issue - this copy is no longer needed, all callers have
- // already copied the user input object.
- // https://github.com/tc39/proposal-temporal/issues/2525
-
// Step 12.
- Rooted<PlainObject*> untilOptions(cx,
- SnapshotOwnProperties(cx, maybeOptions));
- if (!untilOptions) {
- return false;
- }
+ //
+ // The spec performs an unnecessary copy operation. As an optimization, we
+ // omit this copy.
+ auto untilOptions = maybeOptions;
- // Step 13.
- Rooted<Value> largestUnitValue(
- cx, StringValue(TemporalUnitToString(cx, dateLargestUnit)));
- if (!DefineDataProperty(cx, untilOptions, cx->names().largestUnit,
- largestUnitValue)) {
- return false;
- }
-
- // Step 14.
- if (!DifferenceDate(cx, calendar, date1, date2, untilOptions,
- &dateDifference)) {
+ // Steps 13-14.
+ if (!DifferenceDate(cx, calendar, date1, date2, dateLargestUnit,
+ untilOptions, &dateDifference)) {
return false;
}
} else {
@@ -888,82 +855,32 @@ static bool DifferenceISODateTime(JSContext* cx, const PlainDateTime& one,
}
// Step 15.
- TimeDuration balanceResult;
- if (!BalanceTimeDuration(cx,
- {
- 0,
- 0,
- 0,
- dateDifference.days,
- timeDifference.hours,
- timeDifference.minutes,
- timeDifference.seconds,
- timeDifference.milliseconds,
- timeDifference.microseconds,
- timeDifference.nanoseconds,
- },
- largestUnit, &balanceResult)) {
- return false;
- }
-
- // Step 16.
- *result = {dateDifference.years, dateDifference.months,
- dateDifference.weeks, balanceResult.days,
- balanceResult.hours, balanceResult.minutes,
- balanceResult.seconds, balanceResult.milliseconds,
- balanceResult.microseconds, balanceResult.nanoseconds};
- MOZ_ASSERT(IsValidDuration(*result));
- return true;
-}
-
-/**
- * DifferenceISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2,
- * d2, h2, min2, s2, ms2, mus2, ns2, calendarRec, largestUnit, options )
- */
-bool js::temporal::DifferenceISODateTime(JSContext* cx,
- const PlainDateTime& one,
- const PlainDateTime& two,
- Handle<CalendarRecord> calendar,
- TemporalUnit largestUnit,
- Duration* result) {
- return ::DifferenceISODateTime(cx, one, two, calendar, largestUnit, nullptr,
- result);
-}
-
-/**
- * DifferenceISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2,
- * d2, h2, min2, s2, ms2, mus2, ns2, calendarRec, largestUnit, options )
- */
-bool js::temporal::DifferenceISODateTime(
- JSContext* cx, const PlainDateTime& one, const PlainDateTime& two,
- Handle<CalendarRecord> calendar, TemporalUnit largestUnit,
- Handle<PlainObject*> options, Duration* result) {
- return ::DifferenceISODateTime(cx, one, two, calendar, largestUnit, options,
- result);
+ return CreateNormalizedDurationRecord(cx, dateDifference, timeDuration,
+ result);
}
/**
* RoundISODateTime ( year, month, day, hour, minute, second, millisecond,
- * microsecond, nanosecond, increment, unit, roundingMode [ , dayLength ] )
+ * microsecond, nanosecond, increment, unit, roundingMode )
*/
-static PlainDateTime RoundISODateTime(const PlainDateTime& dateTime,
- Increment increment, TemporalUnit unit,
- TemporalRoundingMode roundingMode) {
+PlainDateTime js::temporal::RoundISODateTime(
+ const PlainDateTime& dateTime, Increment increment, TemporalUnit unit,
+ TemporalRoundingMode roundingMode) {
const auto& [date, time] = dateTime;
// Step 1.
MOZ_ASSERT(IsValidISODateTime(dateTime));
- MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
- // Step 2. (Not applicable in our implementation.)
+ // Step 2.
+ MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
// Step 3.
auto roundedTime = RoundTime(time, increment, unit, roundingMode);
MOZ_ASSERT(0 <= roundedTime.days && roundedTime.days <= 1);
// Step 4.
- auto balanceResult =
- BalanceISODate(date.year, date.month, date.day + roundedTime.days);
+ auto balanceResult = BalanceISODate(date.year, date.month,
+ date.day + int32_t(roundedTime.days));
// Step 5.
return {balanceResult, roundedTime.time};
@@ -1049,7 +966,7 @@ static bool DifferenceTemporalPlainDateTime(JSContext* cx,
}
// Step 10.
- Duration diff;
+ NormalizedDuration diff;
if (!::DifferenceISODateTime(cx, dateTime, other, calendar,
settings.largestUnit, resolvedOptions, &diff)) {
return false;
@@ -1060,62 +977,81 @@ static bool DifferenceTemporalPlainDateTime(JSContext* cx,
settings.smallestUnit == TemporalUnit::Nanosecond &&
settings.roundingIncrement == Increment{1};
- // Step 12.
- if (roundingGranularityIsNoop) {
- if (operation == TemporalDifference::Since) {
- diff = diff.negate();
+ // Steps 12-13.
+ DateDuration balancedDate;
+ TimeDuration balancedTime;
+ if (!roundingGranularityIsNoop) {
+ // Step 12.a.
+ Rooted<PlainDateObject*> relativeTo(
+ cx, CreateTemporalDate(cx, dateTime.date(), dateTime.calendar()));
+ if (!relativeTo) {
+ return false;
}
- auto* obj = CreateTemporalDuration(cx, diff);
- if (!obj) {
+ // Steps 12.b-c.
+ NormalizedDuration roundResult;
+ if (!temporal::RoundDuration(cx, diff, settings.roundingIncrement,
+ settings.smallestUnit, settings.roundingMode,
+ relativeTo, calendar, &roundResult)) {
return false;
}
- args.rval().setObject(*obj);
- return true;
- }
+ // Step 12.d.
+ NormalizedTimeDuration withDays;
+ if (!Add24HourDaysToNormalizedTimeDuration(
+ cx, roundResult.time, roundResult.date.days, &withDays)) {
+ return false;
+ }
- // Step 13.
- Rooted<PlainDateObject*> relativeTo(
- cx, CreateTemporalDate(cx, dateTime.date(), dateTime.calendar()));
- if (!relativeTo) {
- return false;
- }
+ // Step 12.e.
+ if (!BalanceTimeDuration(cx, withDays, settings.largestUnit,
+ &balancedTime)) {
+ return false;
+ }
- // Steps 14-15.
- Duration roundResult;
- if (!temporal::RoundDuration(cx, diff, settings.roundingIncrement,
- settings.smallestUnit, settings.roundingMode,
- relativeTo, calendar, &roundResult)) {
- return false;
- }
+ // Step 12.f.
+ auto toBalance = DateDuration{
+ roundResult.date.years,
+ roundResult.date.months,
+ roundResult.date.weeks,
+ balancedTime.days,
+ };
+ if (!temporal::BalanceDateDurationRelative(
+ cx, toBalance, settings.largestUnit, settings.smallestUnit,
+ relativeTo, calendar, &balancedDate)) {
+ return false;
+ }
+ } else {
+ // Step 13.a.
+ NormalizedTimeDuration withDays;
+ if (!Add24HourDaysToNormalizedTimeDuration(cx, diff.time, diff.date.days,
+ &withDays)) {
+ return false;
+ }
- // Step 16.
- TimeDuration result;
- if (!BalanceTimeDuration(cx, roundResult, settings.largestUnit, &result)) {
- return false;
- }
+ // Step 13.b.
+ if (!BalanceTimeDuration(cx, withDays, settings.largestUnit,
+ &balancedTime)) {
+ return false;
+ }
- // Step 17.
- auto toBalance = Duration{
- roundResult.years,
- roundResult.months,
- roundResult.weeks,
- result.days,
- };
- DateDuration balanceResult;
- if (!temporal::BalanceDateDurationRelative(
- cx, toBalance, settings.largestUnit, settings.smallestUnit,
- relativeTo, calendar, &balanceResult)) {
- return false;
+ // Step 13.c.
+ balancedDate = {
+ diff.date.years,
+ diff.date.months,
+ diff.date.weeks,
+ balancedTime.days,
+ };
}
+ MOZ_ASSERT(IsValidDuration(balancedDate));
- // Step 18.
+ // Step 14.
Duration duration = {
- balanceResult.years, balanceResult.months, balanceResult.weeks,
- balanceResult.days, result.hours, result.minutes,
- result.seconds, result.milliseconds, result.microseconds,
- result.nanoseconds,
+ double(balancedDate.years), double(balancedDate.months),
+ double(balancedDate.weeks), double(balancedDate.days),
+ double(balancedTime.hours), double(balancedTime.minutes),
+ double(balancedTime.seconds), double(balancedTime.milliseconds),
+ balancedTime.microseconds, balancedTime.nanoseconds,
};
if (operation == TemporalDifference::Since) {
duration = duration.negate();
@@ -1176,16 +1112,18 @@ static bool AddDurationToOrSubtractDurationFromPlainDateTime(
if (operation == PlainDateTimeDuration::Subtract) {
duration = duration.negate();
}
+ auto normalized = CreateNormalizedDurationRecord(duration);
+ // Step 6
PlainDateTime result;
- if (!AddDateTime(cx, dateTime, calendar, duration, options, &result)) {
+ if (!AddDateTime(cx, dateTime, calendar, normalized, options, &result)) {
return false;
}
- // Steps 6-7.
+ // Steps 7-8.
MOZ_ASSERT(IsValidISODateTime(result));
- // Step 8.
+ // Step 9.
auto* obj = CreateTemporalDateTime(cx, result, dateTime.calendar());
if (!obj) {
return false;
@@ -1824,13 +1762,11 @@ static bool PlainDateTime_with(JSContext* cx, const CallArgs& args) {
if (!temporalDateTimeLike) {
return false;
}
-
- // Step 4.
- if (!RejectTemporalLikeObject(cx, temporalDateTimeLike)) {
+ if (!ThrowIfTemporalLikeObject(cx, temporalDateTimeLike)) {
return false;
}
- // Step 5.
+ // Step 4.
Rooted<PlainObject*> resolvedOptions(cx);
if (args.hasDefined(1)) {
Rooted<JSObject*> options(cx,
@@ -1846,7 +1782,7 @@ static bool PlainDateTime_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 6.
+ // Step 5.
Rooted<CalendarValue> calendarValue(cx, dateTime->calendar());
Rooted<CalendarRecord> calendar(cx);
if (!CreateCalendarMethodsRecord(cx, calendarValue,
@@ -1859,7 +1795,7 @@ static bool PlainDateTime_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 7.
+ // Step 6.
JS::RootedVector<PropertyKey> fieldNames(cx);
if (!CalendarFields(cx, calendar,
{CalendarField::Day, CalendarField::Month,
@@ -1868,14 +1804,14 @@ static bool PlainDateTime_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 8.
+ // Step 7.
Rooted<PlainObject*> fields(cx,
PrepareTemporalFields(cx, dateTime, fieldNames));
if (!fields) {
return false;
}
- // Steps 9-14.
+ // Steps 8-13.
struct TimeField {
using FieldName = ImmutableTenuredPtr<PropertyName*> JSAtomState::*;
@@ -1900,7 +1836,7 @@ static bool PlainDateTime_with(JSContext* cx, const CallArgs& args) {
}
}
- // Step 15.
+ // Step 14.
if (!AppendSorted(cx, fieldNames.get(),
{
TemporalField::Hour,
@@ -1913,37 +1849,37 @@ static bool PlainDateTime_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 16.
+ // Step 15.
Rooted<PlainObject*> partialDateTime(
cx, PreparePartialTemporalFields(cx, temporalDateTimeLike, fieldNames));
if (!partialDateTime) {
return false;
}
- // Step 17.
+ // Step 16.
Rooted<JSObject*> mergedFields(
cx, CalendarMergeFields(cx, calendar, fields, partialDateTime));
if (!mergedFields) {
return false;
}
- // Step 18.
+ // Step 17.
fields = PrepareTemporalFields(cx, mergedFields, fieldNames);
if (!fields) {
return false;
}
- // Step 19.
+ // Step 18.
PlainDateTime result;
if (!InterpretTemporalDateTimeFields(cx, calendar, fields, resolvedOptions,
&result)) {
return false;
}
- // Steps 20-21.
+ // Steps 19-20.
MOZ_ASSERT(IsValidISODateTime(result));
- // Step 22.
+ // Step 21.
auto* obj = CreateTemporalDateTime(cx, result, calendar.receiver());
if (!obj) {
return false;
@@ -1970,7 +1906,7 @@ static bool PlainDateTime_withPlainTime(JSContext* cx, const CallArgs& args) {
auto date = ToPlainDate(temporalDateTime);
Rooted<CalendarValue> calendar(cx, temporalDateTime->calendar());
- // Step 4.
+ // Step 3. (Inlined ToTemporalTimeOrMidnight)
PlainTime time = {};
if (args.hasDefined(0)) {
if (!ToTemporalTime(cx, args[0], &time)) {
@@ -1978,7 +1914,7 @@ static bool PlainDateTime_withPlainTime(JSContext* cx, const CallArgs& args) {
}
}
- // Steps 3 and 5.
+ // Step 4.
auto* obj = CreateTemporalDateTime(cx, {date, time}, calendar);
if (!obj) {
return false;
diff --git a/js/src/builtin/temporal/PlainDateTime.h b/js/src/builtin/temporal/PlainDateTime.h
index 3546fca903..1ae80a4508 100644
--- a/js/src/builtin/temporal/PlainDateTime.h
+++ b/js/src/builtin/temporal/PlainDateTime.h
@@ -8,6 +8,7 @@
#define builtin_temporal_PlainDateTime_h
#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
#include <stdint.h>
@@ -104,6 +105,8 @@ inline PlainDateTime ToPlainDateTime(const PlainDateTimeObject* dateTime) {
return {ToPlainDate(dateTime), ToPlainTime(dateTime)};
}
+class Increment;
+enum class TemporalRoundingMode;
enum class TemporalUnit;
#ifdef DEBUG
@@ -170,25 +173,14 @@ bool InterpretTemporalDateTimeFields(JSContext* cx,
PlainDateTime* result);
/**
- * DifferenceISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2,
- * d2, h2, min2, s2, ms2, mus2, ns2, calendarRec, largestUnit, options )
+ * RoundISODateTime ( year, month, day, hour, minute, second, millisecond,
+ * microsecond, nanosecond, increment, unit, roundingMode )
*/
-bool DifferenceISODateTime(JSContext* cx, const PlainDateTime& one,
- const PlainDateTime& two,
- JS::Handle<CalendarRecord> calendar,
- TemporalUnit largestUnit, Duration* result);
+PlainDateTime RoundISODateTime(const PlainDateTime& dateTime,
+ Increment increment, TemporalUnit unit,
+ TemporalRoundingMode roundingMode);
-/**
- * DifferenceISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2,
- * d2, h2, min2, s2, ms2, mus2, ns2, calendarRec, largestUnit, options )
- */
-bool DifferenceISODateTime(JSContext* cx, const PlainDateTime& one,
- const PlainDateTime& two,
- JS::Handle<CalendarRecord> calendar,
- TemporalUnit largestUnit,
- JS::Handle<PlainObject*> options, Duration* result);
-
-class PlainDateTimeWithCalendar {
+class MOZ_STACK_CLASS PlainDateTimeWithCalendar final {
PlainDateTime dateTime_;
CalendarValue calendar_;
diff --git a/js/src/builtin/temporal/PlainMonthDay.cpp b/js/src/builtin/temporal/PlainMonthDay.cpp
index 0896100a3f..50618a5fb6 100644
--- a/js/src/builtin/temporal/PlainMonthDay.cpp
+++ b/js/src/builtin/temporal/PlainMonthDay.cpp
@@ -96,16 +96,19 @@ static PlainMonthDayObject* CreateTemporalMonthDay(
}
// Step 5.
- obj->setFixedSlot(PlainMonthDayObject::ISO_MONTH_SLOT, Int32Value(isoMonth));
+ obj->setFixedSlot(PlainMonthDayObject::ISO_MONTH_SLOT,
+ Int32Value(int32_t(isoMonth)));
// Step 6.
- obj->setFixedSlot(PlainMonthDayObject::ISO_DAY_SLOT, Int32Value(isoDay));
+ obj->setFixedSlot(PlainMonthDayObject::ISO_DAY_SLOT,
+ Int32Value(int32_t(isoDay)));
// Step 7.
obj->setFixedSlot(PlainMonthDayObject::CALENDAR_SLOT, calendar.toValue());
// Step 8.
- obj->setFixedSlot(PlainMonthDayObject::ISO_YEAR_SLOT, Int32Value(isoYear));
+ obj->setFixedSlot(PlainMonthDayObject::ISO_YEAR_SLOT,
+ Int32Value(int32_t(isoYear)));
// Step 9.
return obj;
@@ -117,7 +120,7 @@ static PlainMonthDayObject* CreateTemporalMonthDay(
*/
PlainMonthDayObject* js::temporal::CreateTemporalMonthDay(
JSContext* cx, const PlainDate& date, Handle<CalendarValue> calendar) {
- auto& [isoYear, isoMonth, isoDay] = date;
+ const auto& [isoYear, isoMonth, isoDay] = date;
// Step 1.
if (!ThrowIfInvalidISODate(cx, date)) {
@@ -307,8 +310,6 @@ static Wrapped<PlainMonthDayObject*> ToTemporalMonthDay(
return nullptr;
}
- // FIXME: spec bug - missing call to CreateCalendarMethodsRecord
-
// Step 13.
Rooted<CalendarRecord> calendar(cx);
if (!CreateCalendarMethodsRecord(cx, calendarValue,
@@ -530,13 +531,11 @@ static bool PlainMonthDay_with(JSContext* cx, const CallArgs& args) {
if (!temporalMonthDayLike) {
return false;
}
-
- // Step 4.
- if (!RejectTemporalLikeObject(cx, temporalMonthDayLike)) {
+ if (!ThrowIfTemporalLikeObject(cx, temporalMonthDayLike)) {
return false;
}
- // Step 5.
+ // Step 4.
Rooted<PlainObject*> resolvedOptions(cx);
if (args.hasDefined(1)) {
Rooted<JSObject*> options(cx,
@@ -552,7 +551,7 @@ static bool PlainMonthDay_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 6.
+ // Step 5.
Rooted<CalendarRecord> calendar(cx);
if (!CreateCalendarMethodsRecord(cx, calendarValue,
{
@@ -564,7 +563,7 @@ static bool PlainMonthDay_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 7.
+ // Step 6.
JS::RootedVector<PropertyKey> fieldNames(cx);
if (!CalendarFields(cx, calendar,
{CalendarField::Day, CalendarField::Month,
@@ -573,34 +572,34 @@ static bool PlainMonthDay_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 8.
+ // Step 7.
Rooted<PlainObject*> fields(cx,
PrepareTemporalFields(cx, monthDay, fieldNames));
if (!fields) {
return false;
}
- // Step 9.
+ // Step 8.
Rooted<PlainObject*> partialMonthDay(
cx, PreparePartialTemporalFields(cx, temporalMonthDayLike, fieldNames));
if (!partialMonthDay) {
return false;
}
- // Step 10.
+ // Step 9.
Rooted<JSObject*> mergedFields(
cx, CalendarMergeFields(cx, calendar, fields, partialMonthDay));
if (!mergedFields) {
return false;
}
- // Step 11.
+ // Step 10.
fields = PrepareTemporalFields(cx, mergedFields, fieldNames);
if (!fields) {
return false;
}
- // Step 12.
+ // Step 11.
auto obj = js::temporal::CalendarMonthDayFromFields(cx, calendar, fields,
resolvedOptions);
if (!obj) {
@@ -836,20 +835,7 @@ static bool PlainMonthDay_toPlainDate(JSContext* cx, const CallArgs& args) {
}
// Step 12.
- Rooted<PlainObject*> options(cx, NewPlainObjectWithProto(cx, nullptr));
- if (!options) {
- return false;
- }
-
- // Step 13.
- Rooted<Value> overflow(cx, StringValue(cx->names().constrain));
- if (!DefineDataProperty(cx, options, cx->names().overflow, overflow)) {
- return false;
- }
-
- // Step 14.
- auto obj = js::temporal::CalendarDateFromFields(
- cx, calendar, mergedFromConcatenatedFields, options);
+ auto obj = CalendarDateFromFields(cx, calendar, mergedFromConcatenatedFields);
if (!obj) {
return false;
}
diff --git a/js/src/builtin/temporal/PlainTime.cpp b/js/src/builtin/temporal/PlainTime.cpp
index bf35b9d93e..c928b06d46 100644
--- a/js/src/builtin/temporal/PlainTime.cpp
+++ b/js/src/builtin/temporal/PlainTime.cpp
@@ -7,7 +7,6 @@
#include "builtin/temporal/PlainTime.h"
#include "mozilla/Assertions.h"
-#include "mozilla/CheckedInt.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Maybe.h"
@@ -46,7 +45,6 @@
#include "js/PropertySpec.h"
#include "js/RootingAPI.h"
#include "js/Value.h"
-#include "vm/BigIntType.h"
#include "vm/BytecodeUtil.h"
#include "vm/GlobalObject.h"
#include "vm/JSAtomState.h"
@@ -121,7 +119,8 @@ static bool IsValidTime(T hour, T minute, T second, T millisecond,
* IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
*/
bool js::temporal::IsValidTime(const PlainTime& time) {
- auto& [hour, minute, second, millisecond, microsecond, nanosecond] = time;
+ const auto& [hour, minute, second, millisecond, microsecond, nanosecond] =
+ time;
return ::IsValidTime(hour, minute, second, millisecond, microsecond,
nanosecond);
}
@@ -217,7 +216,8 @@ static bool ThrowIfInvalidTime(JSContext* cx, T hour, T minute, T second,
* IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
*/
bool js::temporal::ThrowIfInvalidTime(JSContext* cx, const PlainTime& time) {
- auto& [hour, minute, second, millisecond, microsecond, nanosecond] = time;
+ const auto& [hour, minute, second, millisecond, microsecond, nanosecond] =
+ time;
return ::ThrowIfInvalidTime(cx, hour, minute, second, millisecond,
microsecond, nanosecond);
}
@@ -261,9 +261,10 @@ static PlainTime ConstrainTime(double hour, double minute, double second,
* RegulateTime ( hour, minute, second, millisecond, microsecond, nanosecond,
* overflow )
*/
-bool js::temporal::RegulateTime(JSContext* cx, const TimeRecord& time,
+bool js::temporal::RegulateTime(JSContext* cx, const TemporalTimeLike& time,
TemporalOverflow overflow, PlainTime* result) {
- auto& [hour, minute, second, millisecond, microsecond, nanosecond] = time;
+ const auto& [hour, minute, second, millisecond, microsecond, nanosecond] =
+ time;
// Step 1.
MOZ_ASSERT(IsInteger(hour));
@@ -334,25 +335,28 @@ static PlainTimeObject* CreateTemporalTime(JSContext* cx, const CallArgs& args,
}
// Step 4.
- object->setFixedSlot(PlainTimeObject::ISO_HOUR_SLOT, Int32Value(hour));
+ object->setFixedSlot(PlainTimeObject::ISO_HOUR_SLOT,
+ Int32Value(int32_t(hour)));
// Step 5.
- object->setFixedSlot(PlainTimeObject::ISO_MINUTE_SLOT, Int32Value(minute));
+ object->setFixedSlot(PlainTimeObject::ISO_MINUTE_SLOT,
+ Int32Value(int32_t(minute)));
// Step 6.
- object->setFixedSlot(PlainTimeObject::ISO_SECOND_SLOT, Int32Value(second));
+ object->setFixedSlot(PlainTimeObject::ISO_SECOND_SLOT,
+ Int32Value(int32_t(second)));
// Step 7.
object->setFixedSlot(PlainTimeObject::ISO_MILLISECOND_SLOT,
- Int32Value(millisecond));
+ Int32Value(int32_t(millisecond)));
// Step 8.
object->setFixedSlot(PlainTimeObject::ISO_MICROSECOND_SLOT,
- Int32Value(microsecond));
+ Int32Value(int32_t(microsecond)));
// Step 9.
object->setFixedSlot(PlainTimeObject::ISO_NANOSECOND_SLOT,
- Int32Value(nanosecond));
+ Int32Value(int32_t(nanosecond)));
// Step 10.
return object;
@@ -364,7 +368,8 @@ static PlainTimeObject* CreateTemporalTime(JSContext* cx, const CallArgs& args,
*/
PlainTimeObject* js::temporal::CreateTemporalTime(JSContext* cx,
const PlainTime& time) {
- auto& [hour, minute, second, millisecond, microsecond, nanosecond] = time;
+ const auto& [hour, minute, second, millisecond, microsecond, nanosecond] =
+ time;
// Step 1.
if (!ThrowIfInvalidTime(cx, time)) {
@@ -403,63 +408,6 @@ PlainTimeObject* js::temporal::CreateTemporalTime(JSContext* cx,
}
/**
- * CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
- * microseconds, nanoseconds )
- */
-static TimeDuration CreateTimeDurationRecord(double days, int32_t hours,
- int32_t minutes, int32_t seconds,
- int32_t milliseconds,
- int32_t microseconds,
- int32_t nanoseconds) {
- // Step 1.
- MOZ_ASSERT(IsValidDuration({0, 0, 0, days, double(hours), double(minutes),
- double(seconds), double(microseconds),
- double(nanoseconds)}));
-
- // Step 2.
- return {
- days,
- double(hours),
- double(minutes),
- double(seconds),
- double(milliseconds),
- double(microseconds),
- double(nanoseconds),
- };
-}
-
-/**
- * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
- * milliseconds, microseconds, nanoseconds )
- */
-static int32_t DurationSign(int32_t hours, int32_t minutes, int32_t seconds,
- int32_t milliseconds, int32_t microseconds,
- int32_t nanoseconds) {
- // Step 1. (Loop unrolled)
- if (hours) {
- return hours > 0 ? 1 : -1;
- }
- if (minutes) {
- return minutes > 0 ? 1 : -1;
- }
- if (seconds) {
- return seconds > 0 ? 1 : -1;
- }
- if (milliseconds) {
- return milliseconds > 0 ? 1 : -1;
- }
- if (microseconds) {
- return microseconds > 0 ? 1 : -1;
- }
- if (nanoseconds) {
- return nanoseconds > 0 ? 1 : -1;
- }
-
- // Step 2.
- return 0;
-}
-
-/**
* BalanceTime ( hour, minute, second, millisecond, microsecond, nanosecond )
*/
template <typename IntT>
@@ -531,7 +479,7 @@ static BalancedTime BalanceTime(int32_t hour, int32_t minute, int32_t second,
BalancedTime js::temporal::BalanceTime(const PlainTime& time,
int64_t nanoseconds) {
MOZ_ASSERT(IsValidTime(time));
- MOZ_ASSERT(std::abs(nanoseconds) <= 2 * ToNanoseconds(TemporalUnit::Day));
+ MOZ_ASSERT(std::abs(nanoseconds) <= ToNanoseconds(TemporalUnit::Day));
return ::BalanceTime<int64_t>(time.hour, time.minute, time.second,
time.millisecond, time.microsecond,
@@ -541,8 +489,8 @@ BalancedTime js::temporal::BalanceTime(const PlainTime& time,
/**
* DifferenceTime ( h1, min1, s1, ms1, mus1, ns1, h2, min2, s2, ms2, mus2, ns2 )
*/
-TimeDuration js::temporal::DifferenceTime(const PlainTime& time1,
- const PlainTime& time2) {
+NormalizedTimeDuration js::temporal::DifferenceTime(const PlainTime& time1,
+ const PlainTime& time2) {
MOZ_ASSERT(IsValidTime(time1));
MOZ_ASSERT(IsValidTime(time2));
@@ -565,22 +513,15 @@ TimeDuration js::temporal::DifferenceTime(const PlainTime& time1,
int32_t nanoseconds = time2.nanosecond - time1.nanosecond;
// Step 7.
- int32_t sign = ::DurationSign(hours, minutes, seconds, milliseconds,
- microseconds, nanoseconds);
+ auto result = NormalizeTimeDuration(hours, minutes, seconds, milliseconds,
+ microseconds, nanoseconds);
// Step 8.
- auto balanced = ::BalanceTime(hours * sign, minutes * sign, seconds * sign,
- milliseconds * sign, microseconds * sign,
- nanoseconds * sign);
+ MOZ_ASSERT(result.abs().toNanoseconds() <
+ Int128{ToNanoseconds(TemporalUnit::Day)});
// Step 9.
- MOZ_ASSERT(balanced.days == 0);
-
- // Step 10.
- return CreateTimeDurationRecord(
- 0, balanced.time.hour * sign, balanced.time.minute * sign,
- balanced.time.second * sign, balanced.time.millisecond * sign,
- balanced.time.microsecond * sign, balanced.time.nanosecond * sign);
+ return result;
}
/**
@@ -628,7 +569,7 @@ static bool ToTemporalTime(JSContext* cx, Handle<Value> item,
}
// Step 3.d.
- TimeRecord timeResult;
+ TemporalTimeLike timeResult;
if (!ToTemporalTimeRecord(cx, itemObj, &timeResult)) {
return false;
}
@@ -684,218 +625,6 @@ bool js::temporal::ToTemporalTime(JSContext* cx, Handle<Value> item,
}
/**
- * TotalDurationNanoseconds ( hours, minutes, seconds, milliseconds,
- * microseconds, nanoseconds )
- */
-static int64_t TotalDurationNanoseconds(const Duration& duration) {
- // This function is only called from BalanceTime. The difference between two
- // plain times can't exceed the number of nanoseconds in a day.
- MOZ_ASSERT(IsValidDuration(duration));
- MOZ_ASSERT(std::abs(duration.hours) <= 24);
- MOZ_ASSERT(std::abs(duration.minutes) <= 60);
- MOZ_ASSERT(std::abs(duration.seconds) <= 60);
- MOZ_ASSERT(std::abs(duration.milliseconds) <= 1000);
- MOZ_ASSERT(std::abs(duration.microseconds) <= 1000);
- MOZ_ASSERT(std::abs(duration.nanoseconds) <= 1000);
-
- // Step 1.
- auto minutes = int64_t(duration.minutes) + int64_t(duration.hours) * 60;
-
- // Step 2.
- auto seconds = int64_t(duration.seconds) + minutes * 60;
-
- // Step 3.
- auto milliseconds = int64_t(duration.milliseconds) + seconds * 1000;
-
- // Step 4.
- auto microseconds = int64_t(duration.microseconds) + milliseconds * 1000;
-
- // Steps 5.
- return int64_t(duration.nanoseconds) + microseconds * 1000;
-}
-
-/**
- * BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds,
- * microseconds, nanoseconds, largestUnit [ , relativeTo ] )
- */
-static Duration BalanceTimeDuration(const Duration& duration,
- TemporalUnit largestUnit) {
- MOZ_ASSERT(IsValidDuration(duration));
- MOZ_ASSERT(largestUnit > TemporalUnit::Day);
-
- // We only handle time components here.
- MOZ_ASSERT(duration.years == 0);
- MOZ_ASSERT(duration.months == 0);
- MOZ_ASSERT(duration.weeks == 0);
- MOZ_ASSERT(duration.days == 0);
-
- // Step 1. (Not applicable)
-
- // Step 2.
- int64_t nanoseconds = TotalDurationNanoseconds(duration);
- MOZ_ASSERT(std::abs(nanoseconds) <= ToNanoseconds(TemporalUnit::Day));
-
- // Steps 3-4. (Not applicable)
-
- // Step 5.
- int64_t hours = 0;
- int64_t minutes = 0;
- int64_t seconds = 0;
- int64_t milliseconds = 0;
- int64_t microseconds = 0;
-
- // Step 6.
- int32_t sign = nanoseconds < 0 ? -1 : +1;
-
- // Step 7.
- nanoseconds = std::abs(nanoseconds);
-
- // Steps 8-13.
- switch (largestUnit) {
- case TemporalUnit::Auto:
- case TemporalUnit::Year:
- case TemporalUnit::Month:
- case TemporalUnit::Week:
- case TemporalUnit::Day:
- MOZ_CRASH("Unexpected temporal unit");
-
- case TemporalUnit::Hour: {
- // Step 8.
-
- // 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;
-
- // Step 8.g.
- minutes = seconds / 60;
-
- // Step 8.h.
- seconds = seconds % 60;
-
- // Step 8.i.
- hours = minutes / 60;
-
- // Step 8.j.
- minutes = minutes % 60;
-
- break;
- }
- case TemporalUnit::Minute: {
- // Step 9.
-
- // Step 9.a.
- microseconds = nanoseconds / 1000;
-
- // Step 9.b.
- nanoseconds = nanoseconds % 1000;
-
- // Step 9.c.
- milliseconds = microseconds / 1000;
-
- // Step 9.d.
- microseconds = microseconds % 1000;
-
- // Step 9.e.
- seconds = milliseconds / 1000;
-
- // Step 9.f.
- milliseconds = milliseconds % 1000;
-
- // Step 9.g.
- minutes = seconds / 60;
-
- // Step 9.h.
- seconds = seconds % 60;
-
- break;
- }
- case TemporalUnit::Second: {
- // Step 10.
-
- // Step 10.a.
- microseconds = nanoseconds / 1000;
-
- // Step 10.b.
- nanoseconds = nanoseconds % 1000;
-
- // Step 10.c.
- milliseconds = microseconds / 1000;
-
- // Step 10.d.
- microseconds = microseconds % 1000;
-
- // Step 10.e.
- seconds = milliseconds / 1000;
-
- // Step 10.f.
- milliseconds = milliseconds % 1000;
-
- break;
- }
- case TemporalUnit::Millisecond: {
- // Step 11.
-
- // Step 11.a.
- microseconds = nanoseconds / 1000;
-
- // Step 11.b.
- nanoseconds = nanoseconds % 1000;
-
- // Step 11.c.
- milliseconds = microseconds / 1000;
-
- // Step 11.d.
- microseconds = microseconds % 1000;
-
- break;
- }
- case TemporalUnit::Microsecond: {
- // Step 12.
-
- // Step 12.a.
- microseconds = nanoseconds / 1000;
-
- // Step 12.b.
- nanoseconds = nanoseconds % 1000;
-
- break;
- }
- case TemporalUnit::Nanosecond: {
- // Step 13.
- break;
- }
- }
-
- // Step 14.
- return {
- 0,
- 0,
- 0,
- 0,
- double(hours * sign),
- double(minutes * sign),
- double(seconds * sign),
- double(milliseconds * sign),
- double(microseconds * sign),
- double(nanoseconds * sign),
- };
-}
-
-/**
* CompareTemporalTime ( h1, min1, s1, ms1, mus1, ns1, h2, min2, s2, ms2, mus2,
* ns2 )
*/
@@ -940,7 +669,7 @@ int32_t js::temporal::CompareTemporalTime(const PlainTime& one,
*/
static bool ToTemporalTimeRecord(JSContext* cx,
Handle<JSObject*> temporalTimeLike,
- TimeRecord* result) {
+ TemporalTimeLike* result) {
// Steps 1 and 3-4. (Not applicable in our implementation.)
// Step 2. (Inlined call to PrepareTemporalFields.)
@@ -1012,7 +741,7 @@ static bool ToTemporalTimeRecord(JSContext* cx,
*/
bool js::temporal::ToTemporalTimeRecord(JSContext* cx,
Handle<JSObject*> temporalTimeLike,
- TimeRecord* result) {
+ TemporalTimeLike* result) {
// Step 3.a. (Set all fields to zero.)
*result = {};
@@ -1020,59 +749,6 @@ bool js::temporal::ToTemporalTimeRecord(JSContext* cx,
return ::ToTemporalTimeRecord(cx, temporalTimeLike, result);
}
-/**
- * RoundNumberToIncrement ( x, increment, roundingMode )
- */
-static int64_t RoundNumberToIncrement(int64_t x, TemporalUnit unit,
- Increment increment,
- TemporalRoundingMode roundingMode) {
- MOZ_ASSERT(x >= 0);
- MOZ_ASSERT(x < ToNanoseconds(TemporalUnit::Day));
-
- MOZ_ASSERT(unit >= TemporalUnit::Day);
- MOZ_ASSERT_IF(unit == TemporalUnit::Day, increment == Increment{1});
- MOZ_ASSERT_IF(unit > TemporalUnit::Day,
- increment <= MaximumTemporalDurationRoundingIncrement(unit));
-
- int64_t divisor = ToNanoseconds(unit) * increment.value();
- MOZ_ASSERT(divisor > 0);
- MOZ_ASSERT(divisor <= ToNanoseconds(TemporalUnit::Day));
-
- // Division by one has no remainder.
- if (divisor == 1) {
- MOZ_ASSERT(increment == Increment{1});
- return x;
- }
-
- // Steps 1-8.
- int64_t rounded = Divide(x, divisor, roundingMode);
-
- // Step 9.
- mozilla::CheckedInt64 result = rounded;
- result *= increment.value();
-
- MOZ_ASSERT(result.isValid(), "can't overflow when inputs are all in range");
-
- return result.value();
-}
-
-/**
- * RoundNumberToIncrement ( x, increment, roundingMode )
- */
-static int64_t RoundNumberToIncrement(int64_t x, int64_t divisor,
- Increment increment,
- TemporalRoundingMode roundingMode) {
- MOZ_ASSERT(x >= 0);
- MOZ_ASSERT(x < ToNanoseconds(TemporalUnit::Day));
- MOZ_ASSERT(divisor > 0);
- MOZ_ASSERT(increment == Increment{1}, "Rounding increment for 'day' is 1");
-
- // Steps 1-2. (Not applicable in our implementation)
-
- // Steps 3-8.
- return Divide(x, divisor, roundingMode);
-}
-
static int64_t TimeToNanos(const PlainTime& time) {
// No overflow possible because the input is a valid time.
MOZ_ASSERT(IsValidTime(time));
@@ -1090,7 +766,7 @@ static int64_t TimeToNanos(const PlainTime& time) {
/**
* RoundTime ( hour, minute, second, millisecond, microsecond, nanosecond,
- * increment, unit, roundingMode [ , dayLengthNs ] )
+ * increment, unit, roundingMode )
*/
RoundedTime js::temporal::RoundTime(const PlainTime& time, Increment increment,
TemporalUnit unit,
@@ -1163,11 +839,15 @@ RoundedTime js::temporal::RoundTime(const PlainTime& time, Increment increment,
}
// Step 9.
- int64_t r = ::RoundNumberToIncrement(TimeToNanos(quantity), unit, increment,
- roundingMode);
- MOZ_ASSERT(r == int64_t(int32_t(r)),
- "no overflow possible due to limited range of arguments");
- *result = r;
+ int64_t nanos = TimeToNanos(quantity);
+ MOZ_ASSERT(0 <= nanos && nanos < ToNanoseconds(TemporalUnit::Day));
+
+ auto r = RoundNumberToIncrement(nanos, ToNanoseconds(unit), increment,
+ roundingMode);
+ MOZ_ASSERT(r == Int128{int32_t(r)},
+ "can't overflow when inputs are all in range");
+
+ *result = int32_t(r);
// Step 10.
if (unit == TemporalUnit::Day) {
@@ -1181,471 +861,31 @@ RoundedTime js::temporal::RoundTime(const PlainTime& time, Increment increment,
}
/**
- * RoundTime ( hour, minute, second, millisecond, microsecond, nanosecond,
- * increment, unit, roundingMode [ , dayLengthNs ] )
+ * AddTime ( hour, minute, second, millisecond, microsecond, nanosecond, norm )
*/
-RoundedTime js::temporal::RoundTime(const PlainTime& time, Increment increment,
- TemporalUnit unit,
- TemporalRoundingMode roundingMode,
- const InstantSpan& dayLengthNs) {
+AddedTime js::temporal::AddTime(const PlainTime& time,
+ const NormalizedTimeDuration& duration) {
MOZ_ASSERT(IsValidTime(time));
- MOZ_ASSERT(IsValidInstantSpan(dayLengthNs));
- MOZ_ASSERT(dayLengthNs > (InstantSpan{}));
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
- if (unit != TemporalUnit::Day) {
- return RoundTime(time, increment, unit, roundingMode);
+ auto [seconds, nanoseconds] = duration;
+ if (seconds < 0 && nanoseconds > 0) {
+ seconds += 1;
+ nanoseconds -= 1'000'000'000;
}
-
- // Step 1. (Not applicable)
-
- // Step 2.
- int64_t quantity = TimeToNanos(time);
- MOZ_ASSERT(quantity < ToNanoseconds(TemporalUnit::Day));
-
- // Steps 3-8. (Not applicable)
-
- // Step 9.
- int64_t divisor;
- if (auto checkedDiv = dayLengthNs.toNanoseconds(); checkedDiv.isValid()) {
- divisor = checkedDiv.value();
- } else {
- // When the divisor is too large, the expression `quantity / divisor` is a
- // value near zero. Substitute |divisor| with an equivalent expression.
- // Choose |86'400'000'000'000| which will give a similar result because
- // |quantity| is guaranteed to be lower than |86'400'000'000'000|.
- divisor = ToNanoseconds(TemporalUnit::Day);
- }
- MOZ_ASSERT(divisor > 0);
-
- int64_t result =
- ::RoundNumberToIncrement(quantity, divisor, increment, roundingMode);
-
- // Step 10.
- return {result, {0, 0, 0, 0, 0, 0}};
-}
-
-/**
- * AddTime ( hour, minute, second, millisecond, microsecond, nanosecond, hours,
- * minutes, seconds, milliseconds, microseconds, nanoseconds )
- */
-static PlainTime AddTime(const PlainTime& time, const Duration& duration) {
- MOZ_ASSERT(IsValidTime(time));
- MOZ_ASSERT(IsValidDuration(duration));
-
- // Balance the duration so we don't have to worry about imprecise Number
- // computations below.
-
- // Use either int64_t or int32_t below. Assert the total combined amount of
- // the units can be expressed in either int64_t or int32_t.
- static_assert(1 * UnitsPerDay(TemporalUnit::Nanosecond) > INT32_MAX,
- "total combined nanoseconds per day");
- static_assert(2 * UnitsPerDay(TemporalUnit::Microsecond) > INT32_MAX,
- "total combined microseconds per day");
- static_assert(3 * UnitsPerDay(TemporalUnit::Millisecond) <= INT32_MAX,
- "total combined milliseconds per day");
- static_assert(4 * UnitsPerDay(TemporalUnit::Second) <= INT32_MAX,
- "total combined seconds per day");
- static_assert(5 * UnitsPerDay(TemporalUnit::Minute) <= INT32_MAX,
- "total combined minutes per day");
- static_assert(6 * UnitsPerDay(TemporalUnit::Hour) <= INT32_MAX,
- "total combined hours per day");
-
- // We ignore the days overflow in this function, therefore it's possible
- // to restrict each unit to units-per-day.
- int64_t nanoseconds = int64_t(
- std::fmod(duration.nanoseconds, UnitsPerDay(TemporalUnit::Nanosecond)));
- int64_t microseconds = int64_t(
- std::fmod(duration.microseconds, UnitsPerDay(TemporalUnit::Microsecond)));
- int32_t milliseconds = int32_t(
- std::fmod(duration.milliseconds, UnitsPerDay(TemporalUnit::Millisecond)));
- int32_t seconds =
- int32_t(std::fmod(duration.seconds, UnitsPerDay(TemporalUnit::Second)));
- int32_t minutes =
- int32_t(std::fmod(duration.minutes, UnitsPerDay(TemporalUnit::Minute)));
- int32_t hours =
- int32_t(std::fmod(duration.hours, UnitsPerDay(TemporalUnit::Hour)));
-
- // Each unit is now less than the units-per-day.
- MOZ_ASSERT(std::abs(nanoseconds) < UnitsPerDay(TemporalUnit::Nanosecond));
- MOZ_ASSERT(std::abs(microseconds) < UnitsPerDay(TemporalUnit::Microsecond));
- MOZ_ASSERT(std::abs(milliseconds) < UnitsPerDay(TemporalUnit::Millisecond));
- MOZ_ASSERT(std::abs(seconds) < UnitsPerDay(TemporalUnit::Second));
- MOZ_ASSERT(std::abs(minutes) < UnitsPerDay(TemporalUnit::Minute));
- MOZ_ASSERT(std::abs(hours) < UnitsPerDay(TemporalUnit::Hour));
-
- microseconds += nanoseconds / 1000;
- nanoseconds %= 1000;
- MOZ_ASSERT(microseconds < 2 * UnitsPerDay(TemporalUnit::Microsecond));
-
- milliseconds += microseconds / 1000;
- microseconds %= 1000;
- MOZ_ASSERT(milliseconds < 3 * UnitsPerDay(TemporalUnit::Millisecond));
-
- seconds += milliseconds / 1000;
- milliseconds %= 1000;
- MOZ_ASSERT(seconds < 4 * UnitsPerDay(TemporalUnit::Second));
-
- minutes += seconds / 60;
- seconds %= 60;
- MOZ_ASSERT(minutes < 5 * UnitsPerDay(TemporalUnit::Minute));
-
- hours += minutes / 60;
- minutes %= 60;
- MOZ_ASSERT(hours < 6 * UnitsPerDay(TemporalUnit::Hour));
-
- hours %= 24;
-
- MOZ_ASSERT(std::abs(hours) <= 23);
- MOZ_ASSERT(std::abs(minutes) <= 59);
- MOZ_ASSERT(std::abs(seconds) <= 59);
- MOZ_ASSERT(std::abs(milliseconds) <= 999);
- MOZ_ASSERT(std::abs(microseconds) <= 999);
- MOZ_ASSERT(std::abs(nanoseconds) <= 999);
+ MOZ_ASSERT(std::abs(nanoseconds) <= 999'999'999);
// Step 1.
- int32_t hour = time.hour + hours;
+ int64_t second = time.second + seconds;
// Step 2.
- int32_t minute = time.minute + minutes;
+ int32_t nanosecond = time.nanosecond + nanoseconds;
// Step 3.
- int32_t second = time.second + seconds;
-
- // Step 4.
- int32_t millisecond = time.millisecond + milliseconds;
-
- // Step 5.
- int32_t microsecond = time.microsecond + int32_t(microseconds);
-
- // Step 6.
- int32_t nanosecond = time.nanosecond + int32_t(nanoseconds);
-
- // Step 7.
auto balanced =
- ::BalanceTime(hour, minute, second, millisecond, microsecond, nanosecond);
- return balanced.time;
-}
-
-static BigInt* FloorDiv(JSContext* cx, Handle<BigInt*> dividend,
- int32_t divisor) {
- MOZ_ASSERT(divisor > 0);
-
- Rooted<BigInt*> div(cx, BigInt::createFromInt64(cx, divisor));
- if (!div) {
- return nullptr;
- }
-
- Rooted<BigInt*> quotient(cx);
- Rooted<BigInt*> remainder(cx);
- if (!BigInt::divmod(cx, dividend, div, &quotient, &remainder)) {
- return nullptr;
- }
- if (remainder->isNegative()) {
- return BigInt::dec(cx, quotient);
- }
- return quotient;
-}
-
-static bool AddTimeDaysSlow(JSContext* cx, const PlainTime& time,
- const Duration& duration, double* result) {
- MOZ_ASSERT(IsValidTime(time));
- MOZ_ASSERT(IsValidDuration(duration));
-
- Rooted<BigInt*> days(cx, BigInt::createFromDouble(cx, duration.days));
- if (!days) {
- return false;
- }
-
- Rooted<BigInt*> hours(cx, BigInt::createFromDouble(cx, duration.hours));
- if (!hours) {
- return false;
- }
-
- Rooted<BigInt*> minutes(cx, BigInt::createFromDouble(cx, duration.minutes));
- if (!minutes) {
- return false;
- }
-
- Rooted<BigInt*> seconds(cx, BigInt::createFromDouble(cx, duration.seconds));
- if (!seconds) {
- return false;
- }
-
- Rooted<BigInt*> milliseconds(
- cx, BigInt::createFromDouble(cx, duration.milliseconds));
- if (!milliseconds) {
- return false;
- }
-
- Rooted<BigInt*> microseconds(
- cx, BigInt::createFromDouble(cx, duration.microseconds));
- if (!microseconds) {
- return false;
- }
-
- Rooted<BigInt*> nanoseconds(
- cx, BigInt::createFromDouble(cx, duration.nanoseconds));
- if (!nanoseconds) {
- return false;
- }
-
- auto addWithInt32 = [cx](Handle<BigInt*> left, int32_t right) -> BigInt* {
- Rooted<BigInt*> rightBigInt(cx, BigInt::createFromInt64(cx, right));
- if (!rightBigInt) {
- return nullptr;
- }
- return BigInt::add(cx, left, rightBigInt);
- };
-
- // Step 1.
- Rooted<BigInt*> hour(cx, addWithInt32(hours, time.hour));
- if (!hour) {
- return false;
- }
-
- // Step 2.
- Rooted<BigInt*> minute(cx, addWithInt32(minutes, time.minute));
- if (!minute) {
- return false;
- }
-
- // Step 3.
- Rooted<BigInt*> second(cx, addWithInt32(seconds, time.second));
- if (!second) {
- return false;
- }
-
- // Step 4.
- Rooted<BigInt*> millisecond(cx, addWithInt32(milliseconds, time.millisecond));
- if (!millisecond) {
- return false;
- }
-
- // Step 5.
- Rooted<BigInt*> microsecond(cx, addWithInt32(microseconds, time.microsecond));
- if (!microsecond) {
- return false;
- }
-
- // Step 6.
- Rooted<BigInt*> nanosecond(cx, addWithInt32(nanoseconds, time.nanosecond));
- if (!nanosecond) {
- return false;
- }
-
- // Step 7. (Inlined BalanceTime)
-
- auto addFloorDiv = [cx](Handle<BigInt*> left, Handle<BigInt*> right,
- int32_t divisor) -> BigInt* {
- Rooted<BigInt*> quotient(cx, FloorDiv(cx, right, divisor));
- if (!quotient) {
- return nullptr;
- }
- return BigInt::add(cx, left, quotient);
- };
-
- // BalanceTime, steps 1-2.
- microsecond = addFloorDiv(microsecond, nanosecond, 1000);
- if (!microsecond) {
- return false;
- }
-
- // BalanceTime, steps 3-4.
- millisecond = addFloorDiv(millisecond, microsecond, 1000);
- if (!millisecond) {
- return false;
- }
-
- // BalanceTime, steps 5-6.
- second = addFloorDiv(second, millisecond, 1000);
- if (!second) {
- return false;
- }
-
- // BalanceTime, steps 7-8.
- minute = addFloorDiv(minute, second, 60);
- if (!minute) {
- return false;
- }
-
- // BalanceTime, steps 9-10.
- hour = addFloorDiv(hour, minute, 60);
- if (!hour) {
- return false;
- }
-
- // BalanceTime, steps 11-13.
- days = addFloorDiv(days, hour, 24);
- if (!days) {
- return false;
- }
-
- // The days number is used as the input for a duration. Throw if the BigInt
- // when converted to a Number can't be represented in a duration.
- double daysNumber = BigInt::numberValue(days);
- if (!ThrowIfInvalidDuration(cx, {0, 0, 0, daysNumber})) {
- return false;
- }
- MOZ_ASSERT(IsInteger(daysNumber));
-
- *result = daysNumber;
- return true;
-}
-
-static mozilla::Maybe<int64_t> AddTimeDays(const PlainTime& time,
- const Duration& duration) {
- MOZ_ASSERT(IsValidTime(time));
- MOZ_ASSERT(IsValidDuration(duration));
-
- int64_t days;
- if (!mozilla::NumberEqualsInt64(duration.days, &days)) {
- return mozilla::Nothing();
- }
-
- int64_t hours;
- if (!mozilla::NumberEqualsInt64(duration.hours, &hours)) {
- return mozilla::Nothing();
- }
-
- int64_t minutes;
- if (!mozilla::NumberEqualsInt64(duration.minutes, &minutes)) {
- return mozilla::Nothing();
- }
-
- int64_t seconds;
- if (!mozilla::NumberEqualsInt64(duration.seconds, &seconds)) {
- return mozilla::Nothing();
- }
-
- int64_t milliseconds;
- if (!mozilla::NumberEqualsInt64(duration.milliseconds, &milliseconds)) {
- return mozilla::Nothing();
- }
-
- int64_t microseconds;
- if (!mozilla::NumberEqualsInt64(duration.microseconds, &microseconds)) {
- return mozilla::Nothing();
- }
-
- int64_t nanoseconds;
- if (!mozilla::NumberEqualsInt64(duration.nanoseconds, &nanoseconds)) {
- return mozilla::Nothing();
- }
-
- // Step 1.
- auto hour = mozilla::CheckedInt64(time.hour) + hours;
- if (!hour.isValid()) {
- return mozilla::Nothing();
- }
-
- // Step 2.
- auto minute = mozilla::CheckedInt64(time.minute) + minutes;
- if (!minute.isValid()) {
- return mozilla::Nothing();
- }
-
- // Step 3.
- auto second = mozilla::CheckedInt64(time.second) + seconds;
- if (!second.isValid()) {
- return mozilla::Nothing();
- }
-
- // Step 4.
- auto millisecond = mozilla::CheckedInt64(time.millisecond) + milliseconds;
- if (!millisecond.isValid()) {
- return mozilla::Nothing();
- }
-
- // Step 5.
- auto microsecond = mozilla::CheckedInt64(time.microsecond) + microseconds;
- if (!microsecond.isValid()) {
- return mozilla::Nothing();
- }
-
- // Step 6.
- auto nanosecond = mozilla::CheckedInt64(time.nanosecond) + nanoseconds;
- if (!nanosecond.isValid()) {
- return mozilla::Nothing();
- }
-
- // Step 7. (Inlined BalanceTime)
-
- // BalanceTime, steps 1-2.
- microsecond += FloorDiv(nanosecond.value(), 1000);
- if (!microsecond.isValid()) {
- return mozilla::Nothing();
- }
-
- // BalanceTime, steps 3-4.
- millisecond += FloorDiv(microsecond.value(), 1000);
- if (!millisecond.isValid()) {
- return mozilla::Nothing();
- }
-
- // BalanceTime, steps 5-6.
- second += FloorDiv(millisecond.value(), 1000);
- if (!second.isValid()) {
- return mozilla::Nothing();
- }
-
- // BalanceTime, steps 7-8.
- minute += FloorDiv(second.value(), 60);
- if (!minute.isValid()) {
- return mozilla::Nothing();
- }
-
- // BalanceTime, steps 9-10.
- hour += FloorDiv(minute.value(), 60);
- if (!hour.isValid()) {
- return mozilla::Nothing();
- }
-
- // BalanceTime, steps 11-13.
- auto result = mozilla::CheckedInt64(days) + FloorDiv(hour.value(), 24);
- if (!result.isValid()) {
- return mozilla::Nothing();
- }
- return mozilla::Some(result.value());
-}
-
-static bool AddTimeDays(JSContext* cx, const PlainTime& time,
- const Duration& duration, double* result) {
- // Fast-path when we can perform the whole computation with int64 values.
- if (auto days = AddTimeDays(time, duration)) {
- *result = *days;
- return true;
- }
- return AddTimeDaysSlow(cx, time, duration, result);
-}
-
-/**
- * AddTime ( hour, minute, second, millisecond, microsecond, nanosecond, hours,
- * minutes, seconds, milliseconds, microseconds, nanoseconds )
- */
-bool js::temporal::AddTime(JSContext* cx, const PlainTime& time,
- const Duration& duration, PlainTime* result,
- double* daysResult) {
- MOZ_ASSERT(IsValidTime(time));
- MOZ_ASSERT(IsValidDuration(duration));
-
- // Steps 1-7.
- auto balanced = ::AddTime(time, duration);
-
- // Compute |days| separately to ensure no loss of precision occurs.
- //
- // The single caller of this |AddTime| function also needs to compute the
- // addition of |duration.days| and the balanced days. Perform this addition
- // here, so we don't need to pass around BigInt values for exact mathematical
- // results.
- double days;
- if (!AddTimeDays(cx, time, duration, &days)) {
- return false;
- }
- MOZ_ASSERT(IsInteger(days));
-
- *result = balanced;
- *daysResult = days;
- return true;
+ ::BalanceTime<int64_t>(time.hour, time.minute, second, time.millisecond,
+ time.microsecond, nanosecond);
+ return {balanced.days, balanced.time};
}
/**
@@ -1699,30 +939,28 @@ static bool DifferenceTemporalPlainTime(JSContext* cx,
// Step 5.
auto diff = DifferenceTime(temporalTime, other);
- MOZ_ASSERT(diff.days == 0);
// Step 6.
- auto roundedDuration = diff.toDuration();
if (settings.smallestUnit != TemporalUnit::Nanosecond ||
settings.roundingIncrement != Increment{1}) {
// Steps 6.a-b.
- if (!RoundDuration(cx, roundedDuration.time(), settings.roundingIncrement,
- settings.smallestUnit, settings.roundingMode,
- &roundedDuration)) {
- return false;
- }
+ diff = RoundDuration(diff, settings.roundingIncrement,
+ settings.smallestUnit, settings.roundingMode);
}
// Step 7.
- auto balancedDuration =
- BalanceTimeDuration(roundedDuration, settings.largestUnit);
+ TimeDuration balancedDuration;
+ if (!BalanceTimeDuration(cx, diff, settings.largestUnit, &balancedDuration)) {
+ return false;
+ }
// Step 8.
+ auto duration = balancedDuration.toDuration();
if (operation == TemporalDifference::Since) {
- balancedDuration = balancedDuration.negate();
+ duration = duration.negate();
}
- auto* result = CreateTemporalDuration(cx, balancedDuration);
+ auto* result = CreateTemporalDuration(cx, duration);
if (!result) {
return false;
}
@@ -1754,13 +992,14 @@ static bool AddDurationToOrSubtractDurationFromPlainTime(
if (operation == PlainTimeDuration::Subtract) {
duration = duration.negate();
}
- auto result = AddTime(time, duration);
+ auto timeDuration = NormalizeTimeDuration(duration);
// Step 4.
- MOZ_ASSERT(IsValidTime(result));
+ auto result = AddTime(time, timeDuration);
+ MOZ_ASSERT(IsValidTime(result.time));
// Step 5.
- auto* obj = CreateTemporalTime(cx, result);
+ auto* obj = CreateTemporalTime(cx, result.time);
if (!obj) {
return false;
}
@@ -1864,7 +1103,7 @@ static bool PlainTime_from(JSContext* cx, unsigned argc, Value* vp) {
}
// Steps 4-5.
- auto result = ToTemporalTime(cx, args.get(0), overflow);
+ auto* result = ToTemporalTime(cx, args.get(0), overflow);
if (!result) {
return false;
}
@@ -2059,29 +1298,27 @@ static bool PlainTime_with(JSContext* cx, const CallArgs& args) {
if (!temporalTimeLike) {
return false;
}
-
- // Step 4.
- if (!RejectTemporalLikeObject(cx, temporalTimeLike)) {
+ if (!ThrowIfTemporalLikeObject(cx, temporalTimeLike)) {
return false;
}
auto overflow = TemporalOverflow::Constrain;
if (args.hasDefined(1)) {
- // Step 5.
+ // Step 4.
Rooted<JSObject*> options(cx,
RequireObjectArg(cx, "options", "with", args[1]));
if (!options) {
return false;
}
- // Step 6.
+ // Step 5.
if (!ToTemporalOverflow(cx, options, &overflow)) {
return false;
}
}
- // Steps 7-19.
- TimeRecord partialTime = {
+ // Steps 6-18.
+ TemporalTimeLike partialTime = {
double(time.hour), double(time.minute),
double(time.second), double(time.millisecond),
double(time.microsecond), double(time.nanosecond),
@@ -2090,13 +1327,13 @@ static bool PlainTime_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 20.
+ // Step 19.
PlainTime result;
if (!RegulateTime(cx, partialTime, overflow, &result)) {
return false;
}
- // Step 21.
+ // Step 20.
auto* obj = CreateTemporalTime(cx, result);
if (!obj) {
return false;
diff --git a/js/src/builtin/temporal/PlainTime.h b/js/src/builtin/temporal/PlainTime.h
index b6da469913..0614430e46 100644
--- a/js/src/builtin/temporal/PlainTime.h
+++ b/js/src/builtin/temporal/PlainTime.h
@@ -112,19 +112,24 @@ PlainTimeObject* CreateTemporalTime(JSContext* cx, const PlainTime& time);
bool ToTemporalTime(JSContext* cx, JS::Handle<JS::Value> item,
PlainTime* result);
+struct AddedTime {
+ int32_t days = 0;
+ PlainTime time;
+};
+
/**
- * AddTime ( hour, minute, second, millisecond, microsecond, nanosecond, hours,
- * minutes, seconds, milliseconds, microseconds, nanoseconds )
+ * AddTime ( hour, minute, second, millisecond, microsecond, nanosecond, norm )
*/
-bool AddTime(JSContext* cx, const PlainTime& time, const Duration& duration,
- PlainTime* result, double* daysResult);
+AddedTime AddTime(const PlainTime& time,
+ const NormalizedTimeDuration& duration);
/**
* DifferenceTime ( h1, min1, s1, ms1, mus1, ns1, h2, min2, s2, ms2, mus2, ns2 )
*/
-TimeDuration DifferenceTime(const PlainTime& time1, const PlainTime& time2);
+NormalizedTimeDuration DifferenceTime(const PlainTime& time1,
+ const PlainTime& time2);
-struct TimeRecord final {
+struct TemporalTimeLike final {
double hour = 0;
double minute = 0;
double second = 0;
@@ -137,13 +142,13 @@ struct TimeRecord final {
* ToTemporalTimeRecord ( temporalTimeLike [ , completeness ] )
*/
bool ToTemporalTimeRecord(JSContext* cx, JS::Handle<JSObject*> temporalTimeLike,
- TimeRecord* result);
+ TemporalTimeLike* result);
/**
* RegulateTime ( hour, minute, second, millisecond, microsecond, nanosecond,
* overflow )
*/
-bool RegulateTime(JSContext* cx, const TimeRecord& time,
+bool RegulateTime(JSContext* cx, const TemporalTimeLike& time,
TemporalOverflow overflow, PlainTime* result);
/**
@@ -169,19 +174,11 @@ struct RoundedTime final {
/**
* RoundTime ( hour, minute, second, millisecond, microsecond, nanosecond,
- * increment, unit, roundingMode [ , dayLengthNs ] )
+ * increment, unit, roundingMode )
*/
RoundedTime RoundTime(const PlainTime& time, Increment increment,
TemporalUnit unit, TemporalRoundingMode roundingMode);
-/**
- * RoundTime ( hour, minute, second, millisecond, microsecond, nanosecond,
- * increment, unit, roundingMode [ , dayLengthNs ] )
- */
-RoundedTime RoundTime(const PlainTime& time, Increment increment,
- TemporalUnit unit, TemporalRoundingMode roundingMode,
- const InstantSpan& dayLengthNs);
-
} /* namespace js::temporal */
#endif /* builtin_temporal_PlainTime_h */
diff --git a/js/src/builtin/temporal/PlainYearMonth.cpp b/js/src/builtin/temporal/PlainYearMonth.cpp
index b95efd3179..c28277e2be 100644
--- a/js/src/builtin/temporal/PlainYearMonth.cpp
+++ b/js/src/builtin/temporal/PlainYearMonth.cpp
@@ -111,7 +111,7 @@ static PlainYearMonthObject* CreateTemporalYearMonth(
// testing |referenceISODay|?
// Step 2.
- if (!ISOYearMonthWithinLimits(isoYear, isoMonth)) {
+ if (!ISOYearMonthWithinLimits(isoYear, int32_t(isoMonth))) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_PLAIN_YEAR_MONTH_INVALID);
return nullptr;
@@ -130,16 +130,19 @@ static PlainYearMonthObject* CreateTemporalYearMonth(
}
// Step 5.
- obj->setFixedSlot(PlainYearMonthObject::ISO_YEAR_SLOT, Int32Value(isoYear));
+ obj->setFixedSlot(PlainYearMonthObject::ISO_YEAR_SLOT,
+ Int32Value(int32_t(isoYear)));
// Step 6.
- obj->setFixedSlot(PlainYearMonthObject::ISO_MONTH_SLOT, Int32Value(isoMonth));
+ obj->setFixedSlot(PlainYearMonthObject::ISO_MONTH_SLOT,
+ Int32Value(int32_t(isoMonth)));
// Step 7.
obj->setFixedSlot(PlainYearMonthObject::CALENDAR_SLOT, calendar.toValue());
// Step 8.
- obj->setFixedSlot(PlainYearMonthObject::ISO_DAY_SLOT, Int32Value(isoDay));
+ obj->setFixedSlot(PlainYearMonthObject::ISO_DAY_SLOT,
+ Int32Value(int32_t(isoDay)));
// Step 9.
return obj;
@@ -151,7 +154,7 @@ static PlainYearMonthObject* CreateTemporalYearMonth(
*/
PlainYearMonthObject* js::temporal::CreateTemporalYearMonth(
JSContext* cx, const PlainDate& date, Handle<CalendarValue> calendar) {
- auto& [isoYear, isoMonth, isoDay] = date;
+ const auto& [isoYear, isoMonth, isoDay] = date;
// Step 1.
if (!ThrowIfInvalidISODate(cx, date)) {
@@ -251,8 +254,8 @@ static Wrapped<PlainYearMonthObject*> ToTemporalYearMonth(
// Step 3.f.
if (maybeResolvedOptions) {
- return CalendarYearMonthFromFields(cx, calendar, fields,
- maybeResolvedOptions);
+ return temporal::CalendarYearMonthFromFields(cx, calendar, fields,
+ maybeResolvedOptions);
}
return CalendarYearMonthFromFields(cx, calendar, fields);
}
@@ -420,9 +423,6 @@ static bool DifferenceTemporalPlainYearMonth(JSContext* cx,
}
// Step 8.
- // FIXME: spec issue - duplicate CreateDataPropertyOrThrow for "largestUnit".
-
- // Step 9.
Rooted<CalendarRecord> calendarRec(cx);
if (!CreateCalendarMethodsRecord(cx, calendar,
{
@@ -435,7 +435,7 @@ static bool DifferenceTemporalPlainYearMonth(JSContext* cx,
return false;
}
- // Step 10.
+ // Step 9.
JS::RootedVector<PropertyKey> fieldNames(cx);
if (!CalendarFields(cx, calendarRec,
{CalendarField::MonthCode, CalendarField::Year},
@@ -443,101 +443,92 @@ static bool DifferenceTemporalPlainYearMonth(JSContext* cx,
return false;
}
- // Step 11.
+ // Step 10.
Rooted<PlainObject*> thisFields(
cx, PrepareTemporalFields(cx, yearMonth, fieldNames));
if (!thisFields) {
return false;
}
- // Step 12.
+ // Step 11.
Value one = Int32Value(1);
auto handleOne = Handle<Value>::fromMarkedLocation(&one);
if (!DefineDataProperty(cx, thisFields, cx->names().day, handleOne)) {
return false;
}
- // Step 13.
+ // Step 12.
Rooted<Wrapped<PlainDateObject*>> thisDate(
cx, CalendarDateFromFields(cx, calendarRec, thisFields));
if (!thisDate) {
return false;
}
- // Step 14.
+ // Step 13.
Rooted<PlainObject*> otherFields(
cx, PrepareTemporalFields(cx, other, fieldNames));
if (!otherFields) {
return false;
}
- // Step 15.
+ // Step 14.
if (!DefineDataProperty(cx, otherFields, cx->names().day, handleOne)) {
return false;
}
- // Step 16.
+ // Step 15.
Rooted<Wrapped<PlainDateObject*>> otherDate(
cx, CalendarDateFromFields(cx, calendarRec, otherFields));
if (!otherDate) {
return false;
}
- // Steps 17-18.
- Duration result;
+ // Steps 16-17.
+ DateDuration until;
if (resolvedOptions) {
- // Step 17.
- Rooted<Value> largestUnitValue(
- cx, StringValue(TemporalUnitToString(cx, settings.largestUnit)));
- if (!DefineDataProperty(cx, resolvedOptions, cx->names().largestUnit,
- largestUnitValue)) {
- return false;
- }
-
- // Step 18.
if (!CalendarDateUntil(cx, calendarRec, thisDate, otherDate,
- resolvedOptions, &result)) {
+ settings.largestUnit, resolvedOptions, &until)) {
return false;
}
} else {
- // Steps 17-18.
if (!CalendarDateUntil(cx, calendarRec, thisDate, otherDate,
- settings.largestUnit, &result)) {
+ settings.largestUnit, &until)) {
return false;
}
}
// We only care about years and months here, all other fields are set to zero.
- Duration duration = {result.years, result.months};
+ auto dateDuration = DateDuration{until.years, until.months};
- // Step 19.
+ // Step 18.
if (settings.smallestUnit != TemporalUnit::Month ||
settings.roundingIncrement != Increment{1}) {
- // Steps 19.a-b.
- Duration roundResult;
- if (!RoundDuration(cx, duration, settings.roundingIncrement,
+ // Steps 18.a-b.
+ NormalizedDuration roundResult;
+ if (!RoundDuration(cx, {dateDuration, {}}, settings.roundingIncrement,
settings.smallestUnit, settings.roundingMode, thisDate,
calendarRec, &roundResult)) {
return false;
}
- // Step 19.c.
- auto toBalance = Duration{roundResult.years, roundResult.months};
- DateDuration balanceResult;
+ // Step 18.c.
+ auto toBalance =
+ DateDuration{roundResult.date.years, roundResult.date.months};
if (!temporal::BalanceDateDurationRelative(
cx, toBalance, settings.largestUnit, settings.smallestUnit,
- thisDate, calendarRec, &balanceResult)) {
+ thisDate, calendarRec, &dateDuration)) {
return false;
}
- duration = balanceResult.toDuration();
}
- // Step 20.
+ // Step 19.
+ auto duration =
+ Duration{double(dateDuration.years), double(dateDuration.months)};
if (operation == TemporalDifference::Since) {
duration = duration.negate();
}
- auto* obj = CreateTemporalDuration(cx, {duration.years, duration.months});
+ auto* obj = CreateTemporalDuration(cx, duration);
if (!obj) {
return false;
}
@@ -569,16 +560,37 @@ static bool AddDurationToOrSubtractDurationFromPlainYearMonth(
}
// Step 3.
- TimeDuration balanceResult;
- if (!BalanceTimeDuration(cx, duration, TemporalUnit::Day, &balanceResult)) {
+ Rooted<JSObject*> options(cx);
+ if (args.hasDefined(1)) {
+ const char* name =
+ operation == PlainYearMonthDuration::Add ? "add" : "subtract";
+ options = RequireObjectArg(cx, "options", name, args[1]);
+ } else {
+ // TODO: Avoid creating an options object if not necessary.
+ options = NewPlainObjectWithProto(cx, nullptr);
+ }
+ if (!options) {
return false;
}
// Step 4.
- int32_t sign = DurationSign(
- {duration.years, duration.months, duration.weeks, balanceResult.days});
+ auto timeDuration = NormalizeTimeDuration(duration);
// Step 5.
+ auto balancedTime = BalanceTimeDuration(timeDuration, TemporalUnit::Day);
+
+ // Steps 6 and 16. (Reordered)
+ auto durationToAdd = DateDuration{
+ int64_t(duration.years),
+ int64_t(duration.months),
+ int64_t(duration.weeks),
+ int64_t(duration.days) + balancedTime.days,
+ };
+
+ // Step 7.
+ int32_t sign = DurationSign(durationToAdd);
+
+ // Step 8.
Rooted<CalendarValue> calendarValue(cx, yearMonth->calendar());
Rooted<CalendarRecord> calendar(cx);
if (!CreateCalendarMethodsRecord(cx, calendarValue,
@@ -593,7 +605,7 @@ static bool AddDurationToOrSubtractDurationFromPlainYearMonth(
return false;
};
- // Step 6.
+ // Step 9.
JS::RootedVector<PropertyKey> fieldNames(cx);
if (!CalendarFields(cx, calendar,
{CalendarField::MonthCode, CalendarField::Year},
@@ -601,34 +613,34 @@ static bool AddDurationToOrSubtractDurationFromPlainYearMonth(
return false;
}
- // Step 7.
+ // Step 10.
Rooted<PlainObject*> fields(cx,
PrepareTemporalFields(cx, yearMonth, fieldNames));
if (!fields) {
return false;
}
- // Step 8.
+ // Step 11.
Rooted<PlainObject*> fieldsCopy(cx, SnapshotOwnProperties(cx, fields));
if (!fieldsCopy) {
return false;
}
- // Step 9.
+ // Step 12.
Value one = Int32Value(1);
auto handleOne = Handle<Value>::fromMarkedLocation(&one);
if (!DefineDataProperty(cx, fields, cx->names().day, handleOne)) {
return false;
}
- // Step 10.
+ // Step 13.
Rooted<Wrapped<PlainDateObject*>> intermediateDate(
cx, CalendarDateFromFields(cx, calendar, fields));
if (!intermediateDate) {
return false;
}
- // Steps 11-12.
+ // Steps 14-15.
Rooted<Wrapped<PlainDateObject*>> date(cx);
if (sign < 0) {
// |intermediateDate| is initialized to the first day of |yearMonth|'s
@@ -645,10 +657,10 @@ static bool AddDurationToOrSubtractDurationFromPlainYearMonth(
// some days are skipped, for example consider the Julian-to-Gregorian
// calendar transition.
- // Step 11.a.
- Duration oneMonthDuration = {0, 1};
+ // Step 14.a.
+ auto oneMonthDuration = DateDuration{0, 1};
- // Step 11.b.
+ // Step 14.b.
Rooted<Wrapped<PlainDateObject*>> nextMonth(
cx, CalendarDateAdd(cx, calendar, intermediateDate, oneMonthDuration));
if (!nextMonth) {
@@ -661,87 +673,63 @@ static bool AddDurationToOrSubtractDurationFromPlainYearMonth(
}
auto nextMonthDate = ToPlainDate(unwrappedNextMonth);
- // Step 11.c.
- PlainDate endOfMonthISO;
- if (!AddISODate(cx, nextMonthDate, {0, 0, 0, -1},
- TemporalOverflow::Constrain, &endOfMonthISO)) {
- return false;
- }
+ // Step 14.c.
+ auto endOfMonthISO = BalanceISODate(nextMonthDate.year, nextMonthDate.month,
+ nextMonthDate.day - 1);
- // Step 11.d.
+ // Step 14.d.
Rooted<PlainDateWithCalendar> endOfMonth(cx);
if (!CreateTemporalDate(cx, endOfMonthISO, calendar.receiver(),
&endOfMonth)) {
return false;
}
- // Step 11.e.
+ // Step 14.e.
Rooted<Value> day(cx);
if (!CalendarDay(cx, calendar, endOfMonth.date(), &day)) {
return false;
}
- // Step 11.f.
+ // Step 14.f.
if (!DefineDataProperty(cx, fieldsCopy, cx->names().day, day)) {
return false;
}
- // Step 11.g.
+ // Step 14.g.
date = CalendarDateFromFields(cx, calendar, fieldsCopy);
if (!date) {
return false;
}
} else {
- // Step 12.a.
+ // Step 15.a.
date = intermediateDate;
}
- // Step 13.
- Duration durationToAdd = {duration.years, duration.months, duration.weeks,
- balanceResult.days};
-
- // FIXME: spec issue - GetOptionsObject should be called after
- // ToTemporalDurationRecord to validate the input type before performing any
- // other user-visible operations.
- // https://github.com/tc39/proposal-temporal/issues/2721
-
- // Step 14.
- Rooted<JSObject*> options(cx);
- if (args.hasDefined(1)) {
- const char* name =
- operation == PlainYearMonthDuration::Add ? "add" : "subtract";
- options = RequireObjectArg(cx, "options", name, args[1]);
- } else {
- // TODO: Avoid creating an options object if not necessary.
- options = NewPlainObjectWithProto(cx, nullptr);
- }
- if (!options) {
- return false;
- }
+ // Step 16. (Moved above)
- // Step 15.
+ // Step 17.
Rooted<PlainObject*> optionsCopy(cx, SnapshotOwnProperties(cx, options));
if (!optionsCopy) {
return false;
}
- // Step 16.
+ // Step 18.
Rooted<Wrapped<PlainDateObject*>> addedDate(
cx, AddDate(cx, calendar, date, durationToAdd, options));
if (!addedDate) {
return false;
}
- // Step 17.
+ // Step 19.
Rooted<PlainObject*> addedDateFields(
cx, PrepareTemporalFields(cx, addedDate, fieldNames));
if (!addedDateFields) {
return false;
}
- // Step 18.
- auto obj =
- CalendarYearMonthFromFields(cx, calendar, addedDateFields, optionsCopy);
+ // Step 20.
+ auto obj = temporal::CalendarYearMonthFromFields(
+ cx, calendar, addedDateFields, optionsCopy);
if (!obj) {
return false;
}
@@ -1081,13 +1069,11 @@ static bool PlainYearMonth_with(JSContext* cx, const CallArgs& args) {
if (!temporalYearMonthLike) {
return false;
}
-
- // Step 4.
- if (!RejectTemporalLikeObject(cx, temporalYearMonthLike)) {
+ if (!ThrowIfTemporalLikeObject(cx, temporalYearMonthLike)) {
return false;
}
- // Step 5.
+ // Step 4.
Rooted<PlainObject*> resolvedOptions(cx);
if (args.hasDefined(1)) {
Rooted<JSObject*> options(cx,
@@ -1103,7 +1089,7 @@ static bool PlainYearMonth_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 6.
+ // Step 5.
Rooted<CalendarRecord> calendar(cx);
if (!CreateCalendarMethodsRecord(cx, calendarValue,
{
@@ -1115,7 +1101,7 @@ static bool PlainYearMonth_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 7.
+ // Step 6.
JS::RootedVector<PropertyKey> fieldNames(cx);
if (!CalendarFields(
cx, calendar,
@@ -1124,35 +1110,36 @@ static bool PlainYearMonth_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 8.
+ // Step 7.
Rooted<PlainObject*> fields(cx,
PrepareTemporalFields(cx, yearMonth, fieldNames));
if (!fields) {
return false;
}
- // Step 9.
+ // Step 8.
Rooted<PlainObject*> partialYearMonth(
cx, PreparePartialTemporalFields(cx, temporalYearMonthLike, fieldNames));
if (!partialYearMonth) {
return false;
}
- // Step 10.
+ // Step 9.
Rooted<JSObject*> mergedFields(
cx, CalendarMergeFields(cx, calendar, fields, partialYearMonth));
if (!mergedFields) {
return false;
}
- // Step 11.
+ // Step 10.
fields = PrepareTemporalFields(cx, mergedFields, fieldNames);
if (!fields) {
return false;
}
- // Step 12.
- auto obj = CalendarYearMonthFromFields(cx, calendar, fields, resolvedOptions);
+ // Step 11.
+ auto obj = temporal::CalendarYearMonthFromFields(cx, calendar, fields,
+ resolvedOptions);
if (!obj) {
return false;
}
@@ -1466,20 +1453,7 @@ static bool PlainYearMonth_toPlainDate(JSContext* cx, const CallArgs& args) {
}
// Step 12.
- Rooted<PlainObject*> options(cx, NewPlainObjectWithProto(cx, nullptr));
- if (!options) {
- return false;
- }
-
- // Step 13.
- Rooted<Value> overflow(cx, StringValue(cx->names().constrain));
- if (!DefineDataProperty(cx, options, cx->names().overflow, overflow)) {
- return false;
- }
-
- // Step 14.
- auto obj = CalendarDateFromFields(cx, calendar, mergedFromConcatenatedFields,
- options);
+ auto obj = CalendarDateFromFields(cx, calendar, mergedFromConcatenatedFields);
if (!obj) {
return false;
}
diff --git a/js/src/builtin/temporal/Temporal.cpp b/js/src/builtin/temporal/Temporal.cpp
index 3960a2832d..f729a7ce0f 100644
--- a/js/src/builtin/temporal/Temporal.cpp
+++ b/js/src/builtin/temporal/Temporal.cpp
@@ -6,8 +6,10 @@
#include "builtin/temporal/Temporal.h"
+#include "mozilla/Casting.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/Likely.h"
+#include "mozilla/MathAlgorithms.h"
#include "mozilla/Maybe.h"
#include <algorithm>
@@ -15,8 +17,10 @@
#include <cstdlib>
#include <initializer_list>
#include <iterator>
+#include <limits>
#include <stdint.h>
#include <string_view>
+#include <type_traits>
#include <utility>
#include "jsfriendapi.h"
@@ -25,6 +29,7 @@
#include "NamespaceImports.h"
#include "builtin/temporal/Instant.h"
+#include "builtin/temporal/Int128.h"
#include "builtin/temporal/PlainDate.h"
#include "builtin/temporal/PlainDateTime.h"
#include "builtin/temporal/PlainMonthDay.h"
@@ -47,7 +52,6 @@
#include "js/RootingAPI.h"
#include "js/String.h"
#include "js/Value.h"
-#include "vm/BigIntType.h"
#include "vm/BytecodeUtil.h"
#include "vm/GlobalObject.h"
#include "vm/JSAtomState.h"
@@ -137,16 +141,16 @@ static bool GetNumberOption(JSContext* cx, Handle<JSObject*> options,
bool js::temporal::ToTemporalRoundingIncrement(JSContext* cx,
Handle<JSObject*> options,
Increment* increment) {
- // Step 1.
+ // Steps 1-3.
double number = 1;
if (!GetNumberOption(cx, options, cx->names().roundingIncrement, &number)) {
return false;
}
- // Step 3. (Reordered)
+ // Step 5. (Reordered)
number = std::trunc(number);
- // Steps 2 and 4.
+ // Steps 4 and 6.
if (!std::isfinite(number) || number < 1 || number > 1'000'000'000) {
ToCStringBuf cbuf;
const char* numStr = NumberToCString(&cbuf, number);
@@ -157,6 +161,7 @@ bool js::temporal::ToTemporalRoundingIncrement(JSContext* cx,
return false;
}
+ // Step 7.
*increment = Increment{uint32_t(number)};
return true;
}
@@ -177,7 +182,7 @@ bool js::temporal::ValidateTemporalRoundingIncrement(JSContext* cx,
// Steps 3-4.
if (increment.value() > maximum || dividend % increment.value() != 0) {
Int32ToCStringBuf cbuf;
- const char* numStr = Int32ToCString(&cbuf, increment.value());
+ const char* numStr = Int32ToCString(&cbuf, int32_t(increment.value()));
// TODO: Better error message could be helpful.
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
@@ -394,7 +399,7 @@ bool js::temporal::GetTemporalUnit(JSContext* cx, Handle<JSString*> value,
bool js::temporal::ToTemporalRoundingMode(JSContext* cx,
Handle<JSObject*> options,
TemporalRoundingMode* mode) {
- // Step 1.
+ // Steps 1-2.
Rooted<JSString*> string(cx);
if (!GetStringOption(cx, options, cx->names().roundingMode, &string)) {
return false;
@@ -439,556 +444,339 @@ bool js::temporal::ToTemporalRoundingMode(JSContext* cx,
return true;
}
-static BigInt* Divide(JSContext* cx, Handle<BigInt*> dividend, int64_t divisor,
- TemporalRoundingMode roundingMode) {
- MOZ_ASSERT(divisor > 0);
-
- Rooted<BigInt*> div(cx, BigInt::createFromInt64(cx, divisor));
- if (!div) {
- return nullptr;
- }
-
- Rooted<BigInt*> quotient(cx);
- Rooted<BigInt*> remainder(cx);
- if (!BigInt::divmod(cx, dividend, div, &quotient, &remainder)) {
- return nullptr;
- }
-
- // No rounding needed when the remainder is zero.
- if (remainder->isZero()) {
- return quotient;
- }
-
- switch (roundingMode) {
- case TemporalRoundingMode::Ceil: {
- if (!remainder->isNegative()) {
- return BigInt::inc(cx, quotient);
- }
- return quotient;
- }
- case TemporalRoundingMode::Floor: {
- if (remainder->isNegative()) {
- return BigInt::dec(cx, quotient);
- }
- return quotient;
- }
- case TemporalRoundingMode::Trunc:
- // BigInt division truncates.
- return quotient;
- case TemporalRoundingMode::Expand: {
- if (!remainder->isNegative()) {
- return BigInt::inc(cx, quotient);
- }
- return BigInt::dec(cx, quotient);
- }
- case TemporalRoundingMode::HalfCeil: {
- int64_t rem;
- MOZ_ALWAYS_TRUE(BigInt::isInt64(remainder, &rem));
-
- if (!remainder->isNegative()) {
- if (uint64_t(std::abs(rem)) * 2 >= uint64_t(divisor)) {
- return BigInt::inc(cx, quotient);
- }
- } else {
- if (uint64_t(std::abs(rem)) * 2 > uint64_t(divisor)) {
- return BigInt::dec(cx, quotient);
- }
- }
- return quotient;
- }
- case TemporalRoundingMode::HalfFloor: {
- int64_t rem;
- MOZ_ALWAYS_TRUE(BigInt::isInt64(remainder, &rem));
-
- if (remainder->isNegative()) {
- if (uint64_t(std::abs(rem)) * 2 >= uint64_t(divisor)) {
- return BigInt::dec(cx, quotient);
- }
- } else {
- if (uint64_t(std::abs(rem)) * 2 > uint64_t(divisor)) {
- return BigInt::inc(cx, quotient);
- }
- }
- return quotient;
- }
- case TemporalRoundingMode::HalfExpand: {
- int64_t rem;
- MOZ_ALWAYS_TRUE(BigInt::isInt64(remainder, &rem));
-
- if (uint64_t(std::abs(rem)) * 2 >= uint64_t(divisor)) {
- if (!dividend->isNegative()) {
- return BigInt::inc(cx, quotient);
- }
- return BigInt::dec(cx, quotient);
- }
- return quotient;
- }
- case TemporalRoundingMode::HalfTrunc: {
- int64_t rem;
- MOZ_ALWAYS_TRUE(BigInt::isInt64(remainder, &rem));
-
- if (uint64_t(std::abs(rem)) * 2 > uint64_t(divisor)) {
- if (!dividend->isNegative()) {
- return BigInt::inc(cx, quotient);
- }
- return BigInt::dec(cx, quotient);
- }
- return quotient;
- }
- case TemporalRoundingMode::HalfEven: {
- int64_t rem;
- MOZ_ALWAYS_TRUE(BigInt::isInt64(remainder, &rem));
-
- if (uint64_t(std::abs(rem)) * 2 == uint64_t(divisor)) {
- bool isOdd = !quotient->isZero() && (quotient->digit(0) & 1) == 1;
- if (isOdd) {
- if (!dividend->isNegative()) {
- return BigInt::inc(cx, quotient);
- }
- return BigInt::dec(cx, quotient);
- }
- }
- if (uint64_t(std::abs(rem)) * 2 > uint64_t(divisor)) {
- if (!dividend->isNegative()) {
- return BigInt::inc(cx, quotient);
- }
- return BigInt::dec(cx, quotient);
- }
- return quotient;
- }
- }
-
- MOZ_CRASH("invalid rounding mode");
+#ifdef DEBUG
+template <typename T>
+static bool IsValidMul(const T& x, const T& y) {
+ return (mozilla::CheckedInt<T>(x) * y).isValid();
}
-static BigInt* Divide(JSContext* cx, Handle<BigInt*> dividend,
- Handle<BigInt*> divisor,
- TemporalRoundingMode roundingMode) {
- MOZ_ASSERT(!divisor->isNegative());
- MOZ_ASSERT(!divisor->isZero());
+// Copied from mozilla::CheckedInt.
+template <>
+bool IsValidMul<Int128>(const Int128& x, const Int128& y) {
+ static constexpr auto min = Int128{1} << 127;
+ static constexpr auto max = ~min;
- Rooted<BigInt*> quotient(cx);
- Rooted<BigInt*> remainder(cx);
- if (!BigInt::divmod(cx, dividend, divisor, &quotient, &remainder)) {
- return nullptr;
- }
-
- // No rounding needed when the remainder is zero.
- if (remainder->isZero()) {
- return quotient;
- }
-
- switch (roundingMode) {
- case TemporalRoundingMode::Ceil: {
- if (!remainder->isNegative()) {
- return BigInt::inc(cx, quotient);
- }
- return quotient;
- }
- case TemporalRoundingMode::Floor: {
- if (remainder->isNegative()) {
- return BigInt::dec(cx, quotient);
- }
- return quotient;
- }
- case TemporalRoundingMode::Trunc:
- // BigInt division truncates.
- return quotient;
- case TemporalRoundingMode::Expand: {
- if (!remainder->isNegative()) {
- return BigInt::inc(cx, quotient);
- }
- return BigInt::dec(cx, quotient);
- }
- case TemporalRoundingMode::HalfCeil: {
- BigInt* rem = BigInt::add(cx, remainder, remainder);
- if (!rem) {
- return nullptr;
- }
-
- if (!remainder->isNegative()) {
- if (BigInt::absoluteCompare(rem, divisor) >= 0) {
- return BigInt::inc(cx, quotient);
- }
- } else {
- if (BigInt::absoluteCompare(rem, divisor) > 0) {
- return BigInt::dec(cx, quotient);
- }
- }
- return quotient;
- }
- case TemporalRoundingMode::HalfFloor: {
- BigInt* rem = BigInt::add(cx, remainder, remainder);
- if (!rem) {
- return nullptr;
- }
-
- if (remainder->isNegative()) {
- if (BigInt::absoluteCompare(rem, divisor) >= 0) {
- return BigInt::dec(cx, quotient);
- }
- } else {
- if (BigInt::absoluteCompare(rem, divisor) > 0) {
- return BigInt::inc(cx, quotient);
- }
- }
- return quotient;
- }
- case TemporalRoundingMode::HalfExpand: {
- BigInt* rem = BigInt::add(cx, remainder, remainder);
- if (!rem) {
- return nullptr;
- }
-
- if (BigInt::absoluteCompare(rem, divisor) >= 0) {
- if (!dividend->isNegative()) {
- return BigInt::inc(cx, quotient);
- }
- return BigInt::dec(cx, quotient);
- }
- return quotient;
- }
- case TemporalRoundingMode::HalfTrunc: {
- BigInt* rem = BigInt::add(cx, remainder, remainder);
- if (!rem) {
- return nullptr;
- }
-
- if (BigInt::absoluteCompare(rem, divisor) > 0) {
- if (!dividend->isNegative()) {
- return BigInt::inc(cx, quotient);
- }
- return BigInt::dec(cx, quotient);
- }
- return quotient;
- }
- case TemporalRoundingMode::HalfEven: {
- BigInt* rem = BigInt::add(cx, remainder, remainder);
- if (!rem) {
- return nullptr;
- }
-
- if (BigInt::absoluteCompare(rem, divisor) == 0) {
- bool isOdd = !quotient->isZero() && (quotient->digit(0) & 1) == 1;
- if (isOdd) {
- if (!dividend->isNegative()) {
- return BigInt::inc(cx, quotient);
- }
- return BigInt::dec(cx, quotient);
- }
- }
- if (BigInt::absoluteCompare(rem, divisor) > 0) {
- if (!dividend->isNegative()) {
- return BigInt::inc(cx, quotient);
- }
- return BigInt::dec(cx, quotient);
- }
- return quotient;
- }
- }
-
- MOZ_CRASH("invalid rounding mode");
-}
-
-static BigInt* RoundNumberToIncrementSlow(JSContext* cx, Handle<BigInt*> x,
- int64_t divisor, int64_t increment,
- TemporalRoundingMode roundingMode) {
- // Steps 1-8.
- Rooted<BigInt*> rounded(cx, Divide(cx, x, divisor, roundingMode));
- if (!rounded) {
- return nullptr;
- }
-
- // We can skip the next step when |increment=1|.
- if (increment == 1) {
- return rounded;
+ if (x == Int128{0} || y == Int128{0}) {
+ return true;
}
-
- // Step 9.
- Rooted<BigInt*> inc(cx, BigInt::createFromInt64(cx, increment));
- if (!inc) {
- return nullptr;
+ if (x > Int128{0}) {
+ return y > Int128{0} ? x <= max / y : y >= min / x;
}
- return BigInt::mul(cx, rounded, inc);
-}
-
-static BigInt* RoundNumberToIncrementSlow(JSContext* cx, Handle<BigInt*> x,
- int64_t increment,
- TemporalRoundingMode roundingMode) {
- return RoundNumberToIncrementSlow(cx, x, increment, increment, roundingMode);
+ return y > Int128{0} ? x >= min / y : y >= max / x;
}
+#endif
/**
* RoundNumberToIncrement ( x, increment, roundingMode )
*/
-bool js::temporal::RoundNumberToIncrement(JSContext* cx, const Instant& x,
- int64_t increment,
- TemporalRoundingMode roundingMode,
- Instant* result) {
- MOZ_ASSERT(temporal::IsValidEpochInstant(x));
- MOZ_ASSERT(increment > 0);
- MOZ_ASSERT(increment <= ToNanoseconds(TemporalUnit::Day));
-
- // Fast path for the default case.
- if (increment == 1) {
- *result = x;
- return true;
- }
+Int128 js::temporal::RoundNumberToIncrement(int64_t numerator,
+ int64_t denominator,
+ Increment increment,
+ TemporalRoundingMode roundingMode) {
+ MOZ_ASSERT(denominator > 0);
+ MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max());
// Dividing zero is always zero.
- if (x == Instant{}) {
- *result = x;
- return true;
+ if (numerator == 0) {
+ return Int128{0};
+ }
+
+ // We don't have to adjust the divisor when |increment=1|.
+ if (increment == Increment{1}) {
+ // Steps 1-8 and implicit step 9.
+ return Int128{Divide(numerator, denominator, roundingMode)};
}
// Fast-path when we can perform the whole computation with int64 values.
- if (auto num = x.toNanoseconds(); MOZ_LIKELY(num.isValid())) {
+ auto divisor = mozilla::CheckedInt64(denominator) * increment.value();
+ if (MOZ_LIKELY(divisor.isValid())) {
+ MOZ_ASSERT(divisor.value() > 0);
+
// Steps 1-8.
- int64_t rounded = Divide(num.value(), increment, roundingMode);
+ int64_t rounded = Divide(numerator, divisor.value(), roundingMode);
// Step 9.
- mozilla::CheckedInt64 checked = rounded;
- checked *= increment;
- if (MOZ_LIKELY(checked.isValid())) {
- *result = Instant::fromNanoseconds(checked.value());
- return true;
+ auto result = mozilla::CheckedInt64(rounded) * increment.value();
+ if (MOZ_LIKELY(result.isValid())) {
+ return Int128{result.value()};
}
}
- Rooted<BigInt*> bi(cx, ToEpochNanoseconds(cx, x));
- if (!bi) {
- return false;
- }
-
- auto* rounded = RoundNumberToIncrementSlow(cx, bi, increment, roundingMode);
- if (!rounded) {
- return false;
- }
-
- *result = ToInstant(rounded);
- return true;
+ // Int128 path on overflow.
+ return RoundNumberToIncrement(Int128{numerator}, Int128{denominator},
+ increment, roundingMode);
}
/**
* RoundNumberToIncrement ( x, increment, roundingMode )
*/
-bool js::temporal::RoundNumberToIncrement(JSContext* cx, int64_t numerator,
- TemporalUnit unit,
- Increment increment,
- TemporalRoundingMode roundingMode,
- double* result) {
- MOZ_ASSERT(unit >= TemporalUnit::Day);
+Int128 js::temporal::RoundNumberToIncrement(const Int128& numerator,
+ const Int128& denominator,
+ Increment increment,
+ TemporalRoundingMode roundingMode) {
+ MOZ_ASSERT(denominator > Int128{0});
MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max());
- // Take the slow path when the increment is too large.
- if (MOZ_UNLIKELY(increment > Increment{100'000})) {
- Rooted<BigInt*> bi(cx, BigInt::createFromInt64(cx, numerator));
- if (!bi) {
- return false;
- }
+ auto inc = Int128{increment.value()};
+ MOZ_ASSERT(IsValidMul(denominator, inc), "unsupported overflow");
- Rooted<BigInt*> denominator(
- cx, BigInt::createFromInt64(cx, ToNanoseconds(unit)));
- if (!denominator) {
- return false;
- }
-
- return RoundNumberToIncrement(cx, bi, denominator, increment, roundingMode,
- result);
- }
-
- int64_t divisor = ToNanoseconds(unit) * increment.value();
- MOZ_ASSERT(divisor > 0);
- MOZ_ASSERT(divisor <= 8'640'000'000'000'000'000);
-
- // Division by one has no remainder.
- if (divisor == 1) {
- MOZ_ASSERT(increment == Increment{1});
- *result = double(numerator);
- return true;
- }
+ auto divisor = denominator * inc;
+ MOZ_ASSERT(divisor > Int128{0});
// Steps 1-8.
- int64_t rounded = Divide(numerator, divisor, roundingMode);
+ auto rounded = Divide(numerator, divisor, roundingMode);
// Step 9.
- mozilla::CheckedInt64 checked = rounded;
- checked *= increment.value();
- if (checked.isValid()) {
- *result = double(checked.value());
- return true;
- }
-
- Rooted<BigInt*> bi(cx, BigInt::createFromInt64(cx, numerator));
- if (!bi) {
- return false;
- }
- return RoundNumberToIncrement(cx, bi, unit, increment, roundingMode, result);
+ MOZ_ASSERT(IsValidMul(rounded, inc), "unsupported overflow");
+ return rounded * inc;
}
/**
* RoundNumberToIncrement ( x, increment, roundingMode )
*/
-bool js::temporal::RoundNumberToIncrement(
- JSContext* cx, Handle<BigInt*> numerator, TemporalUnit unit,
- Increment increment, TemporalRoundingMode roundingMode, double* result) {
- MOZ_ASSERT(unit >= TemporalUnit::Day);
- MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max());
+Int128 js::temporal::RoundNumberToIncrement(const Int128& x,
+ const Int128& increment,
+ TemporalRoundingMode roundingMode) {
+ MOZ_ASSERT(increment > Int128{0});
- // Take the slow path when the increment is too large.
- if (MOZ_UNLIKELY(increment > Increment{100'000})) {
- Rooted<BigInt*> denominator(
- cx, BigInt::createFromInt64(cx, ToNanoseconds(unit)));
- if (!denominator) {
- return false;
- }
-
- return RoundNumberToIncrement(cx, numerator, denominator, increment,
- roundingMode, result);
- }
-
- int64_t divisor = ToNanoseconds(unit) * increment.value();
- MOZ_ASSERT(divisor > 0);
- MOZ_ASSERT(divisor <= 8'640'000'000'000'000'000);
-
- // Division by one has no remainder.
- if (divisor == 1) {
- MOZ_ASSERT(increment == Increment{1});
- *result = BigInt::numberValue(numerator);
- return true;
- }
-
- // Dividing zero is always zero.
- if (numerator->isZero()) {
- *result = 0;
- return true;
- }
-
- // All callers are already in the slow path, so we don't need to fast-path the
- // case when |x| can be represented by an int64 value.
+ // Steps 1-8.
+ auto rounded = Divide(x, increment, roundingMode);
- // Steps 1-9.
- auto* rounded = RoundNumberToIncrementSlow(cx, numerator, divisor,
- increment.value(), roundingMode);
- if (!rounded) {
- return false;
- }
+ // Step 9.
+ MOZ_ASSERT(IsValidMul(rounded, increment), "unsupported overflow");
+ return rounded * increment;
+}
- *result = BigInt::numberValue(rounded);
- return true;
+template <typename IntT>
+static inline constexpr bool IsSafeInteger(const IntT& x) {
+ constexpr IntT MaxSafeInteger = IntT{int64_t(1) << 53};
+ constexpr IntT MinSafeInteger = -MaxSafeInteger;
+ return MinSafeInteger < x && x < MaxSafeInteger;
}
/**
- * RoundNumberToIncrement ( x, increment, roundingMode )
+ * Return the real number value of the fraction |numerator / denominator|.
+ *
+ * As an optimization we multiply the remainder by 16 when computing the number
+ * of digits after the decimal point, i.e. we compute four instead of one bit of
+ * the fractional digits. The denominator is therefore required to not exceed
+ * 2**(N - log2(16)), where N is the number of non-sign bits in the mantissa.
*/
-bool js::temporal::RoundNumberToIncrement(JSContext* cx, int64_t numerator,
- int64_t denominator,
- Increment increment,
- TemporalRoundingMode roundingMode,
- double* result) {
- MOZ_ASSERT(denominator > 0);
- MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max());
-
- // Dividing zero is always zero.
- if (numerator == 0) {
- *result = 0;
- return true;
- }
-
- // We don't have to adjust the divisor when |increment=1|.
- if (increment == Increment{1}) {
- int64_t divisor = denominator;
- int64_t rounded = Divide(numerator, divisor, roundingMode);
-
- *result = double(rounded);
- return true;
- }
-
- auto divisor = mozilla::CheckedInt64(denominator) * increment.value();
- if (MOZ_LIKELY(divisor.isValid())) {
- MOZ_ASSERT(divisor.value() > 0);
-
- // Steps 1-8.
- int64_t rounded = Divide(numerator, divisor.value(), roundingMode);
-
- // Step 9.
- auto adjusted = mozilla::CheckedInt64(rounded) * increment.value();
- if (MOZ_LIKELY(adjusted.isValid())) {
- *result = double(adjusted.value());
- return true;
+template <typename T>
+static double FractionToDoubleSlow(const T& numerator, const T& denominator) {
+ MOZ_ASSERT(denominator > T{0}, "expected positive denominator");
+ MOZ_ASSERT(denominator <= (T{1} << (std::numeric_limits<T>::digits - 4)),
+ "denominator too large");
+
+ auto absValue = [](const T& value) {
+ if constexpr (std::is_same_v<T, Int128>) {
+ return value.abs();
+ } else {
+ // NB: Not std::abs, because std::abs(INT64_MIN) is undefined behavior.
+ return mozilla::Abs(value);
}
- }
+ };
- // Slow path on overflow.
+ using UnsignedT = decltype(absValue(T{0}));
+ static_assert(!std::numeric_limits<UnsignedT>::is_signed);
- Rooted<BigInt*> bi(cx, BigInt::createFromInt64(cx, numerator));
- if (!bi) {
- return false;
- }
+ auto divrem = [](const UnsignedT& x, const UnsignedT& y) {
+ if constexpr (std::is_same_v<T, Int128>) {
+ return x.divrem(y);
+ } else {
+ return std::pair{x / y, x % y};
+ }
+ };
- Rooted<BigInt*> denom(cx, BigInt::createFromInt64(cx, denominator));
- if (!denom) {
- return false;
+ auto [quot, rem] =
+ divrem(absValue(numerator), static_cast<UnsignedT>(denominator));
+
+ // Simple case when no remainder is present.
+ if (rem == UnsignedT{0}) {
+ double sign = numerator < T{0} ? -1 : 1;
+ return sign * double(quot);
+ }
+
+ using Double = mozilla::FloatingPoint<double>;
+
+ // Significand including the implicit one of IEEE-754 floating point numbers.
+ static constexpr uint32_t SignificandWidthWithImplicitOne =
+ Double::kSignificandWidth + 1;
+
+ // Number of leading zeros for a correctly adjusted significand.
+ static constexpr uint32_t SignificandLeadingZeros =
+ 64 - SignificandWidthWithImplicitOne;
+
+ // Exponent bias for an integral significand. (`Double::kExponentBias` is the
+ // bias for the binary fraction `1.xyz * 2**exp`. For an integral significand
+ // the significand width has to be added to the bias.)
+ static constexpr int32_t ExponentBias =
+ Double::kExponentBias + Double::kSignificandWidth;
+
+ // Significand, possibly unnormalized.
+ uint64_t significand = 0;
+
+ // Significand ignored msd bits.
+ uint32_t ignoredBits = 0;
+
+ // Read quotient, from most to least significant digit. Stop when the
+ // significand got too large for double precision.
+ int32_t shift = std::numeric_limits<UnsignedT>::digits;
+ for (; shift != 0 && ignoredBits == 0; shift -= 4) {
+ uint64_t digit = uint64_t(quot >> (shift - 4)) & 0xf;
+
+ significand = significand * 16 + digit;
+ ignoredBits = significand >> SignificandWidthWithImplicitOne;
+ }
+
+ // Read remainder, from most to least significant digit. Stop when the
+ // remainder is zero or the significand got too large.
+ int32_t fractionDigit = 0;
+ for (; rem != UnsignedT{0} && ignoredBits == 0; fractionDigit++) {
+ auto [digit, next] =
+ divrem(rem * UnsignedT{16}, static_cast<UnsignedT>(denominator));
+ rem = next;
+
+ significand = significand * 16 + uint64_t(digit);
+ ignoredBits = significand >> SignificandWidthWithImplicitOne;
+ }
+
+ // Unbiased exponent. (`shift` remaining bits in the quotient, minus the
+ // fractional digits.)
+ int32_t exponent = shift - (fractionDigit * 4);
+
+ // Significand got too large and some bits are now ignored. Adjust the
+ // significand and exponent.
+ if (ignoredBits != 0) {
+ // significand
+ // ___________|__________
+ // / \
+ // [xxx················yyy|
+ // \_/ \_/
+ // | |
+ // ignoredBits extraBits
+ //
+ // `ignoredBits` have to be shifted back into the 53 bits of the significand
+ // and `extraBits` has to be checked if the result has to be rounded up.
+
+ // Number of ignored/extra bits in the significand.
+ uint32_t extraBitsCount = 32 - mozilla::CountLeadingZeroes32(ignoredBits);
+ MOZ_ASSERT(extraBitsCount > 0);
+
+ // Extra bits in the significand.
+ uint32_t extraBits = uint32_t(significand) & ((1 << extraBitsCount) - 1);
+
+ // Move the ignored bits into the proper significand position and adjust the
+ // exponent to reflect the now moved out extra bits.
+ significand >>= extraBitsCount;
+ exponent += extraBitsCount;
+
+ MOZ_ASSERT((significand >> SignificandWidthWithImplicitOne) == 0,
+ "no excess bits in the significand");
+
+ // When the most significant digit in the extra bits is set, we may need to
+ // round the result.
+ uint32_t msdExtraBit = extraBits >> (extraBitsCount - 1);
+ if (msdExtraBit != 0) {
+ // Extra bits, excluding the most significant digit.
+ uint32_t extraBitExcludingMsdMask = (1 << (extraBitsCount - 1)) - 1;
+
+ // Unprocessed bits in the quotient.
+ auto bitsBelowExtraBits = quot & ((UnsignedT{1} << shift) - UnsignedT{1});
+
+ // Round up if the extra bit's msd is set and either the significand is
+ // odd or any other bits below the extra bit's msd are non-zero.
+ //
+ // Bits below the extra bit's msd are:
+ // 1. The remaining bits of the extra bits.
+ // 2. Any bits below the extra bits.
+ // 3. Any rest of the remainder.
+ bool shouldRoundUp = (significand & 1) != 0 ||
+ (extraBits & extraBitExcludingMsdMask) != 0 ||
+ bitsBelowExtraBits != UnsignedT{0} ||
+ rem != UnsignedT{0};
+ if (shouldRoundUp) {
+ // Add one to the significand bits.
+ significand += 1;
+
+ // If they overflow, the exponent must also be increased.
+ if ((significand >> SignificandWidthWithImplicitOne) != 0) {
+ exponent++;
+ significand >>= 1;
+ }
+ }
+ }
}
- return RoundNumberToIncrement(cx, bi, denom, increment, roundingMode, result);
+ MOZ_ASSERT(significand > 0, "significand is non-zero");
+ MOZ_ASSERT((significand >> SignificandWidthWithImplicitOne) == 0,
+ "no excess bits in the significand");
+
+ // Move the significand into the correct position and adjust the exponent
+ // accordingly.
+ uint32_t significandZeros = mozilla::CountLeadingZeroes64(significand);
+ if (significandZeros < SignificandLeadingZeros) {
+ uint32_t shift = SignificandLeadingZeros - significandZeros;
+ significand >>= shift;
+ exponent += shift;
+ } else if (significandZeros > SignificandLeadingZeros) {
+ uint32_t shift = significandZeros - SignificandLeadingZeros;
+ significand <<= shift;
+ exponent -= shift;
+ }
+
+ // Combine the individual bits of the double value and return it.
+ uint64_t signBit = uint64_t(numerator < T{0} ? 1 : 0)
+ << (Double::kExponentWidth + Double::kSignificandWidth);
+ uint64_t exponentBits = static_cast<uint64_t>(exponent + ExponentBias)
+ << Double::kExponentShift;
+ uint64_t significandBits = significand & Double::kSignificandBits;
+ return mozilla::BitwiseCast<double>(signBit | exponentBits | significandBits);
}
-/**
- * RoundNumberToIncrement ( x, increment, roundingMode )
- */
-bool js::temporal::RoundNumberToIncrement(
- JSContext* cx, Handle<BigInt*> numerator, Handle<BigInt*> denominator,
- Increment increment, TemporalRoundingMode roundingMode, double* result) {
- MOZ_ASSERT(!denominator->isNegative());
- MOZ_ASSERT(!denominator->isZero());
- MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max());
+double js::temporal::FractionToDouble(int64_t numerator, int64_t denominator) {
+ MOZ_ASSERT(denominator > 0);
- // Dividing zero is always zero.
- if (numerator->isZero()) {
- *result = 0;
- return true;
+ // Zero divided by any divisor is still zero.
+ if (numerator == 0) {
+ return 0;
}
- // We don't have to adjust the divisor when |increment=1|.
- if (increment == Increment{1}) {
- auto divisor = denominator;
-
- auto* rounded = Divide(cx, numerator, divisor, roundingMode);
- if (!rounded) {
- return false;
- }
-
- *result = BigInt::numberValue(rounded);
- return true;
+ // When both values can be represented as doubles, use double division to
+ // compute the exact result. The result is exact, because double division is
+ // guaranteed to return the exact result.
+ if (MOZ_LIKELY(::IsSafeInteger(numerator) && ::IsSafeInteger(denominator))) {
+ return double(numerator) / double(denominator);
}
- Rooted<BigInt*> inc(cx, BigInt::createFromUint64(cx, increment.value()));
- if (!inc) {
- return false;
+ // Otherwise call into |FractionToDoubleSlow| to compute the exact result.
+ if (denominator <=
+ (int64_t(1) << (std::numeric_limits<int64_t>::digits - 4))) {
+ // Slightly faster, but still slow approach when |denominator| is small
+ // enough to allow computing on int64 values.
+ return FractionToDoubleSlow(numerator, denominator);
}
+ return FractionToDoubleSlow(Int128{numerator}, Int128{denominator});
+}
- Rooted<BigInt*> divisor(cx, BigInt::mul(cx, denominator, inc));
- if (!divisor) {
- return false;
- }
- MOZ_ASSERT(!divisor->isNegative());
- MOZ_ASSERT(!divisor->isZero());
+double js::temporal::FractionToDouble(const Int128& numerator,
+ const Int128& denominator) {
+ MOZ_ASSERT(denominator > Int128{0});
- // Steps 1-8.
- Rooted<BigInt*> rounded(cx, Divide(cx, numerator, divisor, roundingMode));
- if (!rounded) {
- return false;
+ // Zero divided by any divisor is still zero.
+ if (numerator == Int128{0}) {
+ return 0;
}
- // Step 9.
- auto* adjusted = BigInt::mul(cx, rounded, inc);
- if (!adjusted) {
- return false;
+ // When both values can be represented as doubles, use double division to
+ // compute the exact result. The result is exact, because double division is
+ // guaranteed to return the exact result.
+ if (MOZ_LIKELY(::IsSafeInteger(numerator) && ::IsSafeInteger(denominator))) {
+ return double(numerator) / double(denominator);
}
- *result = BigInt::numberValue(adjusted);
- return true;
+ // Otherwise call into |FractionToDoubleSlow| to compute the exact result.
+ return FractionToDoubleSlow(numerator, denominator);
}
/**
@@ -1404,17 +1192,14 @@ static JSObject* MaybeUnwrapIf(JSObject* object) {
return nullptr;
}
-// FIXME: spec issue - "Reject" is exclusively used for Promise rejection. The
-// existing `RejectPromise` abstract operation unconditionally rejects, whereas
-// this operation conditionally rejects.
-// https://github.com/tc39/proposal-temporal/issues/2534
-
/**
- * RejectTemporalLikeObject ( object )
+ * IsPartialTemporalObject ( object )
*/
-bool js::temporal::RejectTemporalLikeObject(JSContext* cx,
- Handle<JSObject*> object) {
- // Step 1.
+bool js::temporal::ThrowIfTemporalLikeObject(JSContext* cx,
+ Handle<JSObject*> object) {
+ // Step 1. (Handled in caller)
+
+ // Step 2.
if (auto* unwrapped =
MaybeUnwrapIf<PlainDateObject, PlainDateTimeObject,
PlainMonthDayObject, PlainTimeObject,
@@ -1427,31 +1212,31 @@ bool js::temporal::RejectTemporalLikeObject(JSContext* cx,
Rooted<Value> property(cx);
- // Step 2.
+ // Step 3.
if (!GetProperty(cx, object, object, cx->names().calendar, &property)) {
return false;
}
- // Step 3.
+ // Step 4.
if (!property.isUndefined()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_UNEXPECTED_PROPERTY, "calendar");
return false;
}
- // Step 4.
+ // Step 5.
if (!GetProperty(cx, object, object, cx->names().timeZone, &property)) {
return false;
}
- // Step 5.
+ // Step 6.
if (!property.isUndefined()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_UNEXPECTED_PROPERTY, "timeZone");
return false;
}
- // Step 6.
+ // Step 7.
return true;
}
@@ -1813,10 +1598,18 @@ static bool TemporalClassFinish(JSContext* cx, Handle<JSObject*> temporal,
};
// Add the constructor properties.
- for (const auto& protoKey :
- {JSProto_Calendar, JSProto_Duration, JSProto_Instant, JSProto_PlainDate,
- JSProto_PlainDateTime, JSProto_PlainMonthDay, JSProto_PlainTime,
- JSProto_PlainYearMonth, JSProto_TimeZone, JSProto_ZonedDateTime}) {
+ for (const auto& protoKey : {
+ JSProto_Calendar,
+ JSProto_Duration,
+ JSProto_Instant,
+ JSProto_PlainDate,
+ JSProto_PlainDateTime,
+ JSProto_PlainMonthDay,
+ JSProto_PlainTime,
+ JSProto_PlainYearMonth,
+ JSProto_TimeZone,
+ JSProto_ZonedDateTime,
+ }) {
if (!defineProperty(protoKey, ClassName(protoKey, cx))) {
return false;
}
diff --git a/js/src/builtin/temporal/Temporal.h b/js/src/builtin/temporal/Temporal.h
index 3d014bfaa7..8955c1d88a 100644
--- a/js/src/builtin/temporal/Temporal.h
+++ b/js/src/builtin/temporal/Temporal.h
@@ -13,6 +13,7 @@
#include "jstypes.h"
+#include "builtin/temporal/Int128.h"
#include "builtin/temporal/TemporalRoundingMode.h"
#include "builtin/temporal/TemporalUnit.h"
#include "js/RootingAPI.h"
@@ -35,9 +36,6 @@ class TemporalObject : public NativeObject {
static const ClassSpec classSpec_;
};
-struct Instant;
-struct PlainTime;
-
/**
* Rounding increment, which is an integer in the range [1, 1'000'000'000].
*
@@ -171,37 +169,32 @@ bool ToTemporalRoundingMode(JSContext* cx, JS::Handle<JSObject*> options,
/**
* RoundNumberToIncrement ( x, increment, roundingMode )
*/
-bool RoundNumberToIncrement(JSContext* cx, const Instant& x, int64_t increment,
- TemporalRoundingMode roundingMode, Instant* result);
+Int128 RoundNumberToIncrement(int64_t numerator, int64_t denominator,
+ Increment increment,
+ TemporalRoundingMode roundingMode);
/**
* RoundNumberToIncrement ( x, increment, roundingMode )
*/
-bool RoundNumberToIncrement(JSContext* cx, int64_t numerator, TemporalUnit unit,
- Increment increment,
- TemporalRoundingMode roundingMode, double* result);
+Int128 RoundNumberToIncrement(const Int128& numerator,
+ const Int128& denominator, Increment increment,
+ TemporalRoundingMode roundingMode);
/**
* RoundNumberToIncrement ( x, increment, roundingMode )
*/
-bool RoundNumberToIncrement(JSContext* cx, JS::Handle<JS::BigInt*> numerator,
- TemporalUnit unit, Increment increment,
- TemporalRoundingMode roundingMode, double* result);
+Int128 RoundNumberToIncrement(const Int128& x, const Int128& increment,
+ TemporalRoundingMode roundingMode);
/**
- * RoundNumberToIncrement ( x, increment, roundingMode )
+ * Return the double value of the fractional number `numerator / denominator`.
*/
-bool RoundNumberToIncrement(JSContext* cx, int64_t numerator,
- int64_t denominator, Increment increment,
- TemporalRoundingMode roundingMode, double* result);
+double FractionToDouble(int64_t numerator, int64_t denominator);
/**
- * RoundNumberToIncrement ( x, increment, roundingMode )
+ * Return the double value of the fractional number `numerator / denominator`.
*/
-bool RoundNumberToIncrement(JSContext* cx, JS::Handle<JS::BigInt*> numerator,
- JS::Handle<JS::BigInt*> denominator,
- Increment increment,
- TemporalRoundingMode roundingMode, double* result);
+double FractionToDouble(const Int128& numerator, const Int128& denominator);
enum class CalendarOption { Auto, Always, Never, Critical };
@@ -221,7 +214,7 @@ class Precision final {
constexpr Precision(int8_t value, Tag) : value_(value) {}
public:
- constexpr explicit Precision(uint8_t value) : value_(value) {
+ constexpr explicit Precision(uint8_t value) : value_(int8_t(value)) {
MOZ_ASSERT(value < 10);
}
@@ -306,9 +299,12 @@ bool ToShowOffsetOption(JSContext* cx, JS::Handle<JSObject*> options,
ShowOffsetOption* result);
/**
- * RejectTemporalLikeObject ( object )
+ * IsPartialTemporalObject ( object )
+ *
+ * Our implementation performs error reporting in this function instead of in
+ * the caller to provide better error messages.
*/
-bool RejectTemporalLikeObject(JSContext* cx, JS::Handle<JSObject*> object);
+bool ThrowIfTemporalLikeObject(JSContext* cx, JS::Handle<JSObject*> object);
/**
* ToPositiveIntegerWithTruncation ( argument )
diff --git a/js/src/builtin/temporal/TemporalFields.cpp b/js/src/builtin/temporal/TemporalFields.cpp
index 9ac2e44639..72027123e7 100644
--- a/js/src/builtin/temporal/TemporalFields.cpp
+++ b/js/src/builtin/temporal/TemporalFields.cpp
@@ -214,7 +214,7 @@ static Value TemporalFieldDefaultValue(TemporalField field) {
static bool TemporalFieldConvertValue(JSContext* cx, TemporalField field,
MutableHandle<Value> value) {
- auto* name = ToCString(field);
+ const auto* name = ToCString(field);
switch (field) {
case TemporalField::Year:
case TemporalField::Hour:
@@ -365,7 +365,7 @@ bool js::temporal::PrepareTemporalFields(
Rooted<Value> value(cx);
for (auto fieldName : fieldNames) {
auto* property = ToPropertyName(cx, fieldName);
- auto* cstr = ToCString(fieldName);
+ const auto* cstr = ToCString(fieldName);
// Step 6.a. (Not applicable in our implementation.)
@@ -837,8 +837,8 @@ bool js::temporal::AppendSorted(
return false;
}
- auto* left = std::prev(fieldNames.end(), additionalNames.size());
- auto* right = additionalNames.end();
+ const auto* left = std::prev(fieldNames.end(), additionalNames.size());
+ const auto* right = additionalNames.end();
auto* out = fieldNames.end();
// Write backwards into the newly allocated space.
diff --git a/js/src/builtin/temporal/TemporalFields.h b/js/src/builtin/temporal/TemporalFields.h
index 1af3631169..4cdbade772 100644
--- a/js/src/builtin/temporal/TemporalFields.h
+++ b/js/src/builtin/temporal/TemporalFields.h
@@ -7,6 +7,7 @@
#ifndef builtin_temporal_TemporalFields_h
#define builtin_temporal_TemporalFields_h
+#include "mozilla/Attributes.h"
#include "mozilla/FloatingPoint.h"
#include <initializer_list>
@@ -46,7 +47,7 @@ enum class TemporalField {
// NaN whereas pointer fields use nullptr.
//
// [1] <https://tc39.es/proposal-temporal/#table-temporal-field-requirements>
-struct TemporalFields final {
+struct MOZ_STACK_CLASS TemporalFields final {
double year = mozilla::UnspecifiedNaN<double>();
double month = mozilla::UnspecifiedNaN<double>();
JSString* monthCode = nullptr;
diff --git a/js/src/builtin/temporal/TemporalNow.cpp b/js/src/builtin/temporal/TemporalNow.cpp
index adc84361af..3b06736673 100644
--- a/js/src/builtin/temporal/TemporalNow.cpp
+++ b/js/src/builtin/temporal/TemporalNow.cpp
@@ -116,9 +116,9 @@ static JSString* SystemTimeZoneIdentifier(JSContext* cx) {
size_t n = etcGMT.copy(offsetString, etcGMT.length());
offsetString[n++] = offset < 0 ? '+' : '-';
if (offsetHours >= 10) {
- offsetString[n++] = '0' + (offsetHours / 10);
+ offsetString[n++] = char('0' + (offsetHours / 10));
}
- offsetString[n++] = '0' + (offsetHours % 10);
+ offsetString[n++] = char('0' + (offsetHours % 10));
MOZ_ASSERT(n == etcGMT.length() + 2 || n == etcGMT.length() + 3);
diff --git a/js/src/builtin/temporal/TemporalParser.cpp b/js/src/builtin/temporal/TemporalParser.cpp
index c117e63b31..b3a7a58c62 100644
--- a/js/src/builtin/temporal/TemporalParser.cpp
+++ b/js/src/builtin/temporal/TemporalParser.cpp
@@ -80,11 +80,11 @@ bool EqualCharIgnoreCaseAscii(CharT c1, char c2) {
// Convert both characters to lower case before the comparison.
char c = c1;
if (mozilla::IsAsciiUppercaseAlpha(c1)) {
- c = c + toLower;
+ c = char(c + toLower);
}
char d = c2;
if (mozilla::IsAsciiUppercaseAlpha(c2)) {
- d = d + toLower;
+ d = char(d + toLower);
}
return c == d;
}
@@ -544,10 +544,10 @@ class TemporalParser final {
return mozilla::Some(num);
}
- // TimeFractionalPart :
+ // TimeFractionalPart :::
// Digit{1, 9}
//
- // Fraction :
+ // Fraction :::
// DecimalSeparator TimeFractionalPart
mozilla::Maybe<int32_t> fraction() {
if (!reader_.hasMore(2)) {
@@ -679,11 +679,11 @@ class TemporalParser final {
return true;
}
- // Sign :
+ // Sign :::
// ASCIISign
// U+2212
//
- // ASCIISign : one of
+ // ASCIISign ::: one of
// + -
bool hasSign() const { return hasOneOf({'+', '-', 0x2212}); }
@@ -698,61 +698,61 @@ class TemporalParser final {
return plus ? 1 : -1;
}
- // DecimalSeparator : one of
+ // DecimalSeparator ::: one of
// . ,
bool hasDecimalSeparator() const { return hasOneOf({'.', ','}); }
bool decimalSeparator() { return oneOf({'.', ','}); }
- // DaysDesignator : one of
+ // DaysDesignator ::: one of
// D d
bool daysDesignator() { return oneOf({'D', 'd'}); }
- // HoursDesignator : one of
+ // HoursDesignator ::: one of
// H h
bool hoursDesignator() { return oneOf({'H', 'h'}); }
- // MinutesDesignator : one of
+ // MinutesDesignator ::: one of
// M m
bool minutesDesignator() { return oneOf({'M', 'm'}); }
- // MonthsDesignator : one of
+ // MonthsDesignator ::: one of
// M m
bool monthsDesignator() { return oneOf({'M', 'm'}); }
- // DurationDesignator : one of
+ // DurationDesignator ::: one of
// P p
bool durationDesignator() { return oneOf({'P', 'p'}); }
- // SecondsDesignator : one of
+ // SecondsDesignator ::: one of
// S s
bool secondsDesignator() { return oneOf({'S', 's'}); }
- // DateTimeSeparator :
+ // DateTimeSeparator :::
// <SP>
// T
// t
bool dateTimeSeparator() { return oneOf({' ', 'T', 't'}); }
- // TimeDesignator : one of
+ // TimeDesignator ::: one of
// T t
bool hasTimeDesignator() const { return hasOneOf({'T', 't'}); }
bool timeDesignator() { return oneOf({'T', 't'}); }
- // WeeksDesignator : one of
+ // WeeksDesignator ::: one of
// W w
bool weeksDesignator() { return oneOf({'W', 'w'}); }
- // YearsDesignator : one of
+ // YearsDesignator ::: one of
// Y y
bool yearsDesignator() { return oneOf({'Y', 'y'}); }
- // UTCDesignator : one of
+ // UTCDesignator ::: one of
// Z z
bool utcDesignator() { return oneOf({'Z', 'z'}); }
- // TZLeadingChar :
+ // TZLeadingChar :::
// Alpha
// .
// _
@@ -762,7 +762,7 @@ class TemporalParser final {
});
}
- // TZChar :
+ // TZChar :::
// TZLeadingChar
// DecimalDigit
// -
@@ -774,11 +774,11 @@ class TemporalParser final {
});
}
- // AnnotationCriticalFlag :
+ // AnnotationCriticalFlag :::
// !
bool annotationCriticalFlag() { return character('!'); }
- // AKeyLeadingChar :
+ // AKeyLeadingChar :::
// LowercaseAlpha
// _
bool aKeyLeadingChar() {
@@ -787,7 +787,7 @@ class TemporalParser final {
});
}
- // AKeyChar :
+ // AKeyChar :::
// AKeyLeadingChar
// DecimalDigit
// -
@@ -798,7 +798,7 @@ class TemporalParser final {
});
}
- // AnnotationValueComponent :
+ // AnnotationValueComponent :::
// Alpha AnnotationValueComponent?
// DecimalDigit AnnotationValueComponent?
bool annotationValueComponent() {
@@ -929,7 +929,7 @@ class TemporalParser final {
template <typename CharT>
mozilla::Result<ZonedDateTimeString, ParserError>
TemporalParser<CharT>::dateTime() {
- // DateTime :
+ // DateTime :::
// Date
// Date DateTimeSeparator TimeSpec DateTimeUTCOffset?
ZonedDateTimeString result = {};
@@ -961,12 +961,12 @@ TemporalParser<CharT>::dateTime() {
template <typename CharT>
mozilla::Result<PlainDate, ParserError> TemporalParser<CharT>::date() {
- // Date :
+ // Date :::
// DateYear - DateMonth - DateDay
// DateYear DateMonth DateDay
PlainDate result = {};
- // DateYear :
+ // DateYear :::
// DecimalDigit{4}
// Sign DecimalDigit{6}
if (auto year = digits(4)) {
@@ -988,7 +988,7 @@ mozilla::Result<PlainDate, ParserError> TemporalParser<CharT>::date() {
// Optional: -
character('-');
- // DateMonth :
+ // DateMonth :::
// 0 NonzeroDigit
// 10
// 11
@@ -1005,7 +1005,7 @@ mozilla::Result<PlainDate, ParserError> TemporalParser<CharT>::date() {
// Optional: -
character('-');
- // DateDay :
+ // DateDay :::
// 0 NonzeroDigit
// 1 DecimalDigit
// 2 DecimalDigit
@@ -1025,7 +1025,7 @@ mozilla::Result<PlainDate, ParserError> TemporalParser<CharT>::date() {
template <typename CharT>
mozilla::Result<PlainTime, ParserError> TemporalParser<CharT>::timeSpec() {
- // TimeSpec :
+ // TimeSpec :::
// TimeHour
// TimeHour : TimeMinute
// TimeHour TimeMinute
@@ -1033,12 +1033,11 @@ mozilla::Result<PlainTime, ParserError> TemporalParser<CharT>::timeSpec() {
// TimeHour TimeMinute TimeSecond TimeFraction?
PlainTime result = {};
- // TimeHour :
- // Hour[+Padded]
+ // TimeHour :::
+ // Hour
//
- // Hour[Padded] :
- // [~Padded] DecimalDigit
- // [~Padded] 0 DecimalDigit
+ // Hour :::
+ // 0 DecimalDigit
// 1 DecimalDigit
// 20
// 21
@@ -1056,10 +1055,10 @@ mozilla::Result<PlainTime, ParserError> TemporalParser<CharT>::timeSpec() {
// Optional: :
bool needsMinutes = character(':');
- // TimeMinute :
+ // TimeMinute :::
// MinuteSecond
//
- // MinuteSecond :
+ // MinuteSecond :::
// 0 DecimalDigit
// 1 DecimalDigit
// 2 DecimalDigit
@@ -1075,7 +1074,7 @@ mozilla::Result<PlainTime, ParserError> TemporalParser<CharT>::timeSpec() {
// Optional: :
bool needsSeconds = needsMinutes && character(':');
- // TimeSecond :
+ // TimeSecond :::
// MinuteSecond
// 60
if (auto second = digits(2)) {
@@ -1084,7 +1083,7 @@ mozilla::Result<PlainTime, ParserError> TemporalParser<CharT>::timeSpec() {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_LEAPSECOND);
}
- // TimeFraction :
+ // TimeFraction :::
// Fraction
if (auto f = fraction()) {
int32_t fractionalPart = f.value();
@@ -1105,7 +1104,7 @@ mozilla::Result<PlainTime, ParserError> TemporalParser<CharT>::timeSpec() {
template <typename CharT>
mozilla::Result<TimeZoneString, ParserError>
TemporalParser<CharT>::dateTimeUTCOffset() {
- // DateTimeUTCOffset :
+ // DateTimeUTCOffset :::
// UTCDesignator
// UTCOffsetSubMinutePrecision
@@ -1127,13 +1126,13 @@ TemporalParser<CharT>::dateTimeUTCOffset() {
template <typename CharT>
mozilla::Result<TimeZoneUTCOffset, ParserError>
TemporalParser<CharT>::timeZoneUTCOffsetName() {
- // TimeZoneUTCOffsetName :
+ // TimeZoneUTCOffsetName :::
// UTCOffsetMinutePrecision
//
- // UTCOffsetMinutePrecision :
- // Sign Hour[+Padded]
- // Sign Hour[+Padded] TimeSeparator[+Extended] MinuteSecond
- // Sign Hour[+Padded] TimeSeparator[~Extended] MinuteSecond
+ // UTCOffsetMinutePrecision :::
+ // Sign Hour
+ // Sign Hour TimeSeparator[+Extended] MinuteSecond
+ // Sign Hour TimeSeparator[~Extended] MinuteSecond
TimeZoneUTCOffset result = {};
@@ -1142,9 +1141,8 @@ TemporalParser<CharT>::timeZoneUTCOffsetName() {
}
result.sign = sign();
- // Hour[Padded] :
- // [~Padded] DecimalDigit
- // [+Padded] 0 DecimalDigit
+ // Hour :::
+ // 0 DecimalDigit
// 1 DecimalDigit
// 20
// 21
@@ -1159,12 +1157,12 @@ TemporalParser<CharT>::timeZoneUTCOffsetName() {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_HOUR);
}
- // TimeSeparator[Extended] :
+ // TimeSeparator[Extended] :::
// [+Extended] :
// [~Extended] [empty]
bool needsMinutes = character(':');
- // MinuteSecond :
+ // MinuteSecond :::
// 0 DecimalDigit
// 1 DecimalDigit
// 2 DecimalDigit
@@ -1192,18 +1190,18 @@ mozilla::Result<DateTimeUTCOffset, ParserError>
TemporalParser<CharT>::utcOffsetSubMinutePrecision() {
// clang-format off
//
- // UTCOffsetSubMinutePrecision :
+ // UTCOffsetSubMinutePrecision :::
// UTCOffsetMinutePrecision
// UTCOffsetWithSubMinuteComponents[+Extended]
// UTCOffsetWithSubMinuteComponents[~Extended]
//
- // UTCOffsetMinutePrecision :
- // Sign Hour[+Padded]
- // Sign Hour[+Padded] TimeSeparator[+Extended] MinuteSecond
- // Sign Hour[+Padded] TimeSeparator[~Extended] MinuteSecond
+ // UTCOffsetMinutePrecision :::
+ // Sign Hour
+ // Sign Hour TimeSeparator[+Extended] MinuteSecond
+ // Sign Hour TimeSeparator[~Extended] MinuteSecond
//
- // UTCOffsetWithSubMinuteComponents[Extended] :
- // Sign Hour[+Padded] TimeSeparator[?Extended] MinuteSecond TimeSeparator[?Extended] MinuteSecond Fraction?
+ // UTCOffsetWithSubMinuteComponents[Extended] :::
+ // Sign Hour TimeSeparator[?Extended] MinuteSecond TimeSeparator[?Extended] MinuteSecond Fraction?
//
// clang-format on
@@ -1214,9 +1212,8 @@ TemporalParser<CharT>::utcOffsetSubMinutePrecision() {
}
result.sign = sign();
- // Hour[Padded] :
- // [~Padded] DecimalDigit
- // [+Padded] 0 DecimalDigit
+ // Hour :::
+ // 0 DecimalDigit
// 1 DecimalDigit
// 20
// 21
@@ -1231,12 +1228,12 @@ TemporalParser<CharT>::utcOffsetSubMinutePrecision() {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_HOUR);
}
- // TimeSeparator[Extended] :
+ // TimeSeparator[Extended] :::
// [+Extended] :
// [~Extended] [empty]
bool needsMinutes = character(':');
- // MinuteSecond :
+ // MinuteSecond :::
// 0 DecimalDigit
// 1 DecimalDigit
// 2 DecimalDigit
@@ -1249,12 +1246,12 @@ TemporalParser<CharT>::utcOffsetSubMinutePrecision() {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MINUTE);
}
- // TimeSeparator[Extended] :
+ // TimeSeparator[Extended] :::
// [+Extended] :
// [~Extended] [empty]
bool needsSeconds = needsMinutes && character(':');
- // MinuteSecond :
+ // MinuteSecond :::
// 0 DecimalDigit
// 1 DecimalDigit
// 2 DecimalDigit
@@ -1285,7 +1282,7 @@ TemporalParser<CharT>::utcOffsetSubMinutePrecision() {
template <typename CharT>
mozilla::Result<TimeZoneAnnotation, ParserError>
TemporalParser<CharT>::timeZoneIdentifier() {
- // TimeZoneIdentifier :
+ // TimeZoneIdentifier :::
// TimeZoneUTCOffsetName
// TimeZoneIANAName
@@ -1310,7 +1307,7 @@ TemporalParser<CharT>::timeZoneIdentifier() {
template <typename CharT>
mozilla::Result<TimeZoneAnnotation, ParserError>
TemporalParser<CharT>::timeZoneAnnotation() {
- // TimeZoneAnnotation :
+ // TimeZoneAnnotation :::
// [ AnnotationCriticalFlag? TimeZoneIdentifier ]
if (!character('[')) {
@@ -1335,11 +1332,11 @@ TemporalParser<CharT>::timeZoneAnnotation() {
template <typename CharT>
mozilla::Result<TimeZoneName, ParserError>
TemporalParser<CharT>::timeZoneIANAName() {
- // TimeZoneIANAName :
+ // TimeZoneIANAName :::
// TimeZoneIANANameComponent
// TimeZoneIANAName / TimeZoneIANANameComponent
//
- // TimeZoneIANANameComponent :
+ // TimeZoneIANANameComponent :::
// TZLeadingChar
// TimeZoneIANANameComponent TZChar
@@ -1385,7 +1382,7 @@ TemporalParser<CharT>::parseTemporalInstantString() {
// clang-format off
//
- // TemporalInstantString :
+ // TemporalInstantString :::
// Date DateTimeSeparator TimeSpec DateTimeUTCOffset TimeZoneAnnotation? Annotations?
//
// clang-format on
@@ -1492,7 +1489,7 @@ bool js::temporal::ParseTemporalInstantString(JSContext* cx,
template <typename CharT>
mozilla::Result<ZonedDateTimeString, ParserError>
TemporalParser<CharT>::parseTemporalTimeZoneString() {
- // TimeZoneIdentifier :
+ // TimeZoneIdentifier :::
// TimeZoneUTCOffsetName
// TimeZoneIANAName
@@ -1819,10 +1816,10 @@ TemporalParser<CharT>::parseTemporalDurationString(JSContext* cx) {
// Initialize all fields to zero.
TemporalDurationString result = {};
- // TemporalDurationString :
+ // TemporalDurationString :::
// Duration
//
- // Duration :
+ // Duration :::
// Sign? DurationDesignator DurationDate
// Sign? DurationDesignator DurationTime
@@ -1834,7 +1831,7 @@ TemporalParser<CharT>::parseTemporalDurationString(JSContext* cx) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DESIGNATOR);
}
- // DurationDate :
+ // DurationDate :::
// DurationYearsPart DurationTime?
// DurationMonthsPart DurationTime?
// DurationWeeksPart DurationTime?
@@ -1851,12 +1848,12 @@ TemporalParser<CharT>::parseTemporalDurationString(JSContext* cx) {
num = *d;
}
- // DurationYearsPart :
+ // DurationYearsPart :::
// DurationYears YearsDesignator DurationMonthsPart
// DurationYears YearsDesignator DurationWeeksPart
// DurationYears YearsDesignator DurationDaysPart?
//
- // DurationYears :
+ // DurationYears :::
// DecimalDigits[~Sep]
if (yearsDesignator()) {
result.years = num;
@@ -1873,11 +1870,11 @@ TemporalParser<CharT>::parseTemporalDurationString(JSContext* cx) {
}
}
- // DurationMonthsPart :
+ // DurationMonthsPart :::
// DurationMonths MonthsDesignator DurationWeeksPart
// DurationMonths MonthsDesignator DurationDaysPart?
//
- // DurationMonths :
+ // DurationMonths :::
// DecimalDigits[~Sep]
if (monthsDesignator()) {
result.months = num;
@@ -1894,10 +1891,10 @@ TemporalParser<CharT>::parseTemporalDurationString(JSContext* cx) {
}
}
- // DurationWeeksPart :
+ // DurationWeeksPart :::
// DurationWeeks WeeksDesignator DurationDaysPart?
//
- // DurationWeeks :
+ // DurationWeeks :::
// DecimalDigits[~Sep]
if (weeksDesignator()) {
result.weeks = num;
@@ -1914,10 +1911,10 @@ TemporalParser<CharT>::parseTemporalDurationString(JSContext* cx) {
}
}
- // DurationDaysPart :
+ // DurationDaysPart :::
// DurationDays DaysDesignator
//
- // DurationDays :
+ // DurationDays :::
// DecimalDigits[~Sep]
if (daysDesignator()) {
result.days = num;
@@ -1932,7 +1929,7 @@ TemporalParser<CharT>::parseTemporalDurationString(JSContext* cx) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
} while (false);
- // DurationTime :
+ // DurationTime :::
// DurationTimeDesignator DurationHoursPart
// DurationTimeDesignator DurationMinutesPart
// DurationTimeDesignator DurationSecondsPart
@@ -1958,18 +1955,18 @@ TemporalParser<CharT>::parseTemporalDurationString(JSContext* cx) {
// clang-format off
//
- // DurationHoursPart :
+ // DurationHoursPart :::
// DurationWholeHours DurationHoursFraction HoursDesignator
// DurationWholeHours HoursDesignator DurationMinutesPart
// DurationWholeHours HoursDesignator DurationSecondsPart?
//
- // DurationWholeHours :
+ // DurationWholeHours :::
// DecimalDigits[~Sep]
//
- // DurationHoursFraction :
+ // DurationHoursFraction :::
// TimeFraction
//
- // TimeFraction :
+ // TimeFraction :::
// Fraction
//
// clang-format on
@@ -1988,17 +1985,17 @@ TemporalParser<CharT>::parseTemporalDurationString(JSContext* cx) {
// clang-format off
//
- // DurationMinutesPart :
+ // DurationMinutesPart :::
// DurationWholeMinutes DurationMinutesFraction MinutesDesignator
// DurationWholeMinutes MinutesDesignator DurationSecondsPart?
//
- // DurationWholeMinutes :
+ // DurationWholeMinutes :::
// DecimalDigits[~Sep]
//
- // DurationMinutesFraction :
+ // DurationMinutesFraction :::
// TimeFraction
//
- // TimeFraction :
+ // TimeFraction :::
// Fraction
//
// clang-format on
@@ -2018,16 +2015,16 @@ TemporalParser<CharT>::parseTemporalDurationString(JSContext* cx) {
}
}
- // DurationSecondsPart :
+ // DurationSecondsPart :::
// DurationWholeSeconds DurationSecondsFraction? SecondsDesignator
//
- // DurationWholeSeconds :
+ // DurationWholeSeconds :::
// DecimalDigits[~Sep]
//
- // DurationSecondsFraction :
+ // DurationSecondsFraction :::
// TimeFraction
//
- // TimeFraction :
+ // TimeFraction :::
// Fraction
if (secondsDesignator()) {
if (hasHoursFraction || hasMinutesFraction) {
@@ -2106,14 +2103,14 @@ bool js::temporal::ParseTemporalDurationString(JSContext* cx,
// Steps 9.b-d.
int64_t h = int64_t(parsed.hoursFraction) * 60;
- minutes = h / 1'000'000'000;
+ minutes = double(h / 1'000'000'000);
// Steps 13 and 15-17.
int64_t min = (h % 1'000'000'000) * 60;
- seconds = min / 1'000'000'000;
- milliseconds = (min % 1'000'000'000) / 1'000'000;
- microseconds = (min % 1'000'000) / 1'000;
- nanoseconds = (min % 1'000);
+ seconds = double(min / 1'000'000'000);
+ milliseconds = double((min % 1'000'000'000) / 1'000'000);
+ microseconds = double((min % 1'000'000) / 1'000);
+ nanoseconds = double(min % 1'000);
}
// Step 11.
@@ -2130,10 +2127,10 @@ bool js::temporal::ParseTemporalDurationString(JSContext* cx,
// Steps 11.b-d and 15-17.
int64_t min = int64_t(parsed.minutesFraction) * 60;
- seconds = min / 1'000'000'000;
- milliseconds = (min % 1'000'000'000) / 1'000'000;
- microseconds = (min % 1'000'000) / 1'000;
- nanoseconds = (min % 1'000);
+ seconds = double(min / 1'000'000'000);
+ milliseconds = double((min % 1'000'000'000) / 1'000'000);
+ microseconds = double((min % 1'000'000) / 1'000);
+ nanoseconds = double(min % 1'000);
}
// Step 14.
@@ -2148,9 +2145,9 @@ bool js::temporal::ParseTemporalDurationString(JSContext* cx,
seconds = parsed.seconds;
// Steps 14, 16-17
- milliseconds = (parsed.secondsFraction / 1'000'000);
- microseconds = ((parsed.secondsFraction % 1'000'000) / 1'000);
- nanoseconds = (parsed.secondsFraction % 1'000);
+ milliseconds = double(parsed.secondsFraction / 1'000'000);
+ microseconds = double((parsed.secondsFraction % 1'000'000) / 1'000);
+ nanoseconds = double(parsed.secondsFraction % 1'000);
} else {
// Step 10.
minutes = parsed.minutes;
@@ -2168,7 +2165,7 @@ bool js::temporal::ParseTemporalDurationString(JSContext* cx,
int32_t factor = parsed.sign ? parsed.sign : 1;
MOZ_ASSERT(factor == -1 || factor == 1);
- // Step 20.
+ // Steps 20-29.
*result = {
(years * factor) + (+0.0), (months * factor) + (+0.0),
(weeks * factor) + (+0.0), (days * factor) + (+0.0),
@@ -2176,16 +2173,15 @@ bool js::temporal::ParseTemporalDurationString(JSContext* cx,
(seconds * factor) + (+0.0), (milliseconds * factor) + (+0.0),
(microseconds * factor) + (+0.0), (nanoseconds * factor) + (+0.0),
};
- if (!ThrowIfInvalidDuration(cx, *result)) {
- return false;
- }
- return true;
+
+ // Steps 30-31.
+ return ThrowIfInvalidDuration(cx, *result);
}
template <typename CharT>
mozilla::Result<AnnotationKey, ParserError>
TemporalParser<CharT>::annotationKey() {
- // AnnotationKey :
+ // AnnotationKey :::
// AKeyLeadingChar
// AnnotationKey AKeyChar
@@ -2205,7 +2201,7 @@ TemporalParser<CharT>::annotationKey() {
template <typename CharT>
mozilla::Result<AnnotationValue, ParserError>
TemporalParser<CharT>::annotationValue() {
- // AnnotationValue :
+ // AnnotationValue :::
// AnnotationValueComponent
// AnnotationValueComponent - AnnotationValue
@@ -2222,7 +2218,7 @@ TemporalParser<CharT>::annotationValue() {
template <typename CharT>
mozilla::Result<Annotation, ParserError> TemporalParser<CharT>::annotation() {
- // Annotation :
+ // Annotation :::
// [ AnnotationCriticalFlag? AnnotationKey = AnnotationValue ]
if (!character('[')) {
@@ -2255,7 +2251,7 @@ mozilla::Result<Annotation, ParserError> TemporalParser<CharT>::annotation() {
template <typename CharT>
mozilla::Result<CalendarName, ParserError>
TemporalParser<CharT>::annotations() {
- // Annotations:
+ // Annotations :::
// Annotation Annotations?
MOZ_ASSERT(hasAnnotationStart());
@@ -2269,9 +2265,6 @@ TemporalParser<CharT>::annotations() {
}
auto [key, value, critical] = anno.unwrap();
- // FIXME: spec issue - ignore case for "[u-ca=" to match BCP47?
- // https://github.com/tc39/proposal-temporal/issues/2524
-
static constexpr std::string_view ca = "u-ca";
auto keySpan = reader_.substring(key);
@@ -2295,7 +2288,7 @@ mozilla::Result<ZonedDateTimeString, ParserError>
TemporalParser<CharT>::annotatedTime() {
// clang-format off
//
- // AnnotatedTime :
+ // AnnotatedTime :::
// TimeDesignator TimeSpec DateTimeUTCOffset? TimeZoneAnnotation? Annotations?
// TimeSpecWithOptionalOffsetNotAmbiguous TimeZoneAnnotation? Annotations?
//
@@ -2339,7 +2332,7 @@ TemporalParser<CharT>::annotatedTime() {
// clang-format off
//
- // TimeSpecWithOptionalOffsetNotAmbiguous :
+ // TimeSpecWithOptionalOffsetNotAmbiguous :::
// TimeSpec DateTimeUTCOffset? but not one of ValidMonthDay or DateSpecYearMonth
//
// clang-format on
@@ -2407,7 +2400,7 @@ TemporalParser<CharT>::annotatedTime() {
template <typename CharT>
mozilla::Result<ZonedDateTimeString, ParserError>
TemporalParser<CharT>::annotatedDateTime() {
- // AnnotatedDateTime[Zoned] :
+ // AnnotatedDateTime[Zoned] :::
// [~Zoned] DateTime TimeZoneAnnotation? Annotations?
// [+Zoned] DateTime TimeZoneAnnotation Annotations?
@@ -2441,7 +2434,7 @@ mozilla::Result<ZonedDateTimeString, ParserError>
TemporalParser<CharT>::annotatedDateTimeTimeRequired() {
// clang-format off
//
- // AnnotatedDateTimeTimeRequired :
+ // AnnotatedDateTimeTimeRequired :::
// Date DateTimeSeparator TimeSpec DateTimeUTCOffset? TimeZoneAnnotation? Annotations?
//
// clang-format on
@@ -2494,7 +2487,7 @@ TemporalParser<CharT>::annotatedDateTimeTimeRequired() {
template <typename CharT>
mozilla::Result<ZonedDateTimeString, ParserError>
TemporalParser<CharT>::annotatedYearMonth() {
- // AnnotatedYearMonth :
+ // AnnotatedYearMonth :::
// DateSpecYearMonth TimeZoneAnnotation? Annotations?
ZonedDateTimeString result = {};
@@ -2527,7 +2520,7 @@ TemporalParser<CharT>::annotatedYearMonth() {
template <typename CharT>
mozilla::Result<ZonedDateTimeString, ParserError>
TemporalParser<CharT>::annotatedMonthDay() {
- // AnnotatedMonthDay :
+ // AnnotatedMonthDay :::
// DateSpecMonthDay TimeZoneAnnotation? Annotations?
ZonedDateTimeString result = {};
@@ -2560,11 +2553,11 @@ TemporalParser<CharT>::annotatedMonthDay() {
template <typename CharT>
mozilla::Result<PlainDate, ParserError>
TemporalParser<CharT>::dateSpecYearMonth() {
- // DateSpecYearMonth :
+ // DateSpecYearMonth :::
// DateYear -? DateMonth
PlainDate result = {};
- // DateYear :
+ // DateYear :::
// DecimalDigit{4}
// Sign DecimalDigit{6}
if (auto year = digits(4)) {
@@ -2585,7 +2578,7 @@ TemporalParser<CharT>::dateSpecYearMonth() {
character('-');
- // DateMonth :
+ // DateMonth :::
// 0 NonzeroDigit
// 10
// 11
@@ -2608,16 +2601,17 @@ TemporalParser<CharT>::dateSpecYearMonth() {
template <typename CharT>
mozilla::Result<PlainDate, ParserError>
TemporalParser<CharT>::dateSpecMonthDay() {
- // DateSpecMonthDay :
+ // DateSpecMonthDay :::
// -- DateMonth -? DateDay
// DateMonth -? DateDay
PlainDate result = {};
+ // Optional: --
string("--");
result.year = AbsentYear;
- // DateMonth :
+ // DateMonth :::
// 0 NonzeroDigit
// 10
// 11
@@ -2631,9 +2625,10 @@ TemporalParser<CharT>::dateSpecMonthDay() {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MONTH);
}
+ // Optional: -
character('-');
- // DateDay :
+ // DateDay :::
// 0 NonzeroDigit
// 1 DecimalDigit
// 2 DecimalDigit
@@ -2653,19 +2648,19 @@ TemporalParser<CharT>::dateSpecMonthDay() {
template <typename CharT>
mozilla::Result<PlainDate, ParserError> TemporalParser<CharT>::validMonthDay() {
- // ValidMonthDay :
+ // ValidMonthDay :::
// DateMonth -? 0 NonZeroDigit
// DateMonth -? 1 DecimalDigit
// DateMonth -? 2 DecimalDigit
// DateMonth -? 30 but not one of 0230 or 02-30
// DateMonthWithThirtyOneDays -? 31
//
- // DateMonthWithThirtyOneDays : one of
+ // DateMonthWithThirtyOneDays ::: one of
// 01 03 05 07 08 10 12
PlainDate result = {};
- // DateMonth :
+ // DateMonth :::
// 0 NonzeroDigit
// 10
// 11
@@ -2679,6 +2674,7 @@ mozilla::Result<PlainDate, ParserError> TemporalParser<CharT>::validMonthDay() {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MONTH);
}
+ // Optional: -
character('-');
if (auto day = digits(2)) {
@@ -2834,7 +2830,7 @@ JSLinearString* js::temporal::ParseTemporalCalendarString(
template <typename CharT>
mozilla::Result<ZonedDateTimeString, ParserError>
TemporalParser<CharT>::parseTemporalTimeString() {
- // TemporalTimeString :
+ // TemporalTimeString :::
// AnnotatedTime
// AnnotatedDateTimeTimeRequired
@@ -2915,7 +2911,7 @@ bool js::temporal::ParseTemporalTimeString(JSContext* cx, Handle<JSString*> str,
template <typename CharT>
mozilla::Result<ZonedDateTimeString, ParserError>
TemporalParser<CharT>::parseTemporalMonthDayString() {
- // TemporalMonthDayString :
+ // TemporalMonthDayString :::
// AnnotatedMonthDay
// AnnotatedDateTime[~Zoned]
@@ -3014,7 +3010,7 @@ bool js::temporal::ParseTemporalMonthDayString(
template <typename CharT>
mozilla::Result<ZonedDateTimeString, ParserError>
TemporalParser<CharT>::parseTemporalYearMonthString() {
- // TemporalYearMonthString :
+ // TemporalYearMonthString :::
// AnnotatedYearMonth
// AnnotatedDateTime[~Zoned]
@@ -3112,7 +3108,7 @@ bool js::temporal::ParseTemporalYearMonthString(
template <typename CharT>
mozilla::Result<ZonedDateTimeString, ParserError>
TemporalParser<CharT>::parseTemporalDateTimeString() {
- // TemporalDateTimeString[Zoned] :
+ // TemporalDateTimeString[Zoned] :::
// AnnotatedDateTime[?Zoned]
auto dateTime = annotatedDateTime();
@@ -3209,10 +3205,10 @@ mozilla::Result<ZonedDateTimeString, ParserError>
TemporalParser<CharT>::parseTemporalZonedDateTimeString() {
// Parse goal: TemporalDateTimeString[+Zoned]
//
- // TemporalDateTimeString[Zoned] :
+ // TemporalDateTimeString[Zoned] :::
// AnnotatedDateTime[?Zoned]
//
- // AnnotatedDateTime[Zoned] :
+ // AnnotatedDateTime[Zoned] :::
// [~Zoned] DateTime TimeZoneAnnotation? Annotations?
// [+Zoned] DateTime TimeZoneAnnotation Annotations?
@@ -3269,7 +3265,7 @@ static auto ParseTemporalZonedDateTimeString(Handle<JSLinearString*> str) {
bool js::temporal::ParseTemporalZonedDateTimeString(
JSContext* cx, Handle<JSString*> str, PlainDateTime* dateTime, bool* isUTC,
bool* hasOffset, int64_t* timeZoneOffset,
- MutableHandle<ParsedTimeZone> timeZoneName,
+ MutableHandle<ParsedTimeZone> timeZoneAnnotation,
MutableHandle<JSString*> calendar) {
Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
if (!linear) {
@@ -3313,7 +3309,7 @@ bool js::temporal::ParseTemporalZonedDateTimeString(
// { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" }
const auto& annotation = parsed.timeZone.annotation;
- if (!ParseTimeZoneAnnotation(cx, annotation, linear, timeZoneName)) {
+ if (!ParseTimeZoneAnnotation(cx, annotation, linear, timeZoneAnnotation)) {
return false;
}
@@ -3370,7 +3366,7 @@ static auto ParseTemporalRelativeToString(Handle<JSLinearString*> str) {
bool js::temporal::ParseTemporalRelativeToString(
JSContext* cx, Handle<JSString*> str, PlainDateTime* dateTime, bool* isUTC,
bool* hasOffset, int64_t* timeZoneOffset,
- MutableHandle<ParsedTimeZone> timeZoneName,
+ MutableHandle<ParsedTimeZone> timeZoneAnnotation,
MutableHandle<JSString*> calendar) {
Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
if (!linear) {
@@ -3420,7 +3416,7 @@ bool js::temporal::ParseTemporalRelativeToString(
// { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" }
const auto& annotation = parsed.timeZone.annotation;
- if (!ParseTimeZoneAnnotation(cx, annotation, linear, timeZoneName)) {
+ if (!ParseTimeZoneAnnotation(cx, annotation, linear, timeZoneAnnotation)) {
return false;
}
@@ -3444,7 +3440,7 @@ bool js::temporal::ParseTemporalRelativeToString(
*isUTC = false;
*hasOffset = false;
*timeZoneOffset = 0;
- timeZoneName.set(ParsedTimeZone{});
+ timeZoneAnnotation.set(ParsedTimeZone{});
}
// Step 4. (ParseISODateTime, steps 23-24.)
diff --git a/js/src/builtin/temporal/TemporalParser.h b/js/src/builtin/temporal/TemporalParser.h
index 677a90b58d..86ac7bbd82 100644
--- a/js/src/builtin/temporal/TemporalParser.h
+++ b/js/src/builtin/temporal/TemporalParser.h
@@ -8,6 +8,7 @@
#define builtin_temporal_TemporalParser_h
#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
#include <cstdlib>
#include <stdint.h>
@@ -26,7 +27,7 @@ struct PlainDate;
struct PlainDateTime;
struct PlainTime;
-struct ParsedTimeZone {
+struct MOZ_STACK_CLASS ParsedTimeZone final {
JSLinearString* name = nullptr;
int32_t offset = INT32_MIN;
@@ -129,7 +130,7 @@ bool ParseTemporalDateTimeString(JSContext* cx, JS::Handle<JSString*> str,
bool ParseTemporalZonedDateTimeString(
JSContext* cx, JS::Handle<JSString*> str, PlainDateTime* dateTime,
bool* isUTC, bool* hasOffset, int64_t* timeZoneOffset,
- JS::MutableHandle<ParsedTimeZone> timeZoneName,
+ JS::MutableHandle<ParsedTimeZone> timeZoneAnnotation,
JS::MutableHandle<JSString*> calendar);
/**
@@ -138,7 +139,7 @@ bool ParseTemporalZonedDateTimeString(
bool ParseTemporalRelativeToString(
JSContext* cx, JS::Handle<JSString*> str, PlainDateTime* dateTime,
bool* isUTC, bool* hasOffset, int64_t* timeZoneOffset,
- JS::MutableHandle<ParsedTimeZone> timeZoneName,
+ JS::MutableHandle<ParsedTimeZone> timeZoneAnnotation,
JS::MutableHandle<JSString*> calendar);
} /* namespace js::temporal */
diff --git a/js/src/builtin/temporal/TemporalRoundingMode.h b/js/src/builtin/temporal/TemporalRoundingMode.h
index 91ef758fc6..23d3996d09 100644
--- a/js/src/builtin/temporal/TemporalRoundingMode.h
+++ b/js/src/builtin/temporal/TemporalRoundingMode.h
@@ -12,6 +12,8 @@
#include <cmath>
#include <stdint.h>
+#include "builtin/temporal/Int128.h"
+
namespace js::temporal {
// Overview of integer rounding modes is available at
@@ -428,6 +430,286 @@ inline int64_t Divide(int64_t dividend, int64_t divisor,
MOZ_CRASH("invalid rounding mode");
}
+/**
+ * Compute ceiling division ⌈dividend / divisor⌉. The divisor must be a positive
+ * number.
+ */
+constexpr Int128 CeilDiv(const Int128& dividend, const Int128& divisor) {
+ MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported");
+
+ auto [quotient, remainder] = dividend.divrem(divisor);
+
+ // Ceiling division rounds the quotient toward positive infinity. When the
+ // quotient is negative, this is equivalent to rounding toward zero. See [1].
+ //
+ // Int128 division truncates, so rounding toward zero for negative quotients
+ // is already covered. When there is a non-zero positive remainder, the
+ // quotient is positive and we have to increment it by one to implement
+ // rounding toward positive infinity.
+ //
+ // [1]
+ // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
+ if (remainder > Int128{0}) {
+ quotient += Int128{1};
+ }
+ return quotient;
+}
+
+/**
+ * Compute floor division ⌊dividend / divisor⌋. The divisor must be a positive
+ * number.
+ */
+constexpr Int128 FloorDiv(const Int128& dividend, const Int128& divisor) {
+ MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported");
+
+ auto [quotient, remainder] = dividend.divrem(divisor);
+
+ // Floor division rounds the quotient toward negative infinity. When the
+ // quotient is positive, this is equivalent to rounding toward zero. See [1].
+ //
+ // Int128 division truncates, so rounding toward zero for positive quotients
+ // is already covered. When there is a non-zero negative remainder, the
+ // quotient is negative and we have to decrement it by one to implement
+ // rounding toward negative infinity.
+ //
+ // [1]
+ // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
+ if (remainder < Int128{0}) {
+ quotient -= Int128{1};
+ }
+ return quotient;
+}
+
+/**
+ * Compute "round toward infinity" division `dividend / divisor`. The divisor
+ * must be a positive number.
+ */
+constexpr Int128 ExpandDiv(const Int128& dividend, const Int128& divisor) {
+ MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported");
+
+ auto [quotient, remainder] = dividend.divrem(divisor);
+
+ // "Round toward infinity" division rounds positive quotients toward positive
+ // infinity and negative quotients toward negative infinity. See [1].
+ //
+ // When there is a non-zero positive remainder, the quotient is positive and
+ // we have to increment it by one to implement rounding toward positive
+ // infinity. When there is a non-zero negative remainder, the quotient is
+ // negative and we have to decrement it by one to implement rounding toward
+ // negative infinity.
+ //
+ // [1]
+ // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
+ if (remainder > Int128{0}) {
+ quotient += Int128{1};
+ }
+ if (remainder < Int128{0}) {
+ quotient -= Int128{1};
+ }
+ return quotient;
+}
+
+/**
+ * Compute truncating division `dividend / divisor`. The divisor must be a
+ * positive number.
+ */
+constexpr Int128 TruncDiv(const Int128& dividend, const Int128& divisor) {
+ MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported");
+
+ // Truncating division rounds both positive and negative quotients toward
+ // zero, cf. [1].
+ //
+ // Int128 division truncates, so rounding toward zero implicitly happens.
+ //
+ // [1]
+ // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
+ return dividend / divisor;
+}
+
+/**
+ * Compute "round half toward positive infinity" division `dividend / divisor`.
+ * The divisor must be a positive number.
+ */
+inline Int128 HalfCeilDiv(const Int128& dividend, const Int128& divisor) {
+ MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported");
+
+ auto [quotient, remainder] = dividend.divrem(divisor);
+
+ // "Round half toward positive infinity" division rounds the quotient toward
+ // positive infinity when the fractional part of the remainder is ≥0.5. When
+ // the quotient is negative, this is equivalent to rounding toward zero
+ // instead of toward positive infinity. See [1].
+ //
+ // When the remainder is a non-zero positive value, the quotient is positive,
+ // too. When additionally the fractional part of the remainder is ≥0.5, we
+ // have to increment the quotient by one to implement rounding toward positive
+ // infinity.
+ //
+ // Int128 division truncates, so we implicitly round toward zero for negative
+ // quotients. When the absolute value of the fractional part of the remainder
+ // is >0.5, we should instead have rounded toward negative infinity, so we
+ // need to decrement the quotient by one.
+ //
+ // [1]
+ // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
+ if (remainder > Int128{0} &&
+ Uint128(remainder.abs()) * Uint128{2} >= static_cast<Uint128>(divisor)) {
+ quotient += Int128{1};
+ }
+ if (remainder < Int128{0} &&
+ Uint128(remainder.abs()) * Uint128{2} > static_cast<Uint128>(divisor)) {
+ quotient -= Int128{1};
+ }
+ return quotient;
+}
+
+/**
+ * Compute "round half toward negative infinity" division `dividend / divisor`.
+ * The divisor must be a positive number.
+ */
+inline Int128 HalfFloorDiv(const Int128& dividend, const Int128& divisor) {
+ MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported");
+
+ auto [quotient, remainder] = dividend.divrem(divisor);
+
+ // "Round half toward negative infinity" division rounds the quotient toward
+ // negative infinity when the fractional part of the remainder is ≥0.5. When
+ // the quotient is positive, this is equivalent to rounding toward zero
+ // instead of toward negative infinity. See [1].
+ //
+ // When the remainder is a non-zero negative value, the quotient is negative,
+ // too. When additionally the fractional part of the remainder is ≥0.5, we
+ // have to decrement the quotient by one to implement rounding toward negative
+ // infinity.
+ //
+ // Int128 division truncates, so we implicitly round toward zero for positive
+ // quotients. When the absolute value of the fractional part of the remainder
+ // is >0.5, we should instead have rounded toward positive infinity, so we
+ // need to increment the quotient by one.
+ //
+ // [1]
+ // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
+ if (remainder < Int128{0} &&
+ Uint128(remainder.abs()) * Uint128{2} >= static_cast<Uint128>(divisor)) {
+ quotient -= Int128{1};
+ }
+ if (remainder > Int128{0} &&
+ Uint128(remainder.abs()) * Uint128{2} > static_cast<Uint128>(divisor)) {
+ quotient += Int128{1};
+ }
+ return quotient;
+}
+
+/**
+ * Compute "round half toward infinity" division `dividend / divisor`. The
+ * divisor must be a positive number.
+ */
+inline Int128 HalfExpandDiv(const Int128& dividend, const Int128& divisor) {
+ MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported");
+
+ auto [quotient, remainder] = dividend.divrem(divisor);
+
+ // "Round half toward infinity" division rounds positive quotients whose
+ // remainder has a fractional part ≥0.5 toward positive infinity. And negative
+ // quotients whose remainder has a fractional part ≥0.5 toward negative
+ // infinity. See [1].
+ //
+ // Int128 division truncates, which means it rounds toward zero, so we have
+ // to increment resp. decrement the quotient when the fractional part of the
+ // remainder is ≥0.5 to round toward ±infinity.
+ //
+ // [1]
+ // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
+ if (Uint128(remainder.abs()) * Uint128{2} >= static_cast<Uint128>(divisor)) {
+ quotient += (dividend > Int128{0}) ? Int128{1} : Int128{-1};
+ }
+ return quotient;
+}
+
+/**
+ * Compute "round half toward zero" division `dividend / divisor`. The divisor
+ * must be a positive number.
+ */
+inline Int128 HalfTruncDiv(const Int128& dividend, const Int128& divisor) {
+ MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported");
+
+ auto [quotient, remainder] = dividend.divrem(divisor);
+
+ // "Round half toward zero" division rounds both positive and negative
+ // quotients whose remainder has a fractional part ≤0.5 toward zero. See [1].
+ //
+ // Int128 division truncates, so we implicitly round toward zero. When the
+ // fractional part of the remainder is >0.5, we should instead have rounded
+ // toward ±infinity, so we need to increment resp. decrement the quotient by
+ // one.
+ //
+ // [1]
+ // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
+ if (Uint128(remainder.abs()) * Uint128{2} > static_cast<Uint128>(divisor)) {
+ quotient += (dividend > Int128{0}) ? Int128{1} : Int128{-1};
+ }
+ return quotient;
+}
+
+/**
+ * Compute "round half to even" division `dividend / divisor`. The divisor must
+ * be a positive number.
+ */
+inline Int128 HalfEvenDiv(const Int128& dividend, const Int128& divisor) {
+ MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported");
+
+ auto [quotient, remainder] = dividend.divrem(divisor);
+
+ // "Round half to even" division rounds both positive and negative quotients
+ // to the nearest even integer. See [1].
+ //
+ // Int128 division truncates, so we implicitly round toward zero. When the
+ // fractional part of the remainder is 0.5 and the quotient is odd or when the
+ // fractional part of the remainder is >0.5, we should instead have rounded
+ // toward ±infinity, so we need to increment resp. decrement the quotient by
+ // one.
+ //
+ // [1]
+ // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
+ if ((quotient & Int128{1}) == Int128{1} &&
+ Uint128(remainder.abs()) * Uint128{2} == static_cast<Uint128>(divisor)) {
+ quotient += (dividend > Int128{0}) ? Int128{1} : Int128{-1};
+ }
+ if (Uint128(remainder.abs()) * Uint128{2} > static_cast<Uint128>(divisor)) {
+ quotient += (dividend > Int128{0}) ? Int128{1} : Int128{-1};
+ }
+ return quotient;
+}
+
+/**
+ * Perform `dividend / divisor` and round the result according to the given
+ * rounding mode.
+ */
+inline Int128 Divide(const Int128& dividend, const Int128& divisor,
+ TemporalRoundingMode roundingMode) {
+ switch (roundingMode) {
+ case TemporalRoundingMode::Ceil:
+ return CeilDiv(dividend, divisor);
+ case TemporalRoundingMode::Floor:
+ return FloorDiv(dividend, divisor);
+ case TemporalRoundingMode::Expand:
+ return ExpandDiv(dividend, divisor);
+ case TemporalRoundingMode::Trunc:
+ return TruncDiv(dividend, divisor);
+ case TemporalRoundingMode::HalfCeil:
+ return HalfCeilDiv(dividend, divisor);
+ case TemporalRoundingMode::HalfFloor:
+ return HalfFloorDiv(dividend, divisor);
+ case TemporalRoundingMode::HalfExpand:
+ return HalfExpandDiv(dividend, divisor);
+ case TemporalRoundingMode::HalfTrunc:
+ return HalfTruncDiv(dividend, divisor);
+ case TemporalRoundingMode::HalfEven:
+ return HalfEvenDiv(dividend, divisor);
+ }
+ MOZ_CRASH("invalid rounding mode");
+}
+
} /* namespace js::temporal */
#endif /* builtin_temporal_TemporalRoundingMode_h */
diff --git a/js/src/builtin/temporal/TemporalTypes.h b/js/src/builtin/temporal/TemporalTypes.h
index 654fd65a3b..b645a9a175 100644
--- a/js/src/builtin/temporal/TemporalTypes.h
+++ b/js/src/builtin/temporal/TemporalTypes.h
@@ -11,7 +11,9 @@
#include "mozilla/CheckedInt.h"
#include <stdint.h>
+#include <type_traits>
+#include "builtin/temporal/Int128.h"
#include "builtin/temporal/TemporalUnit.h"
namespace js::temporal {
@@ -181,15 +183,19 @@ struct SecondsAndNanoseconds {
/**
* Return the nanoseconds value.
- *
- * The returned nanoseconds amount can be invalid on overflow. The caller is
- * responsible for handling the overflow case.
*/
- constexpr mozilla::CheckedInt64 toNanoseconds() const {
- mozilla::CheckedInt64 nanos = seconds;
- nanos *= ToNanoseconds(TemporalUnit::Second);
- nanos += nanoseconds;
- return nanos;
+ constexpr Int128 toNanoseconds() const {
+ return Int128{seconds} * Int128{ToNanoseconds(TemporalUnit::Second)} +
+ Int128{nanoseconds};
+ }
+
+ /**
+ * Cast to a different representation.
+ */
+ template <class Other>
+ constexpr Other to() const {
+ static_assert(std::is_base_of_v<SecondsAndNanoseconds<Other>, Other>);
+ return Other{seconds, nanoseconds};
}
/**
@@ -209,7 +215,7 @@ struct SecondsAndNanoseconds {
*/
static constexpr Derived fromMilliseconds(int64_t milliseconds) {
int64_t seconds = milliseconds / 1'000;
- int32_t millis = milliseconds % 1'000;
+ int32_t millis = int32_t(milliseconds % 1'000);
if (millis < 0) {
seconds -= 1;
millis += 1'000;
@@ -222,7 +228,7 @@ struct SecondsAndNanoseconds {
*/
static constexpr Derived fromMicroseconds(int64_t microseconds) {
int64_t seconds = microseconds / 1'000'000;
- int32_t micros = microseconds % 1'000'000;
+ int32_t micros = int32_t(microseconds % 1'000'000);
if (micros < 0) {
seconds -= 1;
micros += 1'000'000;
@@ -235,7 +241,21 @@ struct SecondsAndNanoseconds {
*/
static constexpr Derived fromNanoseconds(int64_t nanoseconds) {
int64_t seconds = nanoseconds / 1'000'000'000;
- int32_t nanos = nanoseconds % 1'000'000'000;
+ int32_t nanos = int32_t(nanoseconds % 1'000'000'000);
+ if (nanos < 0) {
+ seconds -= 1;
+ nanos += 1'000'000'000;
+ }
+ return {seconds, nanos};
+ }
+
+ /**
+ * Create from a nanoseconds value.
+ */
+ static constexpr Derived fromNanoseconds(const Int128& nanoseconds) {
+ auto div = nanoseconds.divrem(Int128{1'000'000'000});
+ int64_t seconds = int64_t(div.first);
+ int32_t nanos = int32_t(div.second);
if (nanos < 0) {
seconds -= 1;
nanos += 1'000'000'000;
@@ -365,6 +385,32 @@ struct Instant final : SecondsAndNanoseconds<Instant> {
static constexpr Instant min() { return -max(); }
};
+// Minimum and maximum valid epoch day relative to midnight at the beginning of
+// 1 January 1970 UTC.
+//
+// NOTE in ISODateTimeWithinLimits:
+//
+// Temporal.PlainDateTime objects can represent points in time within 24 hours
+// (8.64 × 10**13 nanoseconds) of the Temporal.Instant boundaries. This ensures
+// that a Temporal.Instant object can be converted into a Temporal.PlainDateTime
+// object using any time zone.
+//
+// This limits the maximum valid date-time to +275760-09-13T23:59:59.999Z and
+// the minimum valid date-time -271821-04-19T00:00:00.001Z. The corresponding
+// maximum and minimum valid date values are therefore +275760-09-13 and
+// -271821-04-19. There are exactly 100'000'000 days from 1 January 1970 UTC to
+// the maximum valid date and -100'000'001 days to the minimum valid date.
+constexpr inline int32_t MinEpochDay = -100'000'001;
+constexpr inline int32_t MaxEpochDay = 100'000'000;
+
+static_assert(MinEpochDay ==
+ Instant::min().seconds / ToSeconds(TemporalUnit::Day) - 1);
+static_assert(MaxEpochDay ==
+ Instant::max().seconds / ToSeconds(TemporalUnit::Day));
+
+// Maximum number of days between two valid epoch days.
+constexpr inline int32_t MaxEpochDaysDuration = MaxEpochDay - MinEpochDay;
+
/**
* Plain date represents a date in the ISO 8601 calendar.
*/
@@ -437,6 +483,9 @@ struct PlainDateTime final {
}
};
+struct DateDuration;
+struct TimeDuration;
+
/**
* Duration represents the difference between dates or times. Each duration
* component is an integer and all components must have the same sign.
@@ -453,7 +502,7 @@ struct Duration final {
double microseconds = 0;
double nanoseconds = 0;
- bool operator==(const Duration& other) const {
+ constexpr bool operator==(const Duration& other) const {
return years == other.years && months == other.months &&
weeks == other.weeks && days == other.days && hours == other.hours &&
minutes == other.minutes && seconds == other.seconds &&
@@ -462,35 +511,14 @@ struct Duration final {
nanoseconds == other.nanoseconds;
}
- bool operator!=(const Duration& other) const { return !(*this == other); }
-
- /**
- * Return the date components of this duration.
- */
- Duration date() const { return {years, months, weeks, days}; }
-
- /**
- * Return the time components of this duration.
- */
- Duration time() const {
- return {
- 0,
- 0,
- 0,
- 0,
- hours,
- minutes,
- seconds,
- milliseconds,
- microseconds,
- nanoseconds,
- };
+ constexpr bool operator!=(const Duration& other) const {
+ return !(*this == other);
}
/**
* Return a new duration with every component negated.
*/
- Duration negate() const {
+ constexpr Duration negate() const {
// Add zero to convert -0 to +0.
return {
-years + (+0.0), -months + (+0.0), -weeks + (+0.0),
@@ -499,6 +527,11 @@ struct Duration final {
-nanoseconds + (+0.0),
};
}
+
+ /**
+ * Return the date components of this duration.
+ */
+ inline DateDuration toDateDuration() const;
};
/**
@@ -506,41 +539,144 @@ struct Duration final {
* component is an integer and all components must have the same sign.
*/
struct DateDuration final {
- double years = 0;
- double months = 0;
- double weeks = 0;
- double days = 0;
+ // abs(years) < 2**32
+ int64_t years = 0;
+
+ // abs(months) < 2**32
+ int64_t months = 0;
+
+ // abs(weeks) < 2**32
+ int64_t weeks = 0;
+
+ // abs(days) < ⌈(2**53) / (24 * 60 * 60)⌉
+ int64_t days = 0;
- Duration toDuration() { return {years, months, weeks, days}; }
+ constexpr bool operator==(const DateDuration& other) const {
+ return years == other.years && months == other.months &&
+ weeks == other.weeks && days == other.days;
+ }
+
+ constexpr bool operator!=(const DateDuration& other) const {
+ return !(*this == other);
+ }
+
+ constexpr Duration toDuration() const {
+ return {
+ double(years),
+ double(months),
+ double(weeks),
+ double(days),
+ };
+ }
};
+inline DateDuration Duration::toDateDuration() const {
+ return {int64_t(years), int64_t(months), int64_t(weeks), int64_t(days)};
+}
+
/**
* Time duration represents the difference between times. Each duration
* component is an integer and all components must have the same sign.
*/
struct TimeDuration final {
- double days = 0;
- double hours = 0;
- double minutes = 0;
- double seconds = 0;
- double milliseconds = 0;
+ // abs(days) < ⌈(2**53) / (24 * 60 * 60)⌉
+ int64_t days = 0;
+
+ // abs(hours) < ⌈(2**53) / (60 * 60)⌉
+ int64_t hours = 0;
+
+ // abs(minutes) < ⌈(2**53) / 60⌉
+ int64_t minutes = 0;
+
+ // abs(seconds) < (2**53)
+ int64_t seconds = 0;
+
+ // abs(milliseconds) < (2**53) * (1000**1)
+ int64_t milliseconds = 0;
+
+ // abs(microseconds) < (2**53) * (1000**2)
double microseconds = 0;
+
+ // abs(nanoseconds) < (2**53) * (1000**3)
double nanoseconds = 0;
- Duration toDuration() {
+ constexpr Duration toDuration() const {
return {0,
0,
0,
- days,
- hours,
- minutes,
- seconds,
- milliseconds,
+ double(days),
+ double(hours),
+ double(minutes),
+ double(seconds),
+ double(milliseconds),
microseconds,
nanoseconds};
}
};
+/**
+ * Normalized time duration with a seconds value in the range
+ * [-9'007'199'254'740'991, +9'007'199'254'740'991] and a nanoseconds value in
+ * the range [0, 999'999'999].
+ */
+struct NormalizedTimeDuration final
+ : SecondsAndNanoseconds<NormalizedTimeDuration> {
+ constexpr NormalizedTimeDuration& operator+=(
+ const NormalizedTimeDuration& other) {
+ *this = add(*this, other);
+ return *this;
+ }
+
+ constexpr NormalizedTimeDuration& operator-=(
+ const NormalizedTimeDuration& other) {
+ *this = subtract(*this, other);
+ return *this;
+ }
+
+ constexpr NormalizedTimeDuration operator+(
+ const NormalizedTimeDuration& other) const {
+ return add(*this, other);
+ }
+
+ constexpr NormalizedTimeDuration operator-(
+ const NormalizedTimeDuration& other) const {
+ return subtract(*this, other);
+ }
+
+ constexpr NormalizedTimeDuration operator-() const { return negate(*this); }
+
+ /**
+ * Returns the maximum normalized time duration value.
+ */
+ static constexpr NormalizedTimeDuration max() {
+ constexpr int64_t seconds = 0x1f'ffff'ffff'ffff;
+ constexpr int64_t nanos = 999'999'999;
+ return {seconds, nanos};
+ }
+
+ /**
+ * Returns the minimum normalized time duration value.
+ */
+ static constexpr NormalizedTimeDuration min() { return -max(); }
+};
+
+/**
+ * Duration represents the difference between dates or times. Each duration
+ * component is an integer and all components must have the same sign.
+ */
+struct NormalizedDuration final {
+ DateDuration date;
+ NormalizedTimeDuration time;
+
+ constexpr bool operator==(const NormalizedDuration& other) const {
+ return date == other.date && time == other.time;
+ }
+
+ constexpr bool operator!=(const NormalizedDuration& other) const {
+ return !(*this == other);
+ }
+};
+
} /* namespace js::temporal */
#endif /* builtin_temporal_TemporalTypes_h */
diff --git a/js/src/builtin/temporal/TemporalUnit.h b/js/src/builtin/temporal/TemporalUnit.h
index 3c8801cb85..0294c1062d 100644
--- a/js/src/builtin/temporal/TemporalUnit.h
+++ b/js/src/builtin/temporal/TemporalUnit.h
@@ -52,6 +52,31 @@ constexpr int64_t ToNanoseconds(TemporalUnit unit) {
MOZ_CRASH("Unexpected temporal unit");
}
+constexpr int64_t ToMicroseconds(TemporalUnit unit) {
+ switch (unit) {
+ case TemporalUnit::Day:
+ return 86'400'000'000;
+ case TemporalUnit::Hour:
+ return 3'600'000'000;
+ case TemporalUnit::Minute:
+ return 60'000'000;
+ case TemporalUnit::Second:
+ return 1'000'000;
+ case TemporalUnit::Millisecond:
+ return 1'000;
+ case TemporalUnit::Microsecond:
+ return 1;
+
+ case TemporalUnit::Auto:
+ case TemporalUnit::Year:
+ case TemporalUnit::Month:
+ case TemporalUnit::Week:
+ case TemporalUnit::Nanosecond:
+ break;
+ }
+ MOZ_CRASH("Unexpected temporal unit");
+}
+
constexpr int64_t ToMilliseconds(TemporalUnit unit) {
switch (unit) {
case TemporalUnit::Day:
@@ -76,6 +101,29 @@ constexpr int64_t ToMilliseconds(TemporalUnit unit) {
MOZ_CRASH("Unexpected temporal unit");
}
+constexpr int64_t ToSeconds(TemporalUnit unit) {
+ switch (unit) {
+ case TemporalUnit::Day:
+ return 86'400;
+ case TemporalUnit::Hour:
+ return 3'600;
+ case TemporalUnit::Minute:
+ return 60;
+ case TemporalUnit::Second:
+ return 1;
+
+ case TemporalUnit::Auto:
+ case TemporalUnit::Year:
+ case TemporalUnit::Month:
+ case TemporalUnit::Week:
+ case TemporalUnit::Millisecond:
+ case TemporalUnit::Microsecond:
+ case TemporalUnit::Nanosecond:
+ break;
+ }
+ MOZ_CRASH("Unexpected temporal unit");
+}
+
constexpr int64_t UnitsPerDay(TemporalUnit unit) {
switch (unit) {
case TemporalUnit::Day:
diff --git a/js/src/builtin/temporal/TimeZone.cpp b/js/src/builtin/temporal/TimeZone.cpp
index ca7e1b9f11..8282b6739e 100644
--- a/js/src/builtin/temporal/TimeZone.cpp
+++ b/js/src/builtin/temporal/TimeZone.cpp
@@ -1131,23 +1131,23 @@ JSString* js::temporal::FormatUTCOffsetNanoseconds(JSContext* cx,
// Steps 7-8. (Inlined FormatTimeString).
result[n++] = sign;
- result[n++] = '0' + (hour / 10);
- result[n++] = '0' + (hour % 10);
+ result[n++] = char('0' + (hour / 10));
+ result[n++] = char('0' + (hour % 10));
result[n++] = ':';
- result[n++] = '0' + (minute / 10);
- result[n++] = '0' + (minute % 10);
+ result[n++] = char('0' + (minute / 10));
+ result[n++] = char('0' + (minute % 10));
if (second != 0 || subSecondNanoseconds != 0) {
result[n++] = ':';
- result[n++] = '0' + (second / 10);
- result[n++] = '0' + (second % 10);
+ result[n++] = char('0' + (second / 10));
+ result[n++] = char('0' + (second % 10));
if (uint32_t fractional = subSecondNanoseconds) {
result[n++] = '.';
uint32_t k = 100'000'000;
do {
- result[n++] = '0' + (fractional / k);
+ result[n++] = char('0' + (fractional / k));
fractional %= k;
k /= 10;
} while (fractional);
@@ -1338,28 +1338,28 @@ static PlainDateTime GetISOPartsFromEpoch(const Instant& instant) {
int32_t remainderNs = instant.nanoseconds % 1'000'000;
// Step 3.
- int64_t epochMilliseconds = instant.floorToMilliseconds();
+ double epochMilliseconds = double(instant.floorToMilliseconds());
// Step 4.
- int32_t year = JS::YearFromTime(epochMilliseconds);
+ int32_t year = int32_t(JS::YearFromTime(epochMilliseconds));
// Step 5.
- int32_t month = JS::MonthFromTime(epochMilliseconds) + 1;
+ int32_t month = int32_t(JS::MonthFromTime(epochMilliseconds)) + 1;
// Step 6.
- int32_t day = JS::DayFromTime(epochMilliseconds);
+ int32_t day = int32_t(JS::DayFromTime(epochMilliseconds));
// Step 7.
- int32_t hour = HourFromTime(epochMilliseconds);
+ int32_t hour = int32_t(HourFromTime(epochMilliseconds));
// Step 8.
- int32_t minute = MinFromTime(epochMilliseconds);
+ int32_t minute = int32_t(MinFromTime(epochMilliseconds));
// Step 9.
- int32_t second = SecFromTime(epochMilliseconds);
+ int32_t second = int32_t(SecFromTime(epochMilliseconds));
// Step 10.
- int32_t millisecond = msFromTime(epochMilliseconds);
+ int32_t millisecond = int32_t(msFromTime(epochMilliseconds));
// Step 11.
int32_t microsecond = remainderNs / 1000;
@@ -1389,11 +1389,11 @@ static PlainDateTime BalanceISODateTime(const PlainDateTime& dateTime,
MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
MOZ_ASSERT(std::abs(nanoseconds) < ToNanoseconds(TemporalUnit::Day));
- auto& [date, time] = dateTime;
+ const auto& [date, time] = dateTime;
// Step 1.
auto balancedTime = BalanceTime(time, nanoseconds);
- MOZ_ASSERT(-1 <= balancedTime.days && balancedTime.days <= 1);
+ MOZ_ASSERT(std::abs(balancedTime.days) <= 1);
// Step 2.
auto balancedDate =
@@ -1429,9 +1429,6 @@ static PlainDateTimeObject* GetPlainDateTimeFor(
// Steps 5-7.
auto dateTime =
GetPlainDateTimeFor(ToInstant(unwrappedInstant), offsetNanoseconds);
-
- // FIXME: spec issue - CreateTemporalDateTime is infallible
- // https://github.com/tc39/proposal-temporal/issues/2523
MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
return CreateTemporalDateTime(cx, dateTime, calendar);
@@ -1455,9 +1452,6 @@ PlainDateTime js::temporal::GetPlainDateTimeFor(const Instant& instant,
// Step 6.
auto balanced = BalanceISODateTime(dateTime, offsetNanoseconds);
-
- // FIXME: spec issue - CreateTemporalDateTime is infallible
- // https://github.com/tc39/proposal-temporal/issues/2523
MOZ_ASSERT(ISODateTimeWithinLimits(balanced));
// Step 7.
@@ -1524,9 +1518,6 @@ PlainDateTimeObject* js::temporal::GetPlainDateTimeFor(
if (!GetPlainDateTimeFor(cx, timeZone, instant, &dateTime)) {
return nullptr;
}
-
- // FIXME: spec issue - CreateTemporalDateTime is infallible
- // https://github.com/tc39/proposal-temporal/issues/2523
MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
// Step 7.
@@ -1544,9 +1535,6 @@ PlainDateTimeObject* js::temporal::GetPlainDateTimeFor(
// Steps 1-6.
auto dateTime = GetPlainDateTimeFor(instant, offsetNanoseconds);
-
- // FIXME: spec issue - CreateTemporalDateTime is infallible
- // https://github.com/tc39/proposal-temporal/issues/2523
MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
// Step 7.
@@ -1628,6 +1616,8 @@ static bool GetPossibleInstantsForSlow(
JSContext* cx, Handle<TimeZoneRecord> timeZone,
Handle<Wrapped<PlainDateTimeObject*>> dateTime,
MutableHandle<InstantVector> list) {
+ MOZ_ASSERT(list.empty());
+
// Step 1. (Inlined call to TimeZoneMethodsRecordCall)
Rooted<Value> fval(cx, ObjectValue(*timeZone.getPossibleInstantsFor()));
auto thisv = timeZone.receiver().toObject();
@@ -1647,23 +1637,46 @@ static bool GetPossibleInstantsForSlow(
// Step 4. (Not applicable in our implementation.)
- // Steps 5-6.
+ // Step 5.
+ auto min = Instant::max();
+ auto max = Instant::min();
Rooted<Value> nextValue(cx);
while (true) {
- // Steps 6.a and 6.b.i.
+ // Step 5.a.
bool done;
if (!iterator.next(&nextValue, &done)) {
return false;
}
+
+ // Step 5.b.
if (done) {
- break;
+ // Steps 5.b.i-ii.
+ if (list.length() > 1) {
+ // Steps 5.b.ii.1-4. (Not applicable in our implementation.)
+
+ // Step 5.b.ii.5.
+ constexpr auto nsPerDay =
+ InstantSpan::fromNanoseconds(ToNanoseconds(TemporalUnit::Day));
+ if ((max - min).abs() > nsPerDay) {
+ JS_ReportErrorNumberASCII(
+ cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_TIMEZONE_OFFSET_SHIFT_ONE_DAY);
+ return false;
+ }
+ }
+
+ // Step 5.b.iii.
+ return true;
}
- // Steps 6.b.ii.
+ // Step 5.d. (Reordered)
if (nextValue.isObject()) {
JSObject* obj = &nextValue.toObject();
- if (obj->canUnwrapAs<InstantObject>()) {
- // Step 6.b.iii.
+ if (auto* unwrapped = obj->maybeUnwrapIf<InstantObject>()) {
+ auto instant = ToInstant(unwrapped);
+ min = std::min(min, instant);
+ max = std::max(max, instant);
+
if (!list.append(obj)) {
return false;
}
@@ -1671,17 +1684,14 @@ static bool GetPossibleInstantsForSlow(
}
}
- // Step 6.b.ii.1.
+ // Step 5.c.1.
ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, nextValue,
nullptr, "not an instant");
- // Step 6.b.ii.2.
+ // Step 5.c.2.
iterator.closeThrow();
return false;
}
-
- // Step 7.
- return true;
}
/**
@@ -1714,7 +1724,7 @@ static bool GetPossibleInstantsFor(
}
}
- // Steps 1 and 3-7.
+ // Steps 1 and 3-5.
return GetPossibleInstantsForSlow(cx, timeZone, dateTimeObj, list);
}
@@ -1753,7 +1763,7 @@ bool js::temporal::GetPossibleInstantsFor(
return false;
}
- // Steps 1 and 3-7.
+ // Steps 1 and 3-5.
return GetPossibleInstantsForSlow(cx, timeZone, dateTimeObj, list);
}
@@ -1763,9 +1773,9 @@ bool js::temporal::GetPossibleInstantsFor(
*/
static auto AddTime(const PlainTime& time, int64_t nanoseconds) {
MOZ_ASSERT(IsValidTime(time));
- MOZ_ASSERT(std::abs(nanoseconds) <= 2 * ToNanoseconds(TemporalUnit::Day));
+ MOZ_ASSERT(std::abs(nanoseconds) <= ToNanoseconds(TemporalUnit::Day));
- // Steps 1-7.
+ // Steps 1-3.
return BalanceTime(time, nanoseconds);
}
@@ -1871,79 +1881,80 @@ bool js::temporal::DisambiguatePossibleInstants(
int64_t nanoseconds = offsetAfter - offsetBefore;
// Step 18.
+ if (std::abs(nanoseconds) > ToNanoseconds(TemporalUnit::Day)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_TIMEZONE_OFFSET_SHIFT_ONE_DAY);
+ return false;
+ }
+
+ // Step 19.
if (disambiguation == TemporalDisambiguation::Earlier) {
- // Step 18.a.
+ // Steps 19.a-b.
auto earlierTime = ::AddTime(dateTime.time, -nanoseconds);
- MOZ_ASSERT(std::abs(earlierTime.days) <= 2,
- "subtracting nanoseconds is at most two days");
+ MOZ_ASSERT(std::abs(earlierTime.days) <= 1,
+ "subtracting nanoseconds is at most one day");
- // Step 18.b.
- PlainDate earlierDate;
- if (!AddISODate(cx, dateTime.date, {0, 0, 0, double(earlierTime.days)},
- TemporalOverflow::Constrain, &earlierDate)) {
- return false;
- }
+ // Step 19.c.
+ auto earlierDate = BalanceISODate(dateTime.date.year, dateTime.date.month,
+ dateTime.date.day + earlierTime.days);
- // Step 18.c.
+ // Step 19.d.
Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
Rooted<PlainDateTimeWithCalendar> earlierDateTime(
cx,
PlainDateTimeWithCalendar{{earlierDate, earlierTime.time}, calendar});
- // Step 18.d.
+ // Step 19.e.
Rooted<InstantVector> earlierInstants(cx, InstantVector(cx));
if (!GetPossibleInstantsFor(cx, timeZone, earlierDateTime,
&earlierInstants)) {
return false;
}
- // Step 18.e.
+ // Step 19.f.
if (earlierInstants.empty()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_TIMEZONE_INSTANT_AMBIGUOUS);
return false;
}
- // Step 18.f.
+ // Step 19.g.
result.set(earlierInstants[0]);
return true;
}
- // Step 19.
+ // Step 20.
MOZ_ASSERT(disambiguation == TemporalDisambiguation::Compatible ||
disambiguation == TemporalDisambiguation::Later);
- // Step 20.
+ // Steps 21-22.
auto laterTime = ::AddTime(dateTime.time, nanoseconds);
- MOZ_ASSERT(std::abs(laterTime.days) <= 2,
- "adding nanoseconds is at most two days");
+ MOZ_ASSERT(std::abs(laterTime.days) <= 1,
+ "adding nanoseconds is at most one day");
- // Step 21.
- PlainDate laterDate;
- if (!AddISODate(cx, dateTime.date, {0, 0, 0, double(laterTime.days)},
- TemporalOverflow::Constrain, &laterDate)) {
- return false;
- }
+ // Step 23.
+ auto laterDate = BalanceISODate(dateTime.date.year, dateTime.date.month,
+ dateTime.date.day + laterTime.days);
- // Step 22.
+ // Step 24.
Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
Rooted<PlainDateTimeWithCalendar> laterDateTime(
cx, PlainDateTimeWithCalendar{{laterDate, laterTime.time}, calendar});
- // Step 23.
+ // Step 25.
Rooted<InstantVector> laterInstants(cx, InstantVector(cx));
if (!GetPossibleInstantsFor(cx, timeZone, laterDateTime, &laterInstants)) {
return false;
}
- // Steps 24-25.
+ // Steps 26-27.
if (laterInstants.empty()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_TIMEZONE_INSTANT_AMBIGUOUS);
return false;
}
- // Step 26.
+ // Step 28.
size_t last = laterInstants.length() - 1;
result.set(laterInstants[last]);
return true;
@@ -2282,11 +2293,14 @@ static bool TimeZone_getOffsetNanosecondsFor(JSContext* cx, unsigned argc,
static bool TimeZone_getOffsetStringFor(JSContext* cx, const CallArgs& args) {
Rooted<TimeZoneValue> timeZone(cx, &args.thisv().toObject());
- // FIXME: spec issue - CreateTimeZoneMethodsRecord called before
- // ToTemporalInstant whereas TimeZone.p.{getPlainDateTimeFor,getInstantFor}
- // first convert the input arguments.
-
// Step 3.
+ Rooted<Wrapped<InstantObject*>> instant(cx,
+ ToTemporalInstant(cx, args.get(0)));
+ if (!instant) {
+ return false;
+ }
+
+ // Step 4.
Rooted<TimeZoneRecord> timeZoneRec(cx);
if (!CreateTimeZoneMethodsRecord(cx, timeZone,
{
@@ -2296,13 +2310,6 @@ static bool TimeZone_getOffsetStringFor(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 4.
- Rooted<Wrapped<InstantObject*>> instant(cx,
- ToTemporalInstant(cx, args.get(0)));
- if (!instant) {
- return false;
- }
-
// Step 5.
JSString* str = GetOffsetStringFor(cx, timeZoneRec, instant);
if (!str) {
diff --git a/js/src/builtin/temporal/TimeZone.h b/js/src/builtin/temporal/TimeZone.h
index f1d0bf3f1f..f13421111a 100644
--- a/js/src/builtin/temporal/TimeZone.h
+++ b/js/src/builtin/temporal/TimeZone.h
@@ -8,6 +8,7 @@
#define builtin_temporal_TimeZone_h
#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
#include "mozilla/EnumSet.h"
#include <stddef.h>
@@ -142,7 +143,7 @@ namespace js::temporal {
*
* Option 2 is a bit easier to implement, so we use this approach for now.
*/
-class TimeZoneValue final {
+class MOZ_STACK_CLASS TimeZoneValue final {
JSObject* object_ = nullptr;
public:
@@ -252,7 +253,7 @@ enum class TimeZoneMethod {
GetPossibleInstantsFor,
};
-class TimeZoneRecord {
+class MOZ_STACK_CLASS TimeZoneRecord final {
TimeZoneValue receiver_;
// Null unless non-builtin time zone methods are used.
diff --git a/js/src/builtin/temporal/ToString.cpp b/js/src/builtin/temporal/ToString.cpp
index c789c5e95c..e13ad8eb03 100644
--- a/js/src/builtin/temporal/ToString.cpp
+++ b/js/src/builtin/temporal/ToString.cpp
@@ -169,7 +169,7 @@ static void FormatFractionalSeconds(TemporalStringBuilder& result,
result.append('.');
// Steps 1.b-c.
- uint32_t k = 100'000'000;
+ int32_t k = 100'000'000;
do {
result.append(char('0' + (subSecondNanoseconds / k)));
subSecondNanoseconds %= k;
@@ -186,7 +186,7 @@ static void FormatFractionalSeconds(TemporalStringBuilder& result,
result.append('.');
// Steps 2.b-c.
- uint32_t k = 100'000'000;
+ int32_t k = 100'000'000;
for (uint8_t i = 0; i < p; i++) {
result.append(char('0' + (subSecondNanoseconds / k)));
subSecondNanoseconds %= k;
@@ -274,7 +274,7 @@ static int32_t RoundNanosecondsToMinutes(int64_t offsetNanoseconds) {
if (std::abs(remainder * 2) >= increment) {
quotient += (offsetNanoseconds > 0 ? 1 : -1);
}
- return quotient;
+ return int32_t(quotient);
}
/**
@@ -636,11 +636,8 @@ JSString* js::temporal::TemporalZonedDateTimeToString(
// Steps 1-3. (Not applicable in our implementation.)
// Step 4.
- Instant ns;
- if (!RoundTemporalInstant(cx, zonedDateTime.instant(), increment, unit,
- roundingMode, &ns)) {
- return nullptr;
- }
+ auto ns = RoundTemporalInstant(zonedDateTime.instant(), increment, unit,
+ roundingMode);
// Step 5.
auto timeZone = zonedDateTime.timeZone();
diff --git a/js/src/builtin/temporal/ZonedDateTime.cpp b/js/src/builtin/temporal/ZonedDateTime.cpp
index 92842a9626..e75b368ba9 100644
--- a/js/src/builtin/temporal/ZonedDateTime.cpp
+++ b/js/src/builtin/temporal/ZonedDateTime.cpp
@@ -10,6 +10,7 @@
#include "mozilla/Maybe.h"
#include <cstdlib>
+#include <limits>
#include <utility>
#include "jspubtd.h"
@@ -18,6 +19,7 @@
#include "builtin/temporal/Calendar.h"
#include "builtin/temporal/Duration.h"
#include "builtin/temporal/Instant.h"
+#include "builtin/temporal/Int96.h"
#include "builtin/temporal/PlainDate.h"
#include "builtin/temporal/PlainDateTime.h"
#include "builtin/temporal/PlainMonthDay.h"
@@ -119,7 +121,8 @@ bool js::temporal::InterpretISODateTimeOffset(
// Step 5.
if (offsetBehaviour == OffsetBehaviour::Wall ||
- offsetOption == TemporalOffset::Ignore) {
+ (offsetBehaviour == OffsetBehaviour::Option &&
+ offsetOption == TemporalOffset::Ignore)) {
// Steps 5.a-b.
return GetInstantFor(cx, timeZone, temporalDateTime, disambiguation,
result);
@@ -127,7 +130,8 @@ bool js::temporal::InterpretISODateTimeOffset(
// Step 6.
if (offsetBehaviour == OffsetBehaviour::Exact ||
- offsetOption == TemporalOffset::Use) {
+ (offsetBehaviour == OffsetBehaviour::Option &&
+ offsetOption == TemporalOffset::Use)) {
// Step 6.a.
auto epochNanoseconds = GetUTCEpochNanoseconds(
dateTime, InstantSpan::fromNanoseconds(offsetNanoseconds));
@@ -151,27 +155,21 @@ bool js::temporal::InterpretISODateTimeOffset(
MOZ_ASSERT(offsetOption == TemporalOffset::Prefer ||
offsetOption == TemporalOffset::Reject);
- // FIXME: spec issue - duplicate assertion
-
// Step 9.
- MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
- timeZone, TimeZoneMethod::GetPossibleInstantsFor));
-
- // Step 10.
Rooted<InstantVector> possibleInstants(cx, InstantVector(cx));
if (!GetPossibleInstantsFor(cx, timeZone, temporalDateTime,
&possibleInstants)) {
return false;
}
- // Step 11.
+ // Step 10.
if (!possibleInstants.empty()) {
- // Step 11.a.
+ // Step 10.a.
Rooted<Wrapped<InstantObject*>> candidate(cx);
for (size_t i = 0; i < possibleInstants.length(); i++) {
candidate = possibleInstants[i];
- // Step 11.a.i.
+ // Step 10.a.i.
int64_t candidateNanoseconds;
if (!GetOffsetNanosecondsFor(cx, timeZone, candidate,
&candidateNanoseconds)) {
@@ -180,7 +178,7 @@ bool js::temporal::InterpretISODateTimeOffset(
MOZ_ASSERT(std::abs(candidateNanoseconds) <
ToNanoseconds(TemporalUnit::Day));
- // Step 11.a.ii.
+ // Step 10.a.ii.
if (candidateNanoseconds == offsetNanoseconds) {
auto* unwrapped = candidate.unwrap(cx);
if (!unwrapped) {
@@ -191,20 +189,20 @@ bool js::temporal::InterpretISODateTimeOffset(
return true;
}
- // Step 11.a.iii.
+ // Step 10.a.iii.
if (matchBehaviour == MatchBehaviour::MatchMinutes) {
- // Step 11.a.iii.1.
+ // Step 10.a.iii.1.
int64_t roundedCandidateNanoseconds =
RoundNanosecondsToMinutesIncrement(candidateNanoseconds);
- // Step 11.a.iii.2.
+ // Step 10.a.iii.2.
if (roundedCandidateNanoseconds == offsetNanoseconds) {
auto* unwrapped = candidate.unwrap(cx);
if (!unwrapped) {
return false;
}
- // Step 11.a.iii.2.a.
+ // Step 10.a.iii.2.a.
*result = ToInstant(unwrapped);
return true;
}
@@ -212,14 +210,14 @@ bool js::temporal::InterpretISODateTimeOffset(
}
}
- // Step 12.
+ // Step 11.
if (offsetOption == TemporalOffset::Reject) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_ZONED_DATE_TIME_NO_TIME_FOUND);
return false;
}
- // Step 13.
+ // Step 12.
Rooted<Wrapped<InstantObject*>> instant(cx);
if (!DisambiguatePossibleInstants(cx, possibleInstants, timeZone,
ToPlainDateTime(temporalDateTime),
@@ -232,7 +230,7 @@ bool js::temporal::InterpretISODateTimeOffset(
return false;
}
- // Step 14.
+ // Step 13.
*result = ToInstant(unwrappedInstant);
return true;
}
@@ -430,19 +428,19 @@ static bool ToTemporalZonedDateTime(JSContext* cx, Handle<Value> item,
bool isUTC;
bool hasOffset;
int64_t timeZoneOffset;
- Rooted<ParsedTimeZone> timeZoneString(cx);
+ Rooted<ParsedTimeZone> timeZoneAnnotation(cx);
Rooted<JSString*> calendarString(cx);
- if (!ParseTemporalZonedDateTimeString(cx, string, &dateTime, &isUTC,
- &hasOffset, &timeZoneOffset,
- &timeZoneString, &calendarString)) {
+ if (!ParseTemporalZonedDateTimeString(
+ cx, string, &dateTime, &isUTC, &hasOffset, &timeZoneOffset,
+ &timeZoneAnnotation, &calendarString)) {
return false;
}
// Step 6.d.
- MOZ_ASSERT(timeZoneString);
+ MOZ_ASSERT(timeZoneAnnotation);
// Step 6.e.
- if (!ToTemporalTimeZone(cx, timeZoneString, &timeZone)) {
+ if (!ToTemporalTimeZone(cx, timeZoneAnnotation, &timeZone)) {
return false;
}
@@ -624,7 +622,7 @@ struct PlainDateTimeAndInstant {
static bool AddDaysToZonedDateTime(JSContext* cx, const Instant& instant,
const PlainDateTime& dateTime,
Handle<TimeZoneRecord> timeZone,
- Handle<CalendarValue> calendar, double days,
+ Handle<CalendarValue> calendar, int64_t days,
TemporalOverflow overflow,
PlainDateTimeAndInstant* result) {
// Step 1. (Not applicable in our implementation.)
@@ -669,7 +667,7 @@ static bool AddDaysToZonedDateTime(JSContext* cx, const Instant& instant,
bool js::temporal::AddDaysToZonedDateTime(
JSContext* cx, const Instant& instant, const PlainDateTime& dateTime,
Handle<TimeZoneRecord> timeZone, Handle<CalendarValue> calendar,
- double days, TemporalOverflow overflow, Instant* result) {
+ int64_t days, TemporalOverflow overflow, Instant* result) {
// Steps 1-7.
PlainDateTimeAndInstant dateTimeAndInstant;
if (!::AddDaysToZonedDateTime(cx, instant, dateTime, timeZone, calendar, days,
@@ -689,7 +687,7 @@ bool js::temporal::AddDaysToZonedDateTime(JSContext* cx, const Instant& instant,
const PlainDateTime& dateTime,
Handle<TimeZoneRecord> timeZone,
Handle<CalendarValue> calendar,
- double days, Instant* result) {
+ int64_t days, Instant* result) {
// Step 2.
auto overflow = TemporalOverflow::Constrain;
@@ -700,18 +698,16 @@ bool js::temporal::AddDaysToZonedDateTime(JSContext* cx, const Instant& instant,
/**
* AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months,
- * weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds
- * [ , precalculatedPlainDateTime [ , options ] ] )
+ * weeks, days, norm [ , precalculatedPlainDateTime [ , options ] ] )
*/
static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds,
Handle<TimeZoneRecord> timeZone,
Handle<CalendarRecord> calendar,
- const Duration& duration,
+ const NormalizedDuration& duration,
mozilla::Maybe<const PlainDateTime&> dateTime,
Handle<JSObject*> maybeOptions, Instant* result) {
MOZ_ASSERT(IsValidEpochInstant(epochNanoseconds));
- MOZ_ASSERT(IsValidDuration(duration.date()));
- MOZ_ASSERT(IsValidDuration(duration.time()));
+ MOZ_ASSERT(IsValidDuration(duration));
// Step 1.
MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
@@ -725,10 +721,9 @@ static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds,
// Steps 4-5. (Not applicable in our implementation)
// Step 6.
- if (duration.years == 0 && duration.months == 0 && duration.weeks == 0 &&
- duration.days == 0) {
+ if (duration.date == DateDuration{}) {
// Step 6.a.
- return AddInstant(cx, epochNanoseconds, duration, result);
+ return AddInstant(cx, epochNanoseconds, duration.time, result);
}
// Step 7. (Not applicable in our implementation)
@@ -748,7 +743,8 @@ static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds,
auto& [date, time] = temporalDateTime;
// Step 10.
- if (duration.years == 0 && duration.months == 0 && duration.weeks == 0) {
+ if (duration.date.years == 0 && duration.date.months == 0 &&
+ duration.date.weeks == 0) {
// Step 10.a.
auto overflow = TemporalOverflow::Constrain;
if (maybeOptions) {
@@ -760,13 +756,13 @@ static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds,
// Step 10.b.
Instant intermediate;
if (!AddDaysToZonedDateTime(cx, epochNanoseconds, temporalDateTime,
- timeZone, calendar.receiver(), duration.days,
- overflow, &intermediate)) {
+ timeZone, calendar.receiver(),
+ duration.date.days, overflow, &intermediate)) {
return false;
}
// Step 10.c.
- return AddInstant(cx, intermediate, duration.time(), result);
+ return AddInstant(cx, intermediate, duration.time, result);
}
// Step 11.
@@ -777,13 +773,13 @@ static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds,
const auto& datePart = date;
// Step 13.
- auto dateDuration = duration.date();
+ const auto& dateDuration = duration.date;
// Step 14.
PlainDate addedDate;
if (maybeOptions) {
- if (!CalendarDateAdd(cx, calendar, datePart, dateDuration, maybeOptions,
- &addedDate)) {
+ if (!temporal::CalendarDateAdd(cx, calendar, datePart, dateDuration,
+ maybeOptions, &addedDate)) {
return false;
}
} else {
@@ -808,18 +804,17 @@ static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds,
}
// Step 17.
- return AddInstant(cx, intermediateInstant, duration.time(), result);
+ return AddInstant(cx, intermediateInstant, duration.time, result);
}
/**
* AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months,
- * weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds
- * [ , precalculatedPlainDateTime [ , options ] ] )
+ * weeks, days, norm [ , precalculatedPlainDateTime [ , options ] ] )
*/
static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds,
Handle<TimeZoneRecord> timeZone,
Handle<CalendarRecord> calendar,
- const Duration& duration,
+ const NormalizedDuration& duration,
Handle<JSObject*> maybeOptions, Instant* result) {
return ::AddZonedDateTime(cx, epochNanoseconds, timeZone, calendar, duration,
mozilla::Nothing(), maybeOptions, result);
@@ -827,84 +822,94 @@ static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds,
/**
* AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months,
- * weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds
- * [ , precalculatedPlainDateTime [ , options ] ] )
+ * weeks, days, norm [ , precalculatedPlainDateTime [ , options ] ] )
*/
bool js::temporal::AddZonedDateTime(JSContext* cx,
const Instant& epochNanoseconds,
Handle<TimeZoneRecord> timeZone,
Handle<CalendarRecord> calendar,
- const Duration& duration, Instant* result) {
+ const NormalizedDuration& duration,
+ Instant* result) {
return ::AddZonedDateTime(cx, epochNanoseconds, timeZone, calendar, duration,
mozilla::Nothing(), nullptr, result);
}
/**
* AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months,
- * weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds
- * [ , precalculatedPlainDateTime [ , options ] ] )
+ * weeks, days, norm [ , precalculatedPlainDateTime [ , options ] ] )
*/
-bool js::temporal::AddZonedDateTime(
- JSContext* cx, const Instant& epochNanoseconds,
- Handle<TimeZoneRecord> timeZone, Handle<CalendarRecord> calendar,
- const Duration& duration, const PlainDateTime& dateTime, Instant* result) {
+bool js::temporal::AddZonedDateTime(JSContext* cx,
+ const Instant& epochNanoseconds,
+ Handle<TimeZoneRecord> timeZone,
+ Handle<CalendarRecord> calendar,
+ const NormalizedDuration& duration,
+ const PlainDateTime& dateTime,
+ Instant* result) {
return ::AddZonedDateTime(cx, epochNanoseconds, timeZone, calendar, duration,
mozilla::SomeRef(dateTime), nullptr, result);
}
-double js::temporal::NanosecondsAndDays::daysNumber() const {
- if (days) {
- return BigInt::numberValue(days);
- }
- return double(daysInt);
+/**
+ * AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months,
+ * weeks, days, norm [ , precalculatedPlainDateTime [ , options ] ] )
+ */
+bool js::temporal::AddZonedDateTime(JSContext* cx,
+ const Instant& epochNanoseconds,
+ Handle<TimeZoneRecord> timeZone,
+ Handle<CalendarRecord> calendar,
+ const DateDuration& duration,
+ Instant* result) {
+ return ::AddZonedDateTime(cx, epochNanoseconds, timeZone, calendar,
+ {duration, {}}, mozilla::Nothing(), nullptr,
+ result);
}
-void js::temporal::NanosecondsAndDays::trace(JSTracer* trc) {
- if (days) {
- TraceRoot(trc, &days, "NanosecondsAndDays::days");
- }
+/**
+ * AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months,
+ * weeks, days, norm [ , precalculatedPlainDateTime [ , options ] ] )
+ */
+bool js::temporal::AddZonedDateTime(JSContext* cx,
+ const Instant& epochNanoseconds,
+ Handle<TimeZoneRecord> timeZone,
+ Handle<CalendarRecord> calendar,
+ const DateDuration& duration,
+ const PlainDateTime& dateTime,
+ Instant* result) {
+ return ::AddZonedDateTime(cx, epochNanoseconds, timeZone, calendar,
+ {duration, {}}, mozilla::SomeRef(dateTime), nullptr,
+ result);
}
/**
- * NanosecondsToDays ( nanoseconds, zonedRelativeTo, timeZoneRec [ ,
+ * NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ ,
* precalculatedPlainDateTime ] )
*/
-static bool NanosecondsToDays(
- JSContext* cx, const InstantSpan& nanoseconds,
+static bool NormalizedTimeDurationToDays(
+ JSContext* cx, const NormalizedTimeDuration& duration,
Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone,
mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
- MutableHandle<NanosecondsAndDays> result) {
- MOZ_ASSERT(IsValidInstantSpan(nanoseconds));
+ NormalizedTimeAndDays* result) {
+ MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
// Step 1.
- if (nanoseconds == InstantSpan{}) {
- result.set(NanosecondsAndDays::from(
- int64_t(0), InstantSpan{},
- InstantSpan::fromNanoseconds(ToNanoseconds(TemporalUnit::Day))));
- return true;
- }
+ int32_t sign = NormalizedTimeDurationSign(duration);
// Step 2.
- int32_t sign = nanoseconds < InstantSpan{} ? -1 : 1;
+ if (sign == 0) {
+ *result = {int64_t(0), int64_t(0), ToNanoseconds(TemporalUnit::Day)};
+ return true;
+ }
// Step 3.
- auto startNs = zonedRelativeTo.instant();
- auto calendar = zonedRelativeTo.calendar();
+ const auto& startNs = zonedRelativeTo.instant();
// Step 5.
- //
- // NB: This addition can't overflow, because we've checked that |nanoseconds|
- // can be represented as an InstantSpan value.
- auto endNs = startNs + nanoseconds;
-
- // Step 6.
- if (!IsValidEpochInstant(endNs)) {
- JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
- JSMSG_TEMPORAL_INSTANT_INVALID);
+ Instant endNs;
+ if (!AddInstant(cx, startNs, duration, &endNs)) {
return false;
}
- // Steps 4 and 8.
+ // Steps 4 and 7.
PlainDateTime startDateTime;
if (!precalculatedPlainDateTime) {
if (!GetPlainDateTimeFor(cx, timeZone, startNs, &startDateTime)) {
@@ -914,145 +919,157 @@ static bool NanosecondsToDays(
startDateTime = *precalculatedPlainDateTime;
}
- // Steps 7 and 9.
+ // Steps 6 and 8.
PlainDateTime endDateTime;
if (!GetPlainDateTimeFor(cx, timeZone, endNs, &endDateTime)) {
return false;
}
- // Steps 10-11. (Not applicable in our implementation.)
+ // Steps 9-10. (Not applicable in our implementation.)
- // Step 12.
- //
- // Overflows in step 21 can be safely ignored, because they take too long to
- // happen for int64.
- int64_t days = DaysUntil(startDateTime.date, endDateTime.date);
+ // Step 11.
+ int32_t days = DaysUntil(startDateTime.date, endDateTime.date);
+ MOZ_ASSERT(std::abs(days) <= MaxEpochDaysDuration);
- // Step 13.
+ // Step 12.
int32_t timeSign = CompareTemporalTime(startDateTime.time, endDateTime.time);
- // Steps 14-15.
+ // Steps 13-14.
if (days > 0 && timeSign > 0) {
days -= 1;
} else if (days < 0 && timeSign < 0) {
days += 1;
}
- // Step 16.
+ // Step 15.
PlainDateTimeAndInstant relativeResult;
- if (!::AddDaysToZonedDateTime(cx, startNs, startDateTime, timeZone, calendar,
- days, TemporalOverflow::Constrain,
- &relativeResult)) {
+ if (!::AddDaysToZonedDateTime(cx, startNs, startDateTime, timeZone,
+ zonedRelativeTo.calendar(), days,
+ TemporalOverflow::Constrain, &relativeResult)) {
return false;
}
MOZ_ASSERT(IsValidISODateTime(relativeResult.dateTime));
MOZ_ASSERT(IsValidEpochInstant(relativeResult.instant));
- // Step 17.
- if (sign > 0) {
- // Step 17.a.
- while (days > 0 && relativeResult.instant > endNs) {
- // This loop can iterate indefinitely when given a specially crafted
- // time zone object, so we need to check for interrupts.
- if (!CheckForInterrupt(cx)) {
- return false;
- }
-
- // Step 17.a.i.
- days -= 1;
+ // Step 16.
+ if (sign > 0 && days > 0 && relativeResult.instant > endNs) {
+ // Step 16.a.
+ days -= 1;
- // Step 17.a.ii.
- if (!::AddDaysToZonedDateTime(cx, startNs, startDateTime, timeZone,
- calendar, days, TemporalOverflow::Constrain,
- &relativeResult)) {
- return false;
- }
- MOZ_ASSERT(IsValidISODateTime(relativeResult.dateTime));
- MOZ_ASSERT(IsValidEpochInstant(relativeResult.instant));
+ // Step 16.b.
+ if (!::AddDaysToZonedDateTime(
+ cx, startNs, startDateTime, timeZone, zonedRelativeTo.calendar(),
+ days, TemporalOverflow::Constrain, &relativeResult)) {
+ return false;
+ }
+ MOZ_ASSERT(IsValidISODateTime(relativeResult.dateTime));
+ MOZ_ASSERT(IsValidEpochInstant(relativeResult.instant));
+
+ // Step 16.c.
+ if (days > 0 && relativeResult.instant > endNs) {
+ JS_ReportErrorNumberASCII(
+ cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_ZONED_DATE_TIME_INCONSISTENT_INSTANT);
+ return false;
}
-
MOZ_ASSERT_IF(days > 0, relativeResult.instant <= endNs);
}
MOZ_ASSERT_IF(days == 0, relativeResult.instant == startNs);
- // Step 18.
+ // Step 17. (Inlined NormalizedTimeDurationFromEpochNanosecondsDifference)
auto ns = endNs - relativeResult.instant;
MOZ_ASSERT(IsValidInstantSpan(ns));
- // Steps 19-21.
- InstantSpan dayLengthNs{};
- while (true) {
- // This loop can iterate indefinitely when given a specially crafted time
- // zone object, so we need to check for interrupts.
- if (!CheckForInterrupt(cx)) {
- return false;
- }
+ // Step 18.
+ PlainDateTimeAndInstant oneDayFarther;
+ if (!::AddDaysToZonedDateTime(cx, relativeResult.instant,
+ relativeResult.dateTime, timeZone,
+ zonedRelativeTo.calendar(), sign,
+ TemporalOverflow::Constrain, &oneDayFarther)) {
+ return false;
+ }
+ MOZ_ASSERT(IsValidISODateTime(oneDayFarther.dateTime));
+ MOZ_ASSERT(IsValidEpochInstant(oneDayFarther.instant));
+
+ // Step 19. (Inlined NormalizedTimeDurationFromEpochNanosecondsDifference)
+ auto dayLengthNs = oneDayFarther.instant - relativeResult.instant;
+ MOZ_ASSERT(IsValidInstantSpan(dayLengthNs));
+
+ // clang-format off
+ //
+ // ns = endNs - relativeResult.instant
+ // dayLengthNs = oneDayFarther.instant - relativeResult.instant
+ // oneDayLess = ns - dayLengthNs
+ // = (endNs - relativeResult.instant) - (oneDayFarther.instant - relativeResult.instant)
+ // = endNs - relativeResult.instant - oneDayFarther.instant + relativeResult.instant
+ // = endNs - oneDayFarther.instant
+ //
+ // |endNs| and |oneDayFarther.instant| are both valid epoch instant values,
+ // so the difference |oneDayLess| is a valid epoch instant difference value.
+ //
+ // clang-format on
+ // Step 20. (Inlined SubtractNormalizedTimeDuration)
+ auto oneDayLess = ns - dayLengthNs;
+ MOZ_ASSERT(IsValidInstantSpan(oneDayLess));
+ MOZ_ASSERT(oneDayLess == (endNs - oneDayFarther.instant));
+
+ // Step 21.
+ if (oneDayLess == InstantSpan{} ||
+ ((oneDayLess < InstantSpan{}) == (sign < 0))) {
// Step 21.a.
+ ns = oneDayLess;
+
+ // Step 21.b.
+ relativeResult = oneDayFarther;
+
+ // Step 21.c.
+ days += sign;
+
+ // Step 21.d.
PlainDateTimeAndInstant oneDayFarther;
if (!::AddDaysToZonedDateTime(
cx, relativeResult.instant, relativeResult.dateTime, timeZone,
- calendar, sign, TemporalOverflow::Constrain, &oneDayFarther)) {
+ zonedRelativeTo.calendar(), sign, TemporalOverflow::Constrain,
+ &oneDayFarther)) {
return false;
}
MOZ_ASSERT(IsValidISODateTime(oneDayFarther.dateTime));
MOZ_ASSERT(IsValidEpochInstant(oneDayFarther.instant));
- // Step 21.b.
+ // Step 21.e. (Inlined NormalizedTimeDurationFromEpochNanosecondsDifference)
dayLengthNs = oneDayFarther.instant - relativeResult.instant;
MOZ_ASSERT(IsValidInstantSpan(dayLengthNs));
// clang-format off
//
- // First iteration:
- //
- // ns = endNs - relativeResult.instant
- // dayLengthNs = oneDayFarther.instant - relativeResult.instant
- // diff = ns - dayLengthNs
- // = (endNs - relativeResult.instant) - (oneDayFarther.instant - relativeResult.instant)
- // = endNs - relativeResult.instant - oneDayFarther.instant + relativeResult.instant
- // = endNs - oneDayFarther.instant
- //
- // Second iteration:
- //
- // ns = diff'
+ // ns = oneDayLess'
// = endNs - oneDayFarther.instant'
// relativeResult.instant = oneDayFarther.instant'
// dayLengthNs = oneDayFarther.instant - relativeResult.instant
// = oneDayFarther.instant - oneDayFarther.instant'
- // diff = ns - dayLengthNs
- // = (endNs - oneDayFarther.instant') - (oneDayFarther.instant - oneDayFarther.instant')
- // = endNs - oneDayFarther.instant' - oneDayFarther.instant + oneDayFarther.instant'
- // = endNs - oneDayFarther.instant
- //
- // Where |diff'| and |oneDayFarther.instant'| denote the variables from the
- // previous iteration.
+ // oneDayLess = ns - dayLengthNs
+ // = (endNs - oneDayFarther.instant') - (oneDayFarther.instant - oneDayFarther.instant')
+ // = endNs - oneDayFarther.instant' - oneDayFarther.instant + oneDayFarther.instant'
+ // = endNs - oneDayFarther.instant
//
- // This repeats for all following iterations.
+ // Where |oneDayLess'| and |oneDayFarther.instant'| denote the variables
+ // from before this if-statement block.
//
// |endNs| and |oneDayFarther.instant| are both valid epoch instant values,
- // so the difference is a valid epoch instant difference value, too.
+ // so the difference |oneDayLess| is a valid epoch instant difference value.
//
// clang-format on
- // Step 21.c.
- auto diff = ns - dayLengthNs;
- MOZ_ASSERT(IsValidInstantSpan(diff));
- MOZ_ASSERT(diff == (endNs - oneDayFarther.instant));
-
- if (diff == InstantSpan{} || ((diff < InstantSpan{}) == (sign < 0))) {
- // Step 21.c.i.
- ns = diff;
-
- // Step 21.c.ii.
- relativeResult = oneDayFarther;
-
- // Step 21.c.iii.
- days += sign;
- } else {
- // Step 21.d.
- break;
+ // Step 21.f.
+ auto oneDayLess = ns - dayLengthNs;
+ if (oneDayLess == InstantSpan{} ||
+ ((oneDayLess < InstantSpan{}) == (sign < 0))) {
+ JS_ReportErrorNumberASCII(
+ cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_ZONED_DATE_TIME_INCONSISTENT_INSTANT);
+ return false;
}
}
@@ -1075,15 +1092,6 @@ static bool NanosecondsToDays(
MOZ_ASSERT(IsValidInstantSpan(dayLengthNs));
MOZ_ASSERT(IsValidInstantSpan(ns));
- // FIXME: spec issue - rewrite steps 24-25 as:
- //
- // If sign = -1, then
- // If nanoseconds > 0, throw a RangeError.
- // Else,
- // Assert: nanoseconds ≥ 0.
- //
- // https://github.com/tc39/proposal-temporal/issues/2530
-
// Steps 24-25.
if (sign < 0) {
if (ns > InstantSpan{}) {
@@ -1096,39 +1104,57 @@ static bool NanosecondsToDays(
MOZ_ASSERT(ns >= InstantSpan{});
}
- // Step 26.
- MOZ_ASSERT(ns.abs() < dayLengthNs.abs());
+ // Steps 26-27.
+ dayLengthNs = dayLengthNs.abs();
+ MOZ_ASSERT(ns.abs() < dayLengthNs);
- // Step 27.
- result.set(NanosecondsAndDays::from(days, ns, dayLengthNs.abs()));
+ // Step 28.
+ constexpr auto maxDayLength = Int128{1} << 53;
+ auto dayLengthNanos = dayLengthNs.toNanoseconds();
+ if (dayLengthNanos >= maxDayLength) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_ZONED_DATE_TIME_INCORRECT_SIGN,
+ "days");
+ return false;
+ }
+
+ auto timeNanos = ns.toNanoseconds();
+ MOZ_ASSERT(timeNanos == Int128{int64_t(timeNanos)},
+ "abs(ns) < dayLengthNs < 2**53 implies that |ns| fits in int64");
+
+ // Step 29.
+ static_assert(std::numeric_limits<decltype(days)>::max() <=
+ ((int64_t(1) << 53) / (24 * 60 * 60)));
+
+ // Step 30.
+ *result = {int64_t(days), int64_t(timeNanos), int64_t(dayLengthNanos)};
return true;
}
/**
- * NanosecondsToDays ( nanoseconds, zonedRelativeTo, timeZoneRec [ ,
+ * NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ ,
* precalculatedPlainDateTime ] )
*/
-bool js::temporal::NanosecondsToDays(JSContext* cx,
- const InstantSpan& nanoseconds,
- Handle<ZonedDateTime> zonedRelativeTo,
- Handle<TimeZoneRecord> timeZone,
- MutableHandle<NanosecondsAndDays> result) {
- return ::NanosecondsToDays(cx, nanoseconds, zonedRelativeTo, timeZone,
- mozilla::Nothing(), result);
+bool js::temporal::NormalizedTimeDurationToDays(
+ JSContext* cx, const NormalizedTimeDuration& duration,
+ Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone,
+ NormalizedTimeAndDays* result) {
+ return ::NormalizedTimeDurationToDays(cx, duration, zonedRelativeTo, timeZone,
+ mozilla::Nothing(), result);
}
/**
- * NanosecondsToDays ( nanoseconds, zonedRelativeTo, timeZoneRec [ ,
+ * NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ ,
* precalculatedPlainDateTime ] )
*/
-bool js::temporal::NanosecondsToDays(
- JSContext* cx, const InstantSpan& nanoseconds,
+bool js::temporal::NormalizedTimeDurationToDays(
+ JSContext* cx, const NormalizedTimeDuration& duration,
Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone,
const PlainDateTime& precalculatedPlainDateTime,
- MutableHandle<NanosecondsAndDays> result) {
- return ::NanosecondsToDays(cx, nanoseconds, zonedRelativeTo, timeZone,
- mozilla::SomeRef(precalculatedPlainDateTime),
- result);
+ NormalizedTimeAndDays* result) {
+ return ::NormalizedTimeDurationToDays(
+ cx, duration, zonedRelativeTo, timeZone,
+ mozilla::SomeRef(precalculatedPlainDateTime), result);
}
/**
@@ -1139,27 +1165,22 @@ static bool DifferenceZonedDateTime(
JSContext* cx, const Instant& ns1, const Instant& ns2,
Handle<TimeZoneRecord> timeZone, Handle<CalendarRecord> calendar,
TemporalUnit largestUnit, Handle<PlainObject*> maybeOptions,
- mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
- Duration* result) {
+ const PlainDateTime& precalculatedPlainDateTime,
+ NormalizedDuration* result) {
MOZ_ASSERT(IsValidEpochInstant(ns1));
MOZ_ASSERT(IsValidEpochInstant(ns2));
// Steps 1.
if (ns1 == ns2) {
- *result = {};
+ *result = CreateNormalizedDurationRecord({}, {});
return true;
}
+ // FIXME: spec issue - precalculatedPlainDateTime is never undefined
+ // https://github.com/tc39/proposal-temporal/issues/2822
+
// Steps 2-3.
- PlainDateTime startDateTime;
- if (!precalculatedPlainDateTime) {
- // Steps 2.a-b.
- if (!GetPlainDateTimeFor(cx, timeZone, ns1, &startDateTime)) {
- return false;
- }
- } else {
- startDateTime = *precalculatedPlainDateTime;
- }
+ const auto& startDateTime = precalculatedPlainDateTime;
// Steps 4-5.
PlainDateTime endDateTime;
@@ -1168,65 +1189,100 @@ static bool DifferenceZonedDateTime(
}
// Step 6.
- Duration dateDifference;
- if (maybeOptions) {
- if (!DifferenceISODateTime(cx, startDateTime, endDateTime, calendar,
- largestUnit, maybeOptions, &dateDifference)) {
- return false;
- }
- } else {
- if (!DifferenceISODateTime(cx, startDateTime, endDateTime, calendar,
- largestUnit, &dateDifference)) {
- return false;
- }
- }
+ int32_t sign = (ns2 - ns1 < InstantSpan{}) ? -1 : 1;
// Step 7.
- Instant intermediateNs;
- if (!AddZonedDateTime(cx, ns1, timeZone, calendar,
- {
- dateDifference.years,
- dateDifference.months,
- dateDifference.weeks,
- },
- startDateTime, &intermediateNs)) {
- return false;
- }
- MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
+ int32_t maxDayCorrection = 1 + (sign > 0);
// Step 8.
- auto timeRemainder = ns2 - intermediateNs;
- MOZ_ASSERT(IsValidInstantSpan(timeRemainder));
+ int32_t dayCorrection = 0;
// Step 9.
- Rooted<ZonedDateTime> intermediate(
- cx,
- ZonedDateTime{intermediateNs, timeZone.receiver(), calendar.receiver()});
+ auto timeDuration = DifferenceTime(startDateTime.time, endDateTime.time);
// Step 10.
- Rooted<NanosecondsAndDays> nanosAndDays(cx);
- if (!NanosecondsToDays(cx, timeRemainder, intermediate, timeZone,
- &nanosAndDays)) {
- return false;
+ if (NormalizedTimeDurationSign(timeDuration) == -sign) {
+ dayCorrection += 1;
}
- // Step 11.
- TimeDuration timeDifference;
- if (!BalanceTimeDuration(cx, nanosAndDays.nanoseconds(), TemporalUnit::Hour,
- &timeDifference)) {
- return false;
+ // Steps 11-12.
+ Rooted<PlainDateTimeWithCalendar> intermediateDateTime(cx);
+ while (dayCorrection <= maxDayCorrection) {
+ // Step 12.a.
+ auto intermediateDate =
+ BalanceISODate(endDateTime.date.year, endDateTime.date.month,
+ endDateTime.date.day - dayCorrection * sign);
+
+ // FIXME: spec issue - CreateTemporalDateTime is fallible
+ // https://github.com/tc39/proposal-temporal/issues/2824
+
+ // Step 12.b.
+ if (!CreateTemporalDateTime(cx, {intermediateDate, startDateTime.time},
+ calendar.receiver(), &intermediateDateTime)) {
+ return false;
+ }
+
+ // Steps 12.c-d.
+ Instant intermediateInstant;
+ if (!GetInstantFor(cx, timeZone, intermediateDateTime,
+ TemporalDisambiguation::Compatible,
+ &intermediateInstant)) {
+ return false;
+ }
+
+ // Step 12.e.
+ auto norm = NormalizedTimeDurationFromEpochNanosecondsDifference(
+ ns2, intermediateInstant);
+
+ // Step 12.f.
+ int32_t timeSign = NormalizedTimeDurationSign(norm);
+
+ // Step 12.g.
+ if (sign != -timeSign) {
+ // Step 13.a.
+ const auto& date1 = startDateTime.date;
+ MOZ_ASSERT(ISODateTimeWithinLimits(date1));
+
+ // Step 13.b.
+ const auto& date2 = intermediateDate;
+ MOZ_ASSERT(ISODateTimeWithinLimits(date2));
+
+ // Step 13.c.
+ auto dateLargestUnit = std::min(largestUnit, TemporalUnit::Day);
+
+ // Steps 13.d-e.
+ //
+ // The spec performs an unnecessary copy operation. As an optimization, we
+ // omit this copy.
+ auto untilOptions = maybeOptions;
+
+ // Step 13.f.
+ DateDuration dateDifference;
+ if (untilOptions) {
+ if (!DifferenceDate(cx, calendar, date1, date2, dateLargestUnit,
+ untilOptions, &dateDifference)) {
+ return false;
+ }
+ } else {
+ if (!DifferenceDate(cx, calendar, date1, date2, dateLargestUnit,
+ &dateDifference)) {
+ return false;
+ }
+ }
+
+ // Step 13.g.
+ return CreateNormalizedDurationRecord(cx, dateDifference, norm, result);
+ }
+
+ // Step 12.h.
+ dayCorrection += 1;
}
- // Step 12.
- *result = {
- dateDifference.years, dateDifference.months,
- dateDifference.weeks, nanosAndDays.daysNumber(),
- timeDifference.hours, timeDifference.minutes,
- timeDifference.seconds, timeDifference.milliseconds,
- timeDifference.microseconds, timeDifference.nanoseconds,
- };
- MOZ_ASSERT(IsValidDuration(*result));
- return true;
+ // Steps 14-15.
+ JS_ReportErrorNumberASCII(
+ cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_ZONED_DATE_TIME_INCONSISTENT_INSTANT);
+ return false;
}
/**
@@ -1237,10 +1293,10 @@ bool js::temporal::DifferenceZonedDateTime(
JSContext* cx, const Instant& ns1, const Instant& ns2,
Handle<TimeZoneRecord> timeZone, Handle<CalendarRecord> calendar,
TemporalUnit largestUnit, const PlainDateTime& precalculatedPlainDateTime,
- Duration* result) {
- return ::DifferenceZonedDateTime(
- cx, ns1, ns2, timeZone, calendar, largestUnit, nullptr,
- mozilla::SomeRef(precalculatedPlainDateTime), result);
+ NormalizedDuration* result) {
+ return ::DifferenceZonedDateTime(cx, ns1, ns2, timeZone, calendar,
+ largestUnit, nullptr,
+ precalculatedPlainDateTime, result);
}
/**
@@ -1287,46 +1343,6 @@ static bool TimeZoneEqualsOrThrow(JSContext* cx, Handle<TimeZoneValue> one,
}
/**
- * RoundISODateTime ( year, month, day, hour, minute, second, millisecond,
- * microsecond, nanosecond, increment, unit, roundingMode [ , dayLength ] )
- */
-static bool RoundISODateTime(JSContext* cx, const PlainDateTime& dateTime,
- Increment increment, TemporalUnit unit,
- TemporalRoundingMode roundingMode,
- const InstantSpan& dayLength,
- PlainDateTime* result) {
- MOZ_ASSERT(IsValidInstantSpan(dayLength));
- MOZ_ASSERT(dayLength > (InstantSpan{}));
-
- const auto& [date, time] = dateTime;
-
- // Step 1.
- MOZ_ASSERT(IsValidISODateTime(dateTime));
- MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
-
- // Step 2. (Not applicable in our implementation.)
-
- // Step 3.
- auto roundedTime = RoundTime(time, increment, unit, roundingMode, dayLength);
-
- // |dayLength| can be as small as 1, so the number of rounded days can be as
- // large as the number of nanoseconds in |time|.
- MOZ_ASSERT(0 <= roundedTime.days &&
- roundedTime.days < ToNanoseconds(TemporalUnit::Day));
-
- // Step 4.
- PlainDate balanceResult;
- if (!BalanceISODate(cx, date.year, date.month,
- int64_t(date.day) + roundedTime.days, &balanceResult)) {
- return false;
- }
-
- // Step 5.
- *result = {balanceResult, roundedTime.time};
- return true;
-}
-
-/**
* DifferenceTemporalZonedDateTime ( operation, zonedDateTime, other, options )
*/
static bool DifferenceTemporalZonedDateTime(JSContext* cx,
@@ -1385,20 +1401,24 @@ static bool DifferenceTemporalZonedDateTime(JSContext* cx,
MOZ_ASSERT(settings.smallestUnit >= settings.largestUnit);
// Step 6.a.
- Duration difference;
- if (!DifferenceInstant(cx, zonedDateTime.instant(), other.instant(),
- settings.roundingIncrement, settings.smallestUnit,
- settings.largestUnit, settings.roundingMode,
- &difference)) {
+ auto difference = DifferenceInstant(
+ zonedDateTime.instant(), other.instant(), settings.roundingIncrement,
+ settings.smallestUnit, settings.roundingMode);
+
+ // Step 6.b.
+ TimeDuration balancedTime;
+ if (!BalanceTimeDuration(cx, difference, settings.largestUnit,
+ &balancedTime)) {
return false;
}
- // Step 6.b.
+ // Step 6.c.
+ auto duration = balancedTime.toDuration();
if (operation == TemporalDifference::Since) {
- difference = difference.negate();
+ duration = duration.negate();
}
- auto* result = CreateTemporalDuration(cx, difference);
+ auto* result = CreateTemporalDuration(cx, duration);
if (!result) {
return false;
}
@@ -1407,15 +1427,12 @@ static bool DifferenceTemporalZonedDateTime(JSContext* cx,
return true;
}
- // FIXME: spec issue - move this step next to the calendar validation?
- // https://github.com/tc39/proposal-temporal/issues/2533
-
- // Step 7.
+ // Steps 7-8.
if (!TimeZoneEqualsOrThrow(cx, zonedDateTime.timeZone(), other.timeZone())) {
return false;
}
- // Step 8.
+ // Step 9.
if (zonedDateTime.instant() == other.instant()) {
auto* obj = CreateTemporalDuration(cx, {});
if (!obj) {
@@ -1426,7 +1443,7 @@ static bool DifferenceTemporalZonedDateTime(JSContext* cx,
return true;
}
- // Step 9.
+ // Step 10.
Rooted<TimeZoneRecord> timeZone(cx);
if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(),
{
@@ -1437,7 +1454,7 @@ static bool DifferenceTemporalZonedDateTime(JSContext* cx,
return false;
}
- // Step 10.
+ // Step 11.
Rooted<CalendarRecord> calendar(cx);
if (!CreateCalendarMethodsRecord(cx, zonedDateTime.calendar(),
{
@@ -1448,14 +1465,14 @@ static bool DifferenceTemporalZonedDateTime(JSContext* cx,
return false;
}
- // Steps 11-12.
+ // Steps 12-13.
PlainDateTime precalculatedPlainDateTime;
if (!GetPlainDateTimeFor(cx, timeZone, zonedDateTime.instant(),
&precalculatedPlainDateTime)) {
return false;
}
- // Step 13.
+ // Step 14.
Rooted<PlainDateObject*> plainRelativeTo(
cx, CreateTemporalDate(cx, precalculatedPlainDateTime.date,
calendar.receiver()));
@@ -1463,23 +1480,12 @@ static bool DifferenceTemporalZonedDateTime(JSContext* cx,
return false;
}
- // Step 14.
- if (resolvedOptions) {
- Rooted<Value> largestUnitValue(
- cx, StringValue(TemporalUnitToString(cx, settings.largestUnit)));
- if (!DefineDataProperty(cx, resolvedOptions, cx->names().largestUnit,
- largestUnitValue)) {
- return false;
- }
- }
-
// Step 15.
- Duration difference;
- if (!::DifferenceZonedDateTime(
- cx, zonedDateTime.instant(), other.instant(), timeZone, calendar,
- settings.largestUnit, resolvedOptions,
- mozilla::SomeRef<const PlainDateTime>(precalculatedPlainDateTime),
- &difference)) {
+ NormalizedDuration difference;
+ if (!::DifferenceZonedDateTime(cx, zonedDateTime.instant(), other.instant(),
+ timeZone, calendar, settings.largestUnit,
+ resolvedOptions, precalculatedPlainDateTime,
+ &difference)) {
return false;
}
@@ -1489,59 +1495,76 @@ static bool DifferenceTemporalZonedDateTime(JSContext* cx,
settings.roundingIncrement == Increment{1};
// Step 17.
- if (roundingGranularityIsNoop) {
- if (operation == TemporalDifference::Since) {
- difference = difference.negate();
+ if (!roundingGranularityIsNoop) {
+ // Steps 17.a-b.
+ NormalizedDuration roundResult;
+ if (!RoundDuration(cx, difference, settings.roundingIncrement,
+ settings.smallestUnit, settings.roundingMode,
+ plainRelativeTo, calendar, zonedDateTime, timeZone,
+ precalculatedPlainDateTime, &roundResult)) {
+ return false;
}
- auto* obj = CreateTemporalDuration(cx, difference);
- if (!obj) {
+ // Step 17.c.
+ NormalizedTimeAndDays timeAndDays;
+ if (!NormalizedTimeDurationToDays(cx, roundResult.time, zonedDateTime,
+ timeZone, &timeAndDays)) {
return false;
}
- args.rval().setObject(*obj);
- return true;
- }
+ // Step 17.d.
+ int64_t days = roundResult.date.days + timeAndDays.days;
+
+ // Step 17.e.
+ auto toAdjust = NormalizedDuration{
+ {
+ roundResult.date.years,
+ roundResult.date.months,
+ roundResult.date.weeks,
+ days,
+ },
+ NormalizedTimeDuration::fromNanoseconds(timeAndDays.time),
+ };
+ NormalizedDuration adjustResult;
+ if (!AdjustRoundedDurationDays(cx, toAdjust, settings.roundingIncrement,
+ settings.smallestUnit, settings.roundingMode,
+ zonedDateTime, calendar, timeZone,
+ precalculatedPlainDateTime, &adjustResult)) {
+ return false;
+ }
- // Steps 18-19.
- Duration roundResult;
- if (!RoundDuration(cx, difference, settings.roundingIncrement,
- settings.smallestUnit, settings.roundingMode,
- plainRelativeTo, calendar, zonedDateTime, timeZone,
- precalculatedPlainDateTime, &roundResult)) {
- return false;
- }
+ // Step 17.f.
+ DateDuration balanceResult;
+ if (!temporal::BalanceDateDurationRelative(
+ cx, adjustResult.date, settings.largestUnit, settings.smallestUnit,
+ plainRelativeTo, calendar, &balanceResult)) {
+ return false;
+ }
- // Step 20.
- Duration adjustResult;
- if (!AdjustRoundedDurationDays(cx, roundResult, settings.roundingIncrement,
- settings.smallestUnit, settings.roundingMode,
- zonedDateTime, calendar, timeZone,
- precalculatedPlainDateTime, &adjustResult)) {
- return false;
+ // Step 17.g.
+ if (!CombineDateAndNormalizedTimeDuration(cx, balanceResult,
+ adjustResult.time, &difference)) {
+ return false;
+ }
}
- // Step 21.
- DateDuration balanceResult;
- if (!temporal::BalanceDateDurationRelative(
- cx, adjustResult.date(), settings.largestUnit, settings.smallestUnit,
- plainRelativeTo, calendar, &balanceResult)) {
- return false;
- }
+ // Step 18.
+ auto timeDuration = BalanceTimeDuration(difference.time, TemporalUnit::Hour);
- // Step 22.
- auto result = Duration{
- balanceResult.years, balanceResult.months,
- balanceResult.weeks, balanceResult.days,
- adjustResult.hours, adjustResult.minutes,
- adjustResult.seconds, adjustResult.milliseconds,
- adjustResult.microseconds, adjustResult.nanoseconds,
+ // Step 19.
+ auto duration = Duration{
+ double(difference.date.years), double(difference.date.months),
+ double(difference.date.weeks), double(difference.date.days),
+ double(timeDuration.hours), double(timeDuration.minutes),
+ double(timeDuration.seconds), double(timeDuration.milliseconds),
+ timeDuration.microseconds, timeDuration.nanoseconds,
};
if (operation == TemporalDifference::Since) {
- result = result.negate();
+ duration = duration.negate();
}
+ MOZ_ASSERT(IsValidDuration(duration));
- auto* obj = CreateTemporalDuration(cx, result);
+ auto* obj = CreateTemporalDuration(cx, duration);
if (!obj) {
return false;
}
@@ -1607,15 +1630,17 @@ static bool AddDurationToOrSubtractDurationFromZonedDateTime(
if (operation == ZonedDateTimeDuration::Subtract) {
duration = duration.negate();
}
+ auto normalized = CreateNormalizedDurationRecord(duration);
+ // Step 7.
Instant resultInstant;
if (!::AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar,
- duration, options, &resultInstant)) {
+ normalized, options, &resultInstant)) {
return false;
}
MOZ_ASSERT(IsValidEpochInstant(resultInstant));
- // Step 7.
+ // Step 8.
auto* result = CreateTemporalZonedDateTime(
cx, resultInstant, timeZone.receiver(), calendar.receiver());
if (!result) {
@@ -1764,8 +1789,8 @@ static bool ZonedDateTime_compare(JSContext* cx, unsigned argc, Value* vp) {
}
// Step 3.
- auto oneNs = one.instant();
- auto twoNs = two.instant();
+ const auto& oneNs = one.instant();
+ const auto& twoNs = two.instant();
args.rval().setInt32(oneNs > twoNs ? 1 : oneNs < twoNs ? -1 : 0);
return true;
}
@@ -2359,7 +2384,7 @@ static bool ZonedDateTime_hoursInDay(JSContext* cx, const CallArgs& args) {
}
// Step 4.
- auto instant = zonedDateTime.instant();
+ const auto& instant = zonedDateTime.instant();
// Step 5.
PlainDateTime temporalDateTime;
@@ -2402,20 +2427,12 @@ static bool ZonedDateTime_hoursInDay(JSContext* cx, const CallArgs& args) {
}
// Step 14.
- auto diffNs = tomorrowInstant - todayInstant;
- MOZ_ASSERT(IsValidInstantSpan(diffNs));
+ auto diff = tomorrowInstant - todayInstant;
+ MOZ_ASSERT(IsValidInstantSpan(diff));
// Step 15.
- constexpr int32_t secPerHour = 60 * 60;
- constexpr int64_t nsPerSec = ToNanoseconds(TemporalUnit::Second);
- constexpr double nsPerHour = ToNanoseconds(TemporalUnit::Hour);
-
- int64_t hours = diffNs.seconds / secPerHour;
- int64_t seconds = diffNs.seconds % secPerHour;
- int64_t nanoseconds = seconds * nsPerSec + diffNs.nanoseconds;
-
- double result = double(hours) + double(nanoseconds) / nsPerHour;
- args.rval().setNumber(result);
+ constexpr auto nsPerHour = Int128{ToNanoseconds(TemporalUnit::Hour)};
+ args.rval().setNumber(FractionToDouble(diff.toNanoseconds(), nsPerHour));
return true;
}
@@ -2587,7 +2604,7 @@ static bool ZonedDateTime_offsetNanoseconds(JSContext* cx,
auto timeZone = zonedDateTime.timeZone();
// Step 4.
- auto instant = zonedDateTime.instant();
+ const auto& instant = zonedDateTime.instant();
// Step 5.
int64_t offsetNanoseconds;
@@ -2622,7 +2639,7 @@ static bool ZonedDateTime_offset(JSContext* cx, const CallArgs& args) {
auto timeZone = zonedDateTime.timeZone();
// Step 4.
- auto instant = zonedDateTime.instant();
+ const auto& instant = zonedDateTime.instant();
// Step 5.
JSString* str = GetOffsetStringFor(cx, timeZone, instant);
@@ -2658,13 +2675,11 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) {
if (!temporalZonedDateTimeLike) {
return false;
}
-
- // Step 4.
- if (!RejectTemporalLikeObject(cx, temporalZonedDateTimeLike)) {
+ if (!ThrowIfTemporalLikeObject(cx, temporalZonedDateTimeLike)) {
return false;
}
- // Step 5.
+ // Step 4.
Rooted<PlainObject*> resolvedOptions(cx);
if (args.hasDefined(1)) {
Rooted<JSObject*> options(cx,
@@ -2680,7 +2695,7 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 6.
+ // Step 5.
Rooted<CalendarRecord> calendar(cx);
if (!CreateCalendarMethodsRecord(cx, zonedDateTime.calendar(),
{
@@ -2692,7 +2707,7 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 7.
+ // Step 6.
Rooted<TimeZoneRecord> timeZone(cx);
if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(),
{
@@ -2703,16 +2718,16 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 8.
- auto instant = zonedDateTime.instant();
+ // Step 7.
+ const auto& instant = zonedDateTime.instant();
- // Step 9.
+ // Step 8.
int64_t offsetNanoseconds;
if (!GetOffsetNanosecondsFor(cx, timeZone, instant, &offsetNanoseconds)) {
return false;
}
- // Step 10.
+ // Step 9.
Rooted<PlainDateTimeObject*> dateTime(
cx,
GetPlainDateTimeFor(cx, instant, calendar.receiver(), offsetNanoseconds));
@@ -2720,7 +2735,7 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 11.
+ // Step 10.
JS::RootedVector<PropertyKey> fieldNames(cx);
if (!CalendarFields(cx, calendar,
{CalendarField::Day, CalendarField::Month,
@@ -2729,14 +2744,14 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 12.
+ // Step 11.
Rooted<PlainObject*> fields(cx,
PrepareTemporalFields(cx, dateTime, fieldNames));
if (!fields) {
return false;
}
- // Steps 13-18.
+ // Steps 12-17.
struct TimeField {
using FieldName = ImmutableTenuredPtr<PropertyName*> JSAtomState::*;
@@ -2761,7 +2776,7 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) {
}
}
- // Step 19.
+ // Step 18.
JSString* fieldsOffset = FormatUTCOffsetNanoseconds(cx, offsetNanoseconds);
if (!fieldsOffset) {
return false;
@@ -2772,7 +2787,7 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 20.
+ // Step 19.
if (!AppendSorted(cx, fieldNames.get(),
{
TemporalField::Hour,
@@ -2786,7 +2801,7 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 21.
+ // Step 20.
Rooted<PlainObject*> partialZonedDateTime(
cx,
PreparePartialTemporalFields(cx, temporalZonedDateTimeLike, fieldNames));
@@ -2794,56 +2809,56 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 22.
+ // Step 21.
Rooted<JSObject*> mergedFields(
cx, CalendarMergeFields(cx, calendar, fields, partialZonedDateTime));
if (!mergedFields) {
return false;
}
- // Step 23.
+ // Step 22.
fields = PrepareTemporalFields(cx, mergedFields, fieldNames,
{TemporalField::Offset});
if (!fields) {
return false;
}
- // Step 24-25.
+ // Step 23-24.
auto disambiguation = TemporalDisambiguation::Compatible;
if (!ToTemporalDisambiguation(cx, resolvedOptions, &disambiguation)) {
return false;
}
- // Step 26.
+ // Step 25.
auto offset = TemporalOffset::Prefer;
if (!ToTemporalOffset(cx, resolvedOptions, &offset)) {
return false;
}
- // Step 27.
+ // Step 26.
PlainDateTime dateTimeResult;
if (!InterpretTemporalDateTimeFields(cx, calendar, fields, resolvedOptions,
&dateTimeResult)) {
return false;
}
- // Step 28.
+ // Step 27.
Rooted<Value> offsetString(cx);
if (!GetProperty(cx, fields, fields, cx->names().offset, &offsetString)) {
return false;
}
- // Step 29.
+ // Step 28.
MOZ_ASSERT(offsetString.isString());
- // Step 30.
+ // Step 29.
Rooted<JSString*> offsetStr(cx, offsetString.toString());
int64_t newOffsetNanoseconds;
if (!ParseDateTimeUTCOffset(cx, offsetStr, &newOffsetNanoseconds)) {
return false;
}
- // Step 31.
+ // Step 30.
Instant epochNanoseconds;
if (!InterpretISODateTimeOffset(
cx, dateTimeResult, OffsetBehaviour::Option, newOffsetNanoseconds,
@@ -2852,7 +2867,7 @@ static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) {
return false;
}
- // Step 32.
+ // Step 31.
auto* result = CreateTemporalZonedDateTime(
cx, epochNanoseconds, timeZone.receiver(), calendar.receiver());
if (!result) {
@@ -2880,7 +2895,7 @@ static bool ZonedDateTime_withPlainTime(JSContext* cx, const CallArgs& args) {
Rooted<ZonedDateTime> zonedDateTime(
cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
- // Steps 3-4.
+ // Step 3. (Inlined ToTemporalTimeOrMidnight)
PlainTime time = {};
if (args.hasDefined(0)) {
if (!ToTemporalTime(cx, args[0], &time)) {
@@ -2888,7 +2903,7 @@ static bool ZonedDateTime_withPlainTime(JSContext* cx, const CallArgs& args) {
}
}
- // Step 5.
+ // Step 4.
Rooted<TimeZoneRecord> timeZone(cx);
if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(),
{
@@ -2899,31 +2914,31 @@ static bool ZonedDateTime_withPlainTime(JSContext* cx, const CallArgs& args) {
return false;
}
- // Steps 6 and 8.
+ // Steps 5 and 7.
PlainDateTime plainDateTime;
if (!GetPlainDateTimeFor(cx, timeZone, zonedDateTime.instant(),
&plainDateTime)) {
return false;
}
- // Step 7.
+ // Step 6.
auto calendar = zonedDateTime.calendar();
- // Step 9.
+ // Step 8.
Rooted<PlainDateTimeWithCalendar> resultPlainDateTime(cx);
if (!CreateTemporalDateTime(cx, {plainDateTime.date, time}, calendar,
&resultPlainDateTime)) {
return false;
}
- // Step 10.
+ // Step 9.
Instant instant;
if (!GetInstantFor(cx, timeZone, resultPlainDateTime,
TemporalDisambiguation::Compatible, &instant)) {
return false;
}
- // Step 11.
+ // Step 10.
auto* result =
CreateTemporalZonedDateTime(cx, instant, timeZone.receiver(), calendar);
if (!result) {
@@ -3273,57 +3288,101 @@ static bool ZonedDateTime_round(JSContext* cx, const CallArgs& args) {
GetPlainDateTimeFor(zonedDateTime.instant(), offsetNanoseconds);
// Step 19.
- Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601));
- Rooted<PlainDateTimeWithCalendar> dtStart(cx);
- if (!CreateTemporalDateTime(cx, {temporalDateTime.date, {}}, isoCalendar,
- &dtStart)) {
- return false;
- }
+ Instant epochNanoseconds;
+ if (smallestUnit == TemporalUnit::Day) {
+ // Step 19.a.
+ Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601));
+ Rooted<PlainDateTimeWithCalendar> dtStart(cx);
+ if (!CreateTemporalDateTime(cx, {temporalDateTime.date, {}}, isoCalendar,
+ &dtStart)) {
+ return false;
+ }
- // Steps 20-21.
- Instant startNs;
- if (!GetInstantFor(cx, timeZone, dtStart, TemporalDisambiguation::Compatible,
- &startNs)) {
- return false;
- }
+ // Step 19.b.
+ auto dateEnd =
+ BalanceISODate(temporalDateTime.date.year, temporalDateTime.date.month,
+ temporalDateTime.date.day + 1);
- // Step 22.
- Instant endNs;
- if (!AddDaysToZonedDateTime(cx, startNs, ToPlainDateTime(dtStart), timeZone,
- calendar, 1, &endNs)) {
- return false;
- }
- MOZ_ASSERT(IsValidEpochInstant(endNs));
+ // Step 19.c.
+ Rooted<PlainDateTimeWithCalendar> dtEnd(cx);
+ if (!CreateTemporalDateTime(cx, {dateEnd, {}}, isoCalendar, &dtEnd)) {
+ return false;
+ }
- // Step 23.
- auto dayLengthNs = endNs - startNs;
- MOZ_ASSERT(IsValidInstantSpan(dayLengthNs));
+ // Step 19.d.
+ const auto& thisNs = zonedDateTime.instant();
- // Step 24.
- if (dayLengthNs <= InstantSpan{}) {
- JS_ReportErrorNumberASCII(
- cx, GetErrorMessage, nullptr,
- JSMSG_TEMPORAL_ZONED_DATE_TIME_NON_POSITIVE_DAY_LENGTH);
- return false;
- }
+ // Steps 19.e-f.
+ Instant startNs;
+ if (!GetInstantFor(cx, timeZone, dtStart,
+ TemporalDisambiguation::Compatible, &startNs)) {
+ return false;
+ }
- // Step 25.
- PlainDateTime roundResult;
- if (!RoundISODateTime(cx, temporalDateTime, roundingIncrement, smallestUnit,
- roundingMode, dayLengthNs, &roundResult)) {
- return false;
- }
+ // Step 19.g.
+ if (thisNs < startNs) {
+ JS_ReportErrorNumberASCII(
+ cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_ZONED_DATE_TIME_INCONSISTENT_INSTANT);
+ return false;
+ }
- // Step 26.
- Instant epochNanoseconds;
- if (!InterpretISODateTimeOffset(
- cx, roundResult, OffsetBehaviour::Option, offsetNanoseconds, timeZone,
- TemporalDisambiguation::Compatible, TemporalOffset::Prefer,
- MatchBehaviour::MatchExactly, &epochNanoseconds)) {
- return false;
+ // Steps 19.h-i.
+ Instant endNs;
+ if (!GetInstantFor(cx, timeZone, dtEnd, TemporalDisambiguation::Compatible,
+ &endNs)) {
+ return false;
+ }
+
+ // Step 19.j.
+ if (thisNs >= endNs) {
+ JS_ReportErrorNumberASCII(
+ cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_ZONED_DATE_TIME_INCONSISTENT_INSTANT);
+ return false;
+ }
+
+ // Step 19.k.
+ auto dayLengthNs = endNs - startNs;
+ MOZ_ASSERT(IsValidInstantSpan(dayLengthNs));
+ MOZ_ASSERT(dayLengthNs > InstantSpan{}, "dayLengthNs is positive");
+
+ // Step 19.l. (Inlined NormalizedTimeDurationFromEpochNanosecondsDifference)
+ auto dayProgressNs = thisNs - startNs;
+ MOZ_ASSERT(IsValidInstantSpan(dayProgressNs));
+ MOZ_ASSERT(dayProgressNs >= InstantSpan{}, "dayProgressNs is non-negative");
+
+ MOZ_ASSERT(startNs <= thisNs && thisNs < endNs);
+ MOZ_ASSERT(dayProgressNs < dayLengthNs);
+
+ // Step 19.m. (Inlined RoundNormalizedTimeDurationToIncrement)
+ auto rounded =
+ RoundNumberToIncrement(dayProgressNs.toNanoseconds(),
+ dayLengthNs.toNanoseconds(), roundingMode);
+ auto roundedDaysNs = InstantSpan::fromNanoseconds(rounded);
+ MOZ_ASSERT(roundedDaysNs == InstantSpan{} || roundedDaysNs == dayLengthNs);
+ MOZ_ASSERT(IsValidInstantSpan(roundedDaysNs));
+
+ // Step 19.n.
+ epochNanoseconds = startNs + roundedDaysNs;
+ MOZ_ASSERT(epochNanoseconds == startNs || epochNanoseconds == endNs);
+ } else {
+ // Step 20.a.
+ auto roundResult = RoundISODateTime(temporalDateTime, roundingIncrement,
+ smallestUnit, roundingMode);
+
+ // Step 20.b.
+ if (!InterpretISODateTimeOffset(
+ cx, roundResult, OffsetBehaviour::Option, offsetNanoseconds,
+ timeZone, TemporalDisambiguation::Compatible,
+ TemporalOffset::Prefer, MatchBehaviour::MatchExactly,
+ &epochNanoseconds)) {
+ return false;
+ }
}
+ MOZ_ASSERT(IsValidEpochInstant(epochNanoseconds));
- // Step 27.
+ // Step 22.
auto* result = CreateTemporalZonedDateTime(cx, epochNanoseconds,
timeZone.receiver(), calendar);
if (!result) {
@@ -3557,7 +3616,7 @@ static bool ZonedDateTime_startOfDay(JSContext* cx, const CallArgs& args) {
auto calendar = zonedDateTime.calendar();
// Step 5.
- auto instant = zonedDateTime.instant();
+ const auto& instant = zonedDateTime.instant();
// Steps 5-6.
PlainDateTime temporalDateTime;
@@ -3864,7 +3923,7 @@ static bool ZonedDateTime_getISOFields(JSContext* cx, const CallArgs& args) {
Rooted<IdValueVector> fields(cx, IdValueVector(cx));
// Step 4.
- auto instant = zonedDateTime.instant();
+ const auto& instant = zonedDateTime.instant();
// Step 5.
auto calendar = zonedDateTime.calendar();
diff --git a/js/src/builtin/temporal/ZonedDateTime.h b/js/src/builtin/temporal/ZonedDateTime.h
index 73e3a3384f..7d75a33201 100644
--- a/js/src/builtin/temporal/ZonedDateTime.h
+++ b/js/src/builtin/temporal/ZonedDateTime.h
@@ -8,6 +8,7 @@
#define builtin_temporal_ZonedDateTime_h
#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
#include <stdint.h>
@@ -69,7 +70,7 @@ inline Instant ToInstant(const ZonedDateTimeObject* zonedDateTime) {
return {zonedDateTime->seconds(), zonedDateTime->nanoseconds()};
}
-class ZonedDateTime {
+class MOZ_STACK_CLASS ZonedDateTime final {
Instant instant_;
TimeZoneValue timeZone_;
CalendarValue calendar_;
@@ -125,7 +126,7 @@ ZonedDateTimeObject* CreateTemporalZonedDateTime(
bool AddDaysToZonedDateTime(JSContext* cx, const Instant& instant,
const PlainDateTime& dateTime,
JS::Handle<TimeZoneRecord> timeZone,
- JS::Handle<CalendarValue> calendar, double days,
+ JS::Handle<CalendarValue> calendar, int64_t days,
TemporalOverflow overflow, Instant* result);
/**
@@ -135,29 +136,46 @@ bool AddDaysToZonedDateTime(JSContext* cx, const Instant& instant,
bool AddDaysToZonedDateTime(JSContext* cx, const Instant& instant,
const PlainDateTime& dateTime,
JS::Handle<TimeZoneRecord> timeZone,
- JS::Handle<CalendarValue> calendar, double days,
+ JS::Handle<CalendarValue> calendar, int64_t days,
Instant* result);
/**
* AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months,
- * weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds
- * [ , precalculatedPlainDateTime [ , options ] ] )
+ * weeks, days, norm [ , precalculatedPlainDateTime [ , options ] ] )
*/
bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds,
JS::Handle<TimeZoneRecord> timeZone,
JS::Handle<CalendarRecord> calendar,
- const Duration& duration, Instant* result);
+ const NormalizedDuration& duration, Instant* result);
/**
* AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months,
- * weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds
- * [ , precalculatedPlainDateTime [ , options ] ] )
+ * weeks, days, norm [ , precalculatedPlainDateTime [ , options ] ] )
*/
bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds,
JS::Handle<TimeZoneRecord> timeZone,
JS::Handle<CalendarRecord> calendar,
- const Duration& duration, const PlainDateTime& dateTime,
- Instant* result);
+ const NormalizedDuration& duration,
+ const PlainDateTime& dateTime, Instant* result);
+
+/**
+ * AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months,
+ * weeks, days, norm [ , precalculatedPlainDateTime [ , options ] ] )
+ */
+bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds,
+ JS::Handle<TimeZoneRecord> timeZone,
+ JS::Handle<CalendarRecord> calendar,
+ const DateDuration& duration, Instant* result);
+
+/**
+ * AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months,
+ * weeks, days, norm [ , precalculatedPlainDateTime [ , options ] ] )
+ */
+bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds,
+ JS::Handle<TimeZoneRecord> timeZone,
+ JS::Handle<CalendarRecord> calendar,
+ const DateDuration& duration,
+ const PlainDateTime& dateTime, Instant* result);
/**
* DifferenceZonedDateTime ( ns1, ns2, timeZoneRec, calendarRec, largestUnit,
@@ -169,48 +187,34 @@ bool DifferenceZonedDateTime(JSContext* cx, const Instant& ns1,
JS::Handle<CalendarRecord> calendar,
TemporalUnit largestUnit,
const PlainDateTime& precalculatedPlainDateTime,
- Duration* result);
-
-struct NanosecondsAndDays final {
- JS::BigInt* days = nullptr;
- int64_t daysInt = 0;
- InstantSpan nanoseconds;
- InstantSpan dayLength;
-
- double daysNumber() const;
+ NormalizedDuration* result);
- void trace(JSTracer* trc);
-
- static NanosecondsAndDays from(int64_t days, const InstantSpan& nanoseconds,
- const InstantSpan& dayLength) {
- return {nullptr, days, nanoseconds, dayLength};
- }
-
- static NanosecondsAndDays from(JS::BigInt* days,
- const InstantSpan& nanoseconds,
- const InstantSpan& dayLength) {
- return {days, 0, nanoseconds, dayLength};
- }
+struct NormalizedTimeAndDays final {
+ int64_t days = 0;
+ int64_t time = 0;
+ int64_t dayLength = 0;
};
/**
- * NanosecondsToDays ( nanoseconds, zonedRelativeTo, timeZoneRec [ ,
+ * NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ ,
* precalculatedPlainDateTime ] )
*/
-bool NanosecondsToDays(JSContext* cx, const InstantSpan& nanoseconds,
- JS::Handle<ZonedDateTime> zonedRelativeTo,
- JS::Handle<TimeZoneRecord> timeZone,
- JS::MutableHandle<NanosecondsAndDays> result);
+bool NormalizedTimeDurationToDays(JSContext* cx,
+ const NormalizedTimeDuration& duration,
+ JS::Handle<ZonedDateTime> zonedRelativeTo,
+ JS::Handle<TimeZoneRecord> timeZone,
+ NormalizedTimeAndDays* result);
/**
- * NanosecondsToDays ( nanoseconds, zonedRelativeTo, timeZoneRec [ ,
+ * NormalizedTimeDurationToDays ( norm, zonedRelativeTo, timeZoneRec [ ,
* precalculatedPlainDateTime ] )
*/
-bool NanosecondsToDays(JSContext* cx, const InstantSpan& nanoseconds,
- JS::Handle<ZonedDateTime> zonedRelativeTo,
- JS::Handle<TimeZoneRecord> timeZone,
- const PlainDateTime& precalculatedPlainDateTime,
- JS::MutableHandle<NanosecondsAndDays> result);
+bool NormalizedTimeDurationToDays(
+ JSContext* cx, const NormalizedTimeDuration& duration,
+ JS::Handle<ZonedDateTime> zonedRelativeTo,
+ JS::Handle<TimeZoneRecord> timeZone,
+ const PlainDateTime& precalculatedPlainDateTime,
+ NormalizedTimeAndDays* result);
enum class OffsetBehaviour { Option, Exact, Wall };
@@ -255,26 +259,6 @@ class WrappedPtrOperations<temporal::ZonedDateTime, Wrapper> {
}
};
-template <typename Wrapper>
-class WrappedPtrOperations<temporal::NanosecondsAndDays, Wrapper> {
- const auto& object() const {
- return static_cast<const Wrapper*>(this)->get();
- }
-
- public:
- double daysNumber() const { return object().daysNumber(); }
-
- JS::Handle<JS::BigInt*> days() const {
- return JS::Handle<JS::BigInt*>::fromMarkedLocation(&object().days);
- }
-
- int64_t daysInt() const { return object().daysInt; }
-
- temporal::InstantSpan nanoseconds() const { return object().nanoseconds; }
-
- temporal::InstantSpan dayLength() const { return object().dayLength; }
-};
-
} /* namespace js */
#endif /* builtin_temporal_ZonedDateTime_h */
diff --git a/js/src/builtin/temporal/moz.build b/js/src/builtin/temporal/moz.build
index ae3bb618ad..3c09960783 100644
--- a/js/src/builtin/temporal/moz.build
+++ b/js/src/builtin/temporal/moz.build
@@ -17,6 +17,7 @@ if CONFIG["JS_HAS_TEMPORAL_API"]:
"Calendar.cpp",
"Duration.cpp",
"Instant.cpp",
+ "Int128.cpp",
"Int96.cpp",
"PlainDate.cpp",
"PlainDateTime.cpp",