Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/Duration.h"
#include "mozilla/Assertions.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/EnumSet.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Maybe.h"
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <initializer_list>
#include <stdint.h>
#include <type_traits>
#include <utility>
#include "jsnum.h"
#include "jspubtd.h"
#include "NamespaceImports.h"
#include "builtin/temporal/Calendar.h"
#include "builtin/temporal/Instant.h"
#include "builtin/temporal/PlainDate.h"
#include "builtin/temporal/PlainDateTime.h"
#include "builtin/temporal/Temporal.h"
#include "builtin/temporal/TemporalFields.h"
#include "builtin/temporal/TemporalParser.h"
#include "builtin/temporal/TemporalRoundingMode.h"
#include "builtin/temporal/TemporalTypes.h"
#include "builtin/temporal/TemporalUnit.h"
#include "builtin/temporal/TimeZone.h"
#include "builtin/temporal/Wrapped.h"
#include "builtin/temporal/ZonedDateTime.h"
#include "gc/AllocKind.h"
#include "gc/Barrier.h"
#include "gc/GCEnum.h"
#include "js/CallArgs.h"
#include "js/CallNonGenericMethod.h"
#include "js/Class.h"
#include "js/Conversions.h"
#include "js/ErrorReport.h"
#include "js/friend/ErrorMessages.h"
#include "js/GCVector.h"
#include "js/Id.h"
#include "js/Printer.h"
#include "js/PropertyDescriptor.h"
#include "js/PropertySpec.h"
#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"
#include "vm/JSContext.h"
#include "vm/JSObject.h"
#include "vm/ObjectOperations.h"
#include "vm/PlainObject.h"
#include "vm/StringType.h"
#include "vm/JSObject-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/ObjectOperations-inl.h"
using namespace js;
using namespace js::temporal;
static inline bool IsDuration(Handle<Value> v) {
return v.isObject() && v.toObject().is<DurationObject>();
}
#ifdef DEBUG
static bool IsIntegerOrInfinity(double d) {
return IsInteger(d) || std::isinf(d);
}
static bool IsIntegerOrInfinityDuration(const Duration& duration) {
auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
microseconds, nanoseconds] = duration;
// Integers exceeding the Number range are represented as infinity.
return IsIntegerOrInfinity(years) && IsIntegerOrInfinity(months) &&
IsIntegerOrInfinity(weeks) && IsIntegerOrInfinity(days) &&
IsIntegerOrInfinity(hours) && IsIntegerOrInfinity(minutes) &&
IsIntegerOrInfinity(seconds) && IsIntegerOrInfinity(milliseconds) &&
IsIntegerOrInfinity(microseconds) && IsIntegerOrInfinity(nanoseconds);
}
static bool IsIntegerDuration(const Duration& duration) {
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) &&
IsInteger(seconds) && IsInteger(milliseconds) &&
IsInteger(microseconds) && IsInteger(nanoseconds);
}
#endif
/**
* DurationSign ( years, months, weeks, days, hours, minutes, seconds,
* milliseconds, microseconds, nanoseconds )
*/
int32_t js::temporal::DurationSign(const Duration& duration) {
MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
microseconds, nanoseconds] = duration;
// Step 1.
for (auto v : {years, months, weeks, days, hours, minutes, seconds,
milliseconds, microseconds, nanoseconds}) {
// Step 1.a.
if (v < 0) {
return -1;
}
// Step 1.b.
if (v > 0) {
return 1;
}
}
// Step 2.
return 0;
}
/**
* 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;
// Step 1.
int32_t sign = DurationSign(duration);
// Step 2.
for (auto v : {years, months, weeks, days, hours, minutes, seconds,
milliseconds, microseconds, nanoseconds}) {
// Step 2.a.
if (!std::isfinite(v)) {
return false;
}
// Step 2.b.
if (v < 0 && sign > 0) {
return false;
}
// Step 2.c.
if (v > 0 && sign < 0) {
return false;
}
}
// Step 3.
return true;
}
/**
* IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
* milliseconds, microseconds, nanoseconds )
*/
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;
// 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;
}
// Steps 2.b-c.
if ((v < 0 && sign > 0) || (v > 0 && sign < 0)) {
report(v, name, JSMSG_TEMPORAL_DURATION_INVALID_SIGN);
return false;
}
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;
}
if (!throwIfInvalid(hours, "hours")) {
return false;
}
if (!throwIfInvalid(minutes, "minutes")) {
return false;
}
if (!throwIfInvalid(seconds, "seconds")) {
return false;
}
if (!throwIfInvalid(milliseconds, "milliseconds")) {
return false;
}
if (!throwIfInvalid(microseconds, "microseconds")) {
return false;
}
if (!throwIfInvalid(nanoseconds, "nanoseconds")) {
return false;
}
MOZ_ASSERT(IsValidDuration(duration));
// Step 3.
return true;
}
/**
* DefaultTemporalLargestUnit ( years, months, weeks, days, hours, minutes,
* seconds, milliseconds, microseconds )
*/
static TemporalUnit DefaultTemporalLargestUnit(const Duration& duration) {
MOZ_ASSERT(IsIntegerDuration(duration));
// Step 1.
if (duration.years != 0) {
return TemporalUnit::Year;
}
// Step 2.
if (duration.months != 0) {
return TemporalUnit::Month;
}
// Step 3.
if (duration.weeks != 0) {
return TemporalUnit::Week;
}
// Step 4.
if (duration.days != 0) {
return TemporalUnit::Day;
}
// Step 5.
if (duration.hours != 0) {
return TemporalUnit::Hour;
}
// Step 6.
if (duration.minutes != 0) {
return TemporalUnit::Minute;
}
// Step 7.
if (duration.seconds != 0) {
return TemporalUnit::Second;
}
// Step 8.
if (duration.milliseconds != 0) {
return TemporalUnit::Millisecond;
}
// Step 9.
if (duration.microseconds != 0) {
return TemporalUnit::Microsecond;
}
// Step 10.
return TemporalUnit::Nanosecond;
}
/**
* CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
* milliseconds, microseconds, nanoseconds [ , newTarget ] )
*/
static DurationObject* CreateTemporalDuration(JSContext* cx,
const CallArgs& args,
const Duration& duration) {
auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
microseconds, nanoseconds] = duration;
// Step 1.
if (!ThrowIfInvalidDuration(cx, duration)) {
return nullptr;
}
// Steps 2-3.
Rooted<JSObject*> proto(cx);
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Duration, &proto)) {
return nullptr;
}
auto* object = NewObjectWithClassProto<DurationObject>(cx, proto);
if (!object) {
return nullptr;
}
// Steps 4-13.
// Add zero to convert -0 to +0.
object->setFixedSlot(DurationObject::YEARS_SLOT, NumberValue(years + (+0.0)));
object->setFixedSlot(DurationObject::MONTHS_SLOT,
NumberValue(months + (+0.0)));
object->setFixedSlot(DurationObject::WEEKS_SLOT, NumberValue(weeks + (+0.0)));
object->setFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0)));
object->setFixedSlot(DurationObject::HOURS_SLOT, NumberValue(hours + (+0.0)));
object->setFixedSlot(DurationObject::MINUTES_SLOT,
NumberValue(minutes + (+0.0)));
object->setFixedSlot(DurationObject::SECONDS_SLOT,
NumberValue(seconds + (+0.0)));
object->setFixedSlot(DurationObject::MILLISECONDS_SLOT,
NumberValue(milliseconds + (+0.0)));
object->setFixedSlot(DurationObject::MICROSECONDS_SLOT,
NumberValue(microseconds + (+0.0)));
object->setFixedSlot(DurationObject::NANOSECONDS_SLOT,
NumberValue(nanoseconds + (+0.0)));
// Step 14.
return object;
}
/**
* CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
* milliseconds, microseconds, nanoseconds [ , newTarget ] )
*/
DurationObject* js::temporal::CreateTemporalDuration(JSContext* cx,
const Duration& duration) {
auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
microseconds, nanoseconds] = duration;
MOZ_ASSERT(IsInteger(years));
MOZ_ASSERT(IsInteger(months));
MOZ_ASSERT(IsInteger(weeks));
MOZ_ASSERT(IsInteger(days));
MOZ_ASSERT(IsInteger(hours));
MOZ_ASSERT(IsInteger(minutes));
MOZ_ASSERT(IsInteger(seconds));
MOZ_ASSERT(IsInteger(milliseconds));
MOZ_ASSERT(IsInteger(microseconds));
MOZ_ASSERT(IsInteger(nanoseconds));
// Step 1.
if (!ThrowIfInvalidDuration(cx, duration)) {
return nullptr;
}
// Steps 2-3.
auto* object = NewBuiltinClassInstance<DurationObject>(cx);
if (!object) {
return nullptr;
}
// Steps 4-13.
// Add zero to convert -0 to +0.
object->setFixedSlot(DurationObject::YEARS_SLOT, NumberValue(years + (+0.0)));
object->setFixedSlot(DurationObject::MONTHS_SLOT,
NumberValue(months + (+0.0)));
object->setFixedSlot(DurationObject::WEEKS_SLOT, NumberValue(weeks + (+0.0)));
object->setFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0)));
object->setFixedSlot(DurationObject::HOURS_SLOT, NumberValue(hours + (+0.0)));
object->setFixedSlot(DurationObject::MINUTES_SLOT,
NumberValue(minutes + (+0.0)));
object->setFixedSlot(DurationObject::SECONDS_SLOT,
NumberValue(seconds + (+0.0)));
object->setFixedSlot(DurationObject::MILLISECONDS_SLOT,
NumberValue(milliseconds + (+0.0)));
object->setFixedSlot(DurationObject::MICROSECONDS_SLOT,
NumberValue(microseconds + (+0.0)));
object->setFixedSlot(DurationObject::NANOSECONDS_SLOT,
NumberValue(nanoseconds + (+0.0)));
// Step 14.
return object;
}
/**
* ToIntegerIfIntegral ( argument )
*/
static bool ToIntegerIfIntegral(JSContext* cx, const char* name,
Handle<Value> argument, double* num) {
// Step 1.
double d;
if (!JS::ToNumber(cx, argument, &d)) {
return false;
}
// Step 2.
if (!js::IsInteger(d)) {
ToCStringBuf cbuf;
const char* numStr = NumberToCString(&cbuf, d);
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr,
name);
return false;
}
// Step 3.
*num = d;
return true;
}
/**
* ToIntegerIfIntegral ( argument )
*/
static bool ToIntegerIfIntegral(JSContext* cx, Handle<PropertyName*> name,
Handle<Value> argument, double* result) {
// Step 1.
double d;
if (!JS::ToNumber(cx, argument, &d)) {
return false;
}
// Step 2.
if (!js::IsInteger(d)) {
if (auto nameStr = js::QuoteString(cx, name)) {
ToCStringBuf cbuf;
const char* numStr = NumberToCString(&cbuf, d);
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr,
nameStr.get());
}
return false;
}
// Step 3.
*result = d;
return true;
}
/**
* ToTemporalPartialDurationRecord ( temporalDurationLike )
*/
static bool ToTemporalPartialDurationRecord(
JSContext* cx, Handle<JSObject*> temporalDurationLike, Duration* result) {
// Steps 1-3. (Not applicable in our implementation.)
Rooted<Value> value(cx);
bool any = false;
auto getDurationProperty = [&](Handle<PropertyName*> name, double* num) {
if (!GetProperty(cx, temporalDurationLike, temporalDurationLike, name,
&value)) {
return false;
}
if (!value.isUndefined()) {
any = true;
if (!ToIntegerIfIntegral(cx, name, value, num)) {
return false;
}
}
return true;
};
// Steps 4-23.
if (!getDurationProperty(cx->names().days, &result->days)) {
return false;
}
if (!getDurationProperty(cx->names().hours, &result->hours)) {
return false;
}
if (!getDurationProperty(cx->names().microseconds, &result->microseconds)) {
return false;
}
if (!getDurationProperty(cx->names().milliseconds, &result->milliseconds)) {
return false;
}
if (!getDurationProperty(cx->names().minutes, &result->minutes)) {
return false;
}
if (!getDurationProperty(cx->names().months, &result->months)) {
return false;
}
if (!getDurationProperty(cx->names().nanoseconds, &result->nanoseconds)) {
return false;
}
if (!getDurationProperty(cx->names().seconds, &result->seconds)) {
return false;
}
if (!getDurationProperty(cx->names().weeks, &result->weeks)) {
return false;
}
if (!getDurationProperty(cx->names().years, &result->years)) {
return false;
}
// Step 24.
if (!any) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_DURATION_MISSING_UNIT);
return false;
}
// Step 25.
return true;
}
/**
* ToTemporalDurationRecord ( temporalDurationLike )
*/
bool js::temporal::ToTemporalDurationRecord(JSContext* cx,
Handle<Value> temporalDurationLike,
Duration* result) {
// Step 1.
if (!temporalDurationLike.isObject()) {
// Step 1.a.
if (!temporalDurationLike.isString()) {
ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK,
temporalDurationLike, nullptr, "not a string");
return false;
}
Rooted<JSString*> string(cx, temporalDurationLike.toString());
// Step 1.b.
return ParseTemporalDurationString(cx, string, result);
}
Rooted<JSObject*> durationLike(cx, &temporalDurationLike.toObject());
// Step 2.
if (auto* duration = durationLike->maybeUnwrapIf<DurationObject>()) {
*result = ToDuration(duration);
return true;
}
// Step 3.
Duration duration = {};
// Steps 4-14.
if (!ToTemporalPartialDurationRecord(cx, durationLike, &duration)) {
return false;
}
// Step 15.
if (!ThrowIfInvalidDuration(cx, duration)) {
return false;
}
// Step 16.
*result = duration;
return true;
}
/**
* ToTemporalDuration ( item )
*/
Wrapped<DurationObject*> js::temporal::ToTemporalDuration(JSContext* cx,
Handle<Value> item) {
// Step 1.
if (item.isObject()) {
JSObject* itemObj = &item.toObject();
if (itemObj->canUnwrapAs<DurationObject>()) {
return itemObj;
}
}
// Step 2.
Duration result;
if (!ToTemporalDurationRecord(cx, item, &result)) {
return nullptr;
}
// Step 3.
return CreateTemporalDuration(cx, result);
}
/**
* ToTemporalDuration ( item )
*/
bool js::temporal::ToTemporalDuration(JSContext* cx, Handle<Value> item,
Duration* result) {
auto obj = ToTemporalDuration(cx, item);
if (!obj) {
return false;
}
*result = ToDuration(&obj.unwrap());
return true;
}
/**
* DaysUntil ( earlier, later )
*/
int32_t js::temporal::DaysUntil(const PlainDate& earlier,
const PlainDate& later) {
MOZ_ASSERT(ISODateTimeWithinLimits(earlier));
MOZ_ASSERT(ISODateTimeWithinLimits(later));
// Steps 1-2.
int32_t epochDaysEarlier = MakeDay(earlier);
MOZ_ASSERT(std::abs(epochDaysEarlier) <= 100'000'000);
// Steps 3-4.
int32_t epochDaysLater = MakeDay(later);
MOZ_ASSERT(std::abs(epochDaysLater) <= 100'000'000);
// Step 5.
return epochDaysLater - epochDaysEarlier;
}
/**
* MoveRelativeDate ( calendarRec, relativeTo, duration )
*/
static bool MoveRelativeDate(
JSContext* cx, Handle<CalendarRecord> calendar,
Handle<Wrapped<PlainDateObject*>> relativeTo, const Duration& duration,
MutableHandle<Wrapped<PlainDateObject*>> relativeToResult,
int32_t* daysResult) {
auto* unwrappedRelativeTo = relativeTo.unwrap(cx);
if (!unwrappedRelativeTo) {
return false;
}
auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
// Step 1.
auto newDate = AddDate(cx, calendar, relativeTo, duration);
if (!newDate) {
return false;
}
auto later = ToPlainDate(&newDate.unwrap());
relativeToResult.set(newDate);
// Step 2.
*daysResult = DaysUntil(relativeToDate, later);
MOZ_ASSERT(std::abs(*daysResult) <= 200'000'000);
// Step 3.
return true;
}
/**
* MoveRelativeZonedDateTime ( zonedDateTime, calendarRec, timeZoneRec, years,
* months, weeks, days, precalculatedPlainDateTime )
*/
static bool MoveRelativeZonedDateTime(
JSContext* cx, Handle<ZonedDateTime> zonedDateTime,
Handle<CalendarRecord> calendar, Handle<TimeZoneRecord> timeZone,
const Duration& duration,
mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
MutableHandle<ZonedDateTime> result) {
// Step 1.
MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
// Step 2.
MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
timeZone, TimeZoneMethod::GetPossibleInstantsFor));
// Step 3.
Instant intermediateNs;
if (precalculatedPlainDateTime) {
if (!AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar,
duration.date(), *precalculatedPlainDateTime,
&intermediateNs)) {
return false;
}
} else {
if (!AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar,
duration.date(), &intermediateNs)) {
return false;
}
}
MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
// Step 4.
result.set(ZonedDateTime{intermediateNs, zonedDateTime.timeZone(),
zonedDateTime.calendar()});
return true;
}
/**
* 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;
}
Rooted<BigInt*> nanoseconds(
cx, TotalDurationNanosecondsSlow(cx, duration.time()));
if (!nanoseconds) {
return false;
}
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));
return NanosecondsToDays(cx, nanoseconds, zonedRelativeTo, timeZone,
result);
}
auto* nanoseconds = TotalDurationNanosecondsSlow(cx, duration.time());
if (!nanoseconds) {
return false;
}
// 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);
}
/**
* CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
* microseconds, nanoseconds )
*/
static TimeDuration CreateTimeDurationRecord(int64_t days, int64_t hours,
int64_t minutes, int64_t seconds,
int64_t milliseconds,
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)}));
// Step 2.
return {
double(days), double(hours), double(minutes),
double(seconds), double(milliseconds), double(microseconds),
double(nanoseconds),
};
}
/**
* 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) {
// Step 1.
MOZ_ASSERT(IsValidDuration({0, 0, 0, days, hours, minutes, seconds,
milliseconds, microseconds, 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),
};
}
/**
* BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds,
* microseconds, nanoseconds, largestUnit )
*
* BalancePossiblyInfiniteTimeDuration ( days, hours, minutes, seconds,
* milliseconds, microseconds, nanoseconds, largestUnit )
*/
static TimeDuration BalanceTimeDuration(int64_t nanoseconds,
TemporalUnit largestUnit) {
// Step 1. (Handled in caller.)
// Step 2.
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.)
//
// We don't need to convert to positive numbers, because integer division
// truncates and the %-operator has modulo semantics.
// Steps 5-11.
switch (largestUnit) {
// Step 5.
case TemporalUnit::Year:
case TemporalUnit::Month:
case TemporalUnit::Week:
case TemporalUnit::Day: {
// Step 5.a.
microseconds = nanoseconds / 1000;
// Step 5.b.
nanoseconds = nanoseconds % 1000;
// Step 5.c.
milliseconds = microseconds / 1000;
// Step 5.d.
microseconds = microseconds % 1000;
// Step 5.e.
seconds = milliseconds / 1000;
// Step 5.f.
milliseconds = milliseconds % 1000;
// Step 5.g.
minutes = seconds / 60;
// Step 5.h.
seconds = seconds % 60;
// Step 5.i.
hours = minutes / 60;
// Step 5.j.
minutes = minutes % 60;
// Step 5.k.
days = hours / 24;
// Step 5.l.
hours = hours % 24;
break;
}
case TemporalUnit::Hour: {
// Step 6.a.
microseconds = nanoseconds / 1000;
// Step 6.b.
nanoseconds = nanoseconds % 1000;
// Step 6.c.
milliseconds = microseconds / 1000;
// Step 6.d.
microseconds = microseconds % 1000;
// Step 6.e.
seconds = milliseconds / 1000;
// Step 6.f.
milliseconds = milliseconds % 1000;
// Step 6.g.
minutes = seconds / 60;
// Step 6.h.
seconds = seconds % 60;
// Step 6.i.
hours = minutes / 60;
// Step 6.j.
minutes = minutes % 60;
break;
}
// Step 7.
case TemporalUnit::Minute: {
// Step 7.a.
microseconds = nanoseconds / 1000;
// Step 7.b.
nanoseconds = nanoseconds % 1000;
// Step 7.c.
milliseconds = microseconds / 1000;
// Step 7.d.
microseconds = microseconds % 1000;
// Step 7.e.
seconds = milliseconds / 1000;
// Step 7.f.
milliseconds = milliseconds % 1000;
// Step 7.g.
minutes = seconds / 60;
// Step 7.h.
seconds = seconds % 60;
break;
}
// Step 8.
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.
microseconds = nanoseconds / 1000;
// Step 9.b.
nanoseconds = nanoseconds % 1000;
// Step 9.c.
milliseconds = microseconds / 1000;
// Step 9.d.
microseconds = microseconds % 1000;
break;
}
// Step 10.
case TemporalUnit::Microsecond: {
// Step 10.a.
microseconds = nanoseconds / 1000;
// Step 10.b.
nanoseconds = nanoseconds % 1000;
break;
}
// Step 11.
case TemporalUnit::Nanosecond: {
// Nothing to do.
break;
}
case TemporalUnit::Auto:
MOZ_CRASH("Unexpected temporal unit");
}
// Step 12. (Not applicable, all values are finite)
// Step 13.
return CreateTimeDurationRecord(days, hours, minutes, seconds, milliseconds,
microseconds, nanoseconds);
}
/**
* BalancePossiblyInfiniteTimeDuration ( days, hours, minutes, seconds,
* milliseconds, microseconds, nanoseconds, 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;
}
// 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);
// Steps 4-5.
//
// 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;
}
Rooted<BigInt*> sixty(cx, BigInt::createFromInt64(cx, 60));
if (!sixty) {
return false;
}
Rooted<BigInt*> twentyfour(cx, BigInt::createFromInt64(cx, 24));
if (!twentyfour) {
return false;
}
switch (largestUnit) {
// Step 6.
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;
}
// Step 7.
case TemporalUnit::Hour: {
// Steps 7.a-b.
if (!BigInt::divmod(cx, nanoseconds, thousand, &microseconds,
&nanoseconds)) {
return false;
}
// Steps 7.c-d.
if (!BigInt::divmod(cx, microseconds, thousand, &milliseconds,
&microseconds)) {
return false;
}
// Steps 7.e-f.
if (!BigInt::divmod(cx, milliseconds, thousand, &seconds,
&milliseconds)) {
return false;
}
// Steps 7.g-h.
if (!BigInt::divmod(cx, seconds, sixty, &minutes, &seconds)) {
return false;
}
// Steps 7.i-j.
if (!BigInt::divmod(cx, minutes, sixty, &hours, &minutes)) {
return false;
}
break;
}
// Step 8.
case TemporalUnit::Minute: {
// Steps 8.a-b.
if (!BigInt::divmod(cx, nanoseconds, thousand, &microseconds,
&nanoseconds)) {
return false;
}
// Steps 8.c-d.
if (!BigInt::divmod(cx, microseconds, thousand, &milliseconds,
&microseconds)) {
return false;
}
// Steps 8.e-f.
if (!BigInt::divmod(cx, milliseconds, thousand, &seconds,
&milliseconds)) {
return false;
}
// Steps 8.g-h.
if (!BigInt::divmod(cx, seconds, sixty, &minutes, &seconds)) {
return false;
}
break;
}
// Step 9.
case TemporalUnit::Second: {
// Steps 9.a-b.
if (!BigInt::divmod(cx, nanoseconds, thousand, &microseconds,
&nanoseconds)) {
return false;
}
// Steps 9.c-d.
if (!BigInt::divmod(cx, microseconds, thousand, &milliseconds,
&microseconds)) {
return false;
}
// Steps 9.e-f.
if (!BigInt::divmod(cx, milliseconds, thousand, &seconds,
&milliseconds)) {
return false;
}
break;
}
// Step 10.
case TemporalUnit::Millisecond: {
// Steps 10.a-b.
if (!BigInt::divmod(cx, nanoseconds, thousand, &microseconds,
&nanoseconds)) {
return false;
}
// Steps 10.c-d.
if (!BigInt::divmod(cx, microseconds, thousand, &milliseconds,
&microseconds)) {
return false;
}
break;
}
// Step 11.
case TemporalUnit::Microsecond: {
// Steps 11.a-b.
if (!BigInt::divmod(cx, nanoseconds, thousand, &microseconds,
&nanoseconds)) {
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,
};
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;
}
return ThrowIfInvalidDuration(cx, result->toDuration());
}
/**
* 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,
mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
TimeDuration* result) {
// Step 1. (Not applicable)
// Step 2.
auto intermediateNs = relativeTo.instant();
// Step 3.
const auto& startInstant = relativeTo.instant();
// Step 4.
PlainDateTime startDateTime;
if (duration.days != 0) {
// Step 4.a.
if (!precalculatedPlainDateTime) {
if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &startDateTime)) {
return false;
}
precalculatedPlainDateTime =
mozilla::SomeRef<const PlainDateTime>(startDateTime);
}
// Steps 4.b-c.
Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601));
if (!AddDaysToZonedDateTime(cx, startInstant, *precalculatedPlainDateTime,
timeZone, isoCalendar, duration.days,
&intermediateNs)) {
return false;
}
}
// Step 5.
Instant 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));
// Step 7.
if (nanoseconds == InstantSpan{}) {
*result = {};
return true;
}
// Steps 8-9.
double days = 0;
if (TemporalUnit::Year <= largestUnit && largestUnit <= TemporalUnit::Day) {
// Step 8.a.
if (!precalculatedPlainDateTime) {
if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &startDateTime)) {
return false;
}
precalculatedPlainDateTime =
mozilla::SomeRef<const PlainDateTime>(startDateTime);
}
// Step 8.b.
Rooted<temporal::NanosecondsAndDays> nanosAndDays(cx);
if (!NanosecondsToDays(cx, nanoseconds, relativeTo, timeZone,
*precalculatedPlainDateTime, &nanosAndDays)) {
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
// Step 8.d.
nanoseconds = nanosAndDays.nanoseconds();
MOZ_ASSERT_IF(days > 0, nanoseconds >= InstantSpan{});
MOZ_ASSERT_IF(days < 0, nanoseconds <= InstantSpan{});
// Step 8.e.
largestUnit = TemporalUnit::Hour;
}
// Step 10. (Not applicable in our implementation.)
// Steps 11-12.
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;
}
}
// Step 13.
*result = {
days,
balanceResult.hours,
balanceResult.minutes,
balanceResult.seconds,
balanceResult.milliseconds,
balanceResult.microseconds,
balanceResult.nanoseconds,
};
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}));
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})) {
return false;
}
*result = {years, months, weeks, days};
return true;
}
static bool UnbalanceDateDurationRelativeHasEffect(const Duration& duration,
TemporalUnit largestUnit) {
MOZ_ASSERT(largestUnit != TemporalUnit::Auto);
// Steps 2, 3.a-b, 4.a-b, 6-7.
return (largestUnit > TemporalUnit::Year && duration.years != 0) ||
(largestUnit > TemporalUnit::Month && duration.months != 0) ||
(largestUnit > TemporalUnit::Week && duration.weeks != 0);
}
/**
* UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
* plainRelativeTo, calendarRec )
*/
static bool UnbalanceDateDurationRelative(
JSContext* cx, const Duration& 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;
// Step 1. (Not applicable in our implementation.)
// Steps 2, 3.a, 4.a, and 6.
if (!UnbalanceDateDurationRelativeHasEffect(duration, largestUnit)) {
// Steps 2.a, 3.a, 4.a, and 6.
*result = CreateDateDurationRecord(years, months, weeks, days);
return true;
}
// Step 3.
if (largestUnit == TemporalUnit::Month) {
// Step 3.a. (Handled above)
MOZ_ASSERT(years != 0);
// Step 3.b. (Not applicable in our implementation.)
// Step 3.c.
MOZ_ASSERT(
CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
// Step 3.d.
MOZ_ASSERT(
CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
// Step 3.e.
auto yearsDuration = Duration{years};
// Step 3.f.
Rooted<Wrapped<PlainDateObject*>> later(
cx, CalendarDateAdd(cx, calendar, plainRelativeTo, yearsDuration));
if (!later) {
return false;
}
// Steps 3.g-i.
Duration untilResult;
if (!CalendarDateUntil(cx, calendar, plainRelativeTo, later,
TemporalUnit::Month, &untilResult)) {
return false;
}
// Step 3.j.
double 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.
return CreateDateDurationRecord(cx, 0, months + yearsInMonths, weeks, days,
result);
}
// Step 4.
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 4.e.
auto later =
CalendarDateAdd(cx, calendar, plainRelativeTo, yearsMonthsDuration);
if (!later) {
return false;
}
auto laterDate = ToPlainDate(&later.unwrap());
auto* unwrappedRelativeTo = plainRelativeTo.unwrap(cx);
if (!unwrappedRelativeTo) {
return false;
}
auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
// Step 4.f.
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.
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 9.
auto yearsMonthsWeeksDuration = Duration{years, months, weeks};
// Step 10.
auto later =
CalendarDateAdd(cx, calendar, plainRelativeTo, yearsMonthsWeeksDuration);
if (!later) {
return false;
}
auto laterDate = ToPlainDate(&later.unwrap());
auto* unwrappedRelativeTo = plainRelativeTo.unwrap(cx);
if (!unwrappedRelativeTo) {
return false;
}
auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
// Step 11.
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.
return CreateDateDurationRecord(cx, 0, 0, 0, days + yearsMonthsWeeksInDay,
result);
}
/**
* UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
* plainRelativeTo, calendarRec )
*/
static bool UnbalanceDateDurationRelative(JSContext* cx,
const Duration& 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.
if (!UnbalanceDateDurationRelativeHasEffect(duration, largestUnit)) {
// Steps 2.a, 3.a, 4.a, and 6.
*result = CreateDateDurationRecord(years, months, weeks, days);
return true;
}
// Step 5. (Not applicable.)
// Steps 3.b, 4.b, and 7.
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_DURATION_UNCOMPARABLE, "calendar");
return false;
}
/**
* BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
* smallestUnit, plainRelativeTo, calendarRec )
*/
static bool BalanceDateDurationRelative(
JSContext* cx, const Duration& 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;
// FIXME: spec issue - effectful code paths should be more fine-grained
// similar to UnbalanceDateDurationRelative. For example:
// 1. If largestUnit = "year" and days = 0 and months = 0, then no-op.
// 2. Else if largestUnit = "month" and days = 0, then no-op.
// 3. Else if days = 0, then no-op.
//
// Also note that |weeks| is never balanced, even when non-zero.
// Step 1. (Not applicable in our implementation.)
// Steps 2-4.
if (largestUnit > TemporalUnit::Week ||
(years == 0 && months == 0 && weeks == 0 && days == 0)) {
// Step 4.a.
*result = CreateDateDurationRecord(years, months, weeks, days);
return true;
}
// Step 5.
if (!plainRelativeTo) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
"relativeTo");
return false;
}
// Step 6.
MOZ_ASSERT(
CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
// Step 7.
MOZ_ASSERT(
CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
// Steps 8-9. (Not applicable in our implementation.)
auto untilAddedDate = [&](const Duration& duration, Duration* untilResult) {
Rooted<Wrapped<PlainDateObject*>> later(
cx, AddDate(cx, calendar, plainRelativeTo, duration));
if (!later) {
return false;
}
return CalendarDateUntil(cx, calendar, plainRelativeTo, later, largestUnit,
untilResult);
};
// Step 10.
if (largestUnit == TemporalUnit::Year) {
// Step 10.a.
if (smallestUnit == TemporalUnit::Week) {
// Step 10.a.i.
MOZ_ASSERT(days == 0);
// Step 10.a.ii.
auto yearsMonthsDuration = Duration{years, months};
// Steps 10.a.iii-iv.
Duration 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);
return true;
}
// Step 10.b.
auto yearsMonthsWeeksDaysDuration = Duration{years, months, weeks, days};
// Steps 10.c-d.
Duration untilResult;
if (!untilAddedDate(yearsMonthsWeeksDaysDuration, &untilResult)) {
return false;
}
// FIXME: spec bug - CreateDateDurationRecord is infallible
// Step 10.e.
*result = CreateDateDurationRecord(untilResult.years, untilResult.months,
untilResult.weeks, untilResult.days);
return true;
}
// Step 11.
if (largestUnit == TemporalUnit::Month) {
// Step 11.a.
MOZ_ASSERT(years == 0);
// Step 11.b.
if (smallestUnit == TemporalUnit::Week) {
// Step 10.b.i.
MOZ_ASSERT(days == 0);
// Step 10.b.ii.
*result = CreateDateDurationRecord(0, months, weeks, 0);
return true;
}
// Step 11.c.
auto monthsWeeksDaysDuration = Duration{0, months, weeks, days};
// Steps 11.d-e.
Duration untilResult;
if (!untilAddedDate(monthsWeeksDaysDuration, &untilResult)) {
return false;
}
// FIXME: spec bug - CreateDateDurationRecord is infallible
// Step 11.f.
*result = CreateDateDurationRecord(0, untilResult.months, untilResult.weeks,
untilResult.days);
return true;