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/ZonedDateTime.h"
#include "mozilla/Assertions.h"
#include "mozilla/Maybe.h"
#include <cstdlib>
#include <utility>
#include "jspubtd.h"
#include "NamespaceImports.h"
#include "builtin/temporal/Calendar.h"
#include "builtin/temporal/Duration.h"
#include "builtin/temporal/Instant.h"
#include "builtin/temporal/PlainDate.h"
#include "builtin/temporal/PlainDateTime.h"
#include "builtin/temporal/PlainMonthDay.h"
#include "builtin/temporal/PlainTime.h"
#include "builtin/temporal/PlainYearMonth.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/ToString.h"
#include "builtin/temporal/Wrapped.h"
#include "ds/IdValuePair.h"
#include "gc/AllocKind.h"
#include "gc/Barrier.h"
#include "js/AllocPolicy.h"
#include "js/CallArgs.h"
#include "js/CallNonGenericMethod.h"
#include "js/Class.h"
#include "js/ComparisonOperators.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/TracingAPI.h"
#include "js/Value.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/JSContext-inl.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 IsZonedDateTime(Handle<Value> v) {
return v.isObject() && v.toObject().is<ZonedDateTimeObject>();
}
// Returns |RoundNumberToIncrement(offsetNanoseconds, 60 × 10^9, "halfExpand")|.
static int64_t RoundNanosecondsToMinutesIncrement(int64_t offsetNanoseconds) {
MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
constexpr int64_t increment = ToNanoseconds(TemporalUnit::Minute);
int64_t quotient = offsetNanoseconds / increment;
int64_t remainder = offsetNanoseconds % increment;
if (std::abs(remainder * 2) >= increment) {
quotient += (offsetNanoseconds > 0 ? 1 : -1);
}
return quotient * increment;
}
/**
* InterpretISODateTimeOffset ( year, month, day, hour, minute, second,
* millisecond, microsecond, nanosecond, offsetBehaviour, offsetNanoseconds,
* timeZoneRec, disambiguation, offsetOption, matchBehaviour )
*/
bool js::temporal::InterpretISODateTimeOffset(
JSContext* cx, const PlainDateTime& dateTime,
OffsetBehaviour offsetBehaviour, int64_t offsetNanoseconds,
Handle<TimeZoneRecord> timeZone, TemporalDisambiguation disambiguation,
TemporalOffset offsetOption, MatchBehaviour matchBehaviour,
Instant* result) {
MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
// Step 1.
MOZ_ASSERT(IsValidISODate(dateTime.date));
// Step 2.
MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
// Step 3.
MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
timeZone, TimeZoneMethod::GetPossibleInstantsFor));
// Step 4.
Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
Rooted<PlainDateTimeWithCalendar> temporalDateTime(cx);
if (!CreateTemporalDateTime(cx, dateTime, calendar, &temporalDateTime)) {
return false;
}
// Step 5.
if (offsetBehaviour == OffsetBehaviour::Wall ||
offsetOption == TemporalOffset::Ignore) {
// Steps 5.a-b.
return GetInstantFor(cx, timeZone, temporalDateTime, disambiguation,
result);
}
// Step 6.
if (offsetBehaviour == OffsetBehaviour::Exact ||
offsetOption == TemporalOffset::Use) {
// Step 6.a.
auto epochNanoseconds = GetUTCEpochNanoseconds(
dateTime, InstantSpan::fromNanoseconds(offsetNanoseconds));
// Step 6.b.
if (!IsValidEpochInstant(epochNanoseconds)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_INSTANT_INVALID);
return false;
}
// Step 6.c.
*result = epochNanoseconds;
return true;
}
// Step 7.
MOZ_ASSERT(offsetBehaviour == OffsetBehaviour::Option);
// Step 8.
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.
if (!possibleInstants.empty()) {
// Step 11.a.
Rooted<Wrapped<InstantObject*>> candidate(cx);
for (size_t i = 0; i < possibleInstants.length(); i++) {
candidate = possibleInstants[i];
// Step 11.a.i.
int64_t candidateNanoseconds;
if (!GetOffsetNanosecondsFor(cx, timeZone, candidate,
&candidateNanoseconds)) {
return false;
}
MOZ_ASSERT(std::abs(candidateNanoseconds) <
ToNanoseconds(TemporalUnit::Day));
// Step 11.a.ii.
if (candidateNanoseconds == offsetNanoseconds) {
auto* unwrapped = candidate.unwrap(cx);
if (!unwrapped) {
return false;
}
*result = ToInstant(unwrapped);
return true;
}
// Step 11.a.iii.
if (matchBehaviour == MatchBehaviour::MatchMinutes) {
// Step 11.a.iii.1.
int64_t roundedCandidateNanoseconds =
RoundNanosecondsToMinutesIncrement(candidateNanoseconds);
// Step 11.a.iii.2.
if (roundedCandidateNanoseconds == offsetNanoseconds) {
auto* unwrapped = candidate.unwrap(cx);
if (!unwrapped) {
return false;
}
// Step 11.a.iii.2.a.
*result = ToInstant(unwrapped);
return true;
}
}
}
}
// Step 12.
if (offsetOption == TemporalOffset::Reject) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_ZONED_DATE_TIME_NO_TIME_FOUND);
return false;
}
// Step 13.
Rooted<Wrapped<InstantObject*>> instant(cx);
if (!DisambiguatePossibleInstants(cx, possibleInstants, timeZone,
ToPlainDateTime(temporalDateTime),
disambiguation, &instant)) {
return false;
}
auto* unwrappedInstant = instant.unwrap(cx);
if (!unwrappedInstant) {
return false;
}
// Step 14.
*result = ToInstant(unwrappedInstant);
return true;
}
/**
* ToTemporalZonedDateTime ( item [ , options ] )
*/
static bool ToTemporalZonedDateTime(JSContext* cx, Handle<Value> item,
Handle<JSObject*> maybeOptions,
MutableHandle<ZonedDateTime> result) {
// Step 1. (Not applicable in our implementation)
// Step 2.
Rooted<PlainObject*> maybeResolvedOptions(cx);
if (maybeOptions) {
maybeResolvedOptions = SnapshotOwnProperties(cx, maybeOptions);
if (!maybeResolvedOptions) {
return false;
}
}
// Step 3.
auto offsetBehaviour = OffsetBehaviour::Option;
// Step 4.
auto matchBehaviour = MatchBehaviour::MatchExactly;
// Step 7. (Reordered)
int64_t offsetNanoseconds = 0;
// Step 5.
Rooted<CalendarValue> calendar(cx);
Rooted<TimeZoneValue> timeZone(cx);
PlainDateTime dateTime;
auto disambiguation = TemporalDisambiguation::Compatible;
auto offsetOption = TemporalOffset::Reject;
if (item.isObject()) {
Rooted<JSObject*> itemObj(cx, &item.toObject());
// Step 5.a.
if (auto* zonedDateTime = itemObj->maybeUnwrapIf<ZonedDateTimeObject>()) {
auto instant = ToInstant(zonedDateTime);
Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());
if (!timeZone.wrap(cx)) {
return false;
}
if (!calendar.wrap(cx)) {
return false;
}
result.set(ZonedDateTime{instant, timeZone, calendar});
return true;
}
// Step 5.b.
if (!GetTemporalCalendarWithISODefault(cx, itemObj, &calendar)) {
return false;
}
// Step 5.c.
Rooted<CalendarRecord> calendarRec(cx);
if (!CreateCalendarMethodsRecord(cx, calendar,
{
CalendarMethod::DateFromFields,
CalendarMethod::Fields,
},
&calendarRec)) {
return false;
}
// Step 5.d.
JS::RootedVector<PropertyKey> fieldNames(cx);
if (!CalendarFields(cx, calendarRec,
{CalendarField::Day, CalendarField::Month,
CalendarField::MonthCode, CalendarField::Year},
&fieldNames)) {
return false;
}
// Step 5.e.
if (!AppendSorted(cx, fieldNames.get(),
{
TemporalField::Hour,
TemporalField::Microsecond,
TemporalField::Millisecond,
TemporalField::Minute,
TemporalField::Nanosecond,
TemporalField::Offset,
TemporalField::Second,
TemporalField::TimeZone,
})) {
return false;
}
// Step 5.f.
Rooted<PlainObject*> fields(
cx, PrepareTemporalFields(cx, itemObj, fieldNames,
{TemporalField::TimeZone}));
if (!fields) {
return false;
}
// Step 5.g.
Rooted<Value> timeZoneValue(cx);
if (!GetProperty(cx, fields, fields, cx->names().timeZone,
&timeZoneValue)) {
return false;
}
// Step 5.h.
if (!ToTemporalTimeZone(cx, timeZoneValue, &timeZone)) {
return false;
}
// Step 5.i.
Rooted<Value> offsetValue(cx);
if (!GetProperty(cx, fields, fields, cx->names().offset, &offsetValue)) {
return false;
}
// Step 5.j.
MOZ_ASSERT(offsetValue.isString() || offsetValue.isUndefined());
// Step 5.k.
Rooted<JSString*> offsetString(cx);
if (offsetValue.isString()) {
offsetString = offsetValue.toString();
} else {
offsetBehaviour = OffsetBehaviour::Wall;
}
if (maybeResolvedOptions) {
// Steps 5.l-m.
if (!ToTemporalDisambiguation(cx, maybeResolvedOptions,
&disambiguation)) {
return false;
}
// Step 5.n.
if (!ToTemporalOffset(cx, maybeResolvedOptions, &offsetOption)) {
return false;
}
// Step 5.o.
if (!InterpretTemporalDateTimeFields(cx, calendarRec, fields,
maybeResolvedOptions, &dateTime)) {
return false;
}
} else {
// Steps 5.l-n. (Not applicable)
// Step 5.o.
if (!InterpretTemporalDateTimeFields(cx, calendarRec, fields,
&dateTime)) {
return false;
}
}
// Step 8.
if (offsetBehaviour == OffsetBehaviour::Option) {
if (!ParseDateTimeUTCOffset(cx, offsetString, &offsetNanoseconds)) {
return false;
}
}
} else {
// Step 6.a.
if (!item.isString()) {
ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, item,
nullptr, "not a string");
return false;
}
Rooted<JSString*> string(cx, item.toString());
// Case 1: 19700101Z[+02:00]
// { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: "+02:00" }
//
// Case 2: 19700101+00:00[+02:00]
// { [[Z]]: false, [[OffsetString]]: "+00:00", [[Name]]: "+02:00" }
//
// Case 3: 19700101[+02:00]
// { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "+02:00" }
//
// Case 4: 19700101Z[Europe/Berlin]
// { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" }
//
// Case 5: 19700101+00:00[Europe/Berlin]
// { [[Z]]: false, [[OffsetString]]: "+00:00", [[Name]]: "Europe/Berlin" }
//
// Case 6: 19700101[Europe/Berlin]
// { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" }
// Steps 6.b-c.
bool isUTC;
bool hasOffset;
int64_t timeZoneOffset;
Rooted<ParsedTimeZone> timeZoneString(cx);
Rooted<JSString*> calendarString(cx);
if (!ParseTemporalZonedDateTimeString(cx, string, &dateTime, &isUTC,
&hasOffset, &timeZoneOffset,
&timeZoneString, &calendarString)) {
return false;
}
// Step 6.d.
MOZ_ASSERT(timeZoneString);
// Step 6.e.
if (!ToTemporalTimeZone(cx, timeZoneString, &timeZone)) {
return false;
}
// Step 6.f. (Not applicable in our implementation.)
// Step 6.g.
if (isUTC) {
offsetBehaviour = OffsetBehaviour::Exact;
}
// Step 6.h.
else if (!hasOffset) {
offsetBehaviour = OffsetBehaviour::Wall;
}
// Steps 6.i-l.
if (calendarString) {
if (!ToBuiltinCalendar(cx, calendarString, &calendar)) {
return false;
}
} else {
calendar.set(CalendarValue(cx->names().iso8601));
}
// Step 6.m.
matchBehaviour = MatchBehaviour::MatchMinutes;
if (maybeResolvedOptions) {
// Step 6.n.
if (!ToTemporalDisambiguation(cx, maybeResolvedOptions,
&disambiguation)) {
return false;
}
// Step 6.o.
if (!ToTemporalOffset(cx, maybeResolvedOptions, &offsetOption)) {
return false;
}
// Step 6.p.
TemporalOverflow ignored;
if (!ToTemporalOverflow(cx, maybeResolvedOptions, &ignored)) {
return false;
}
}
// Step 8.
if (offsetBehaviour == OffsetBehaviour::Option) {
MOZ_ASSERT(hasOffset);
offsetNanoseconds = timeZoneOffset;
}
}
// Step 9.
Rooted<TimeZoneRecord> timeZoneRec(cx);
if (!CreateTimeZoneMethodsRecord(cx, timeZone,
{
TimeZoneMethod::GetOffsetNanosecondsFor,
TimeZoneMethod::GetPossibleInstantsFor,
},
&timeZoneRec)) {
return false;
}
// Step 10.
Instant epochNanoseconds;
if (!InterpretISODateTimeOffset(
cx, dateTime, offsetBehaviour, offsetNanoseconds, timeZoneRec,
disambiguation, offsetOption, matchBehaviour, &epochNanoseconds)) {
return false;
}
// Step 11.
result.set(ZonedDateTime{epochNanoseconds, timeZone, calendar});
return true;
}
/**
* ToTemporalZonedDateTime ( item [ , options ] )
*/
static bool ToTemporalZonedDateTime(JSContext* cx, Handle<Value> item,
MutableHandle<ZonedDateTime> result) {
return ToTemporalZonedDateTime(cx, item, nullptr, result);
}
/**
* ToTemporalZonedDateTime ( item [ , options ] )
*/
static ZonedDateTimeObject* ToTemporalZonedDateTime(
JSContext* cx, Handle<Value> item, Handle<JSObject*> maybeOptions) {
Rooted<ZonedDateTime> result(cx);
if (!ToTemporalZonedDateTime(cx, item, maybeOptions, &result)) {
return nullptr;
}
return CreateTemporalZonedDateTime(cx, result.instant(), result.timeZone(),
result.calendar());
}
/**
* CreateTemporalZonedDateTime ( epochNanoseconds, timeZone, calendar [ ,
* newTarget ] )
*/
static ZonedDateTimeObject* CreateTemporalZonedDateTime(
JSContext* cx, const CallArgs& args, Handle<BigInt*> epochNanoseconds,
Handle<TimeZoneValue> timeZone, Handle<CalendarValue> calendar) {
// Step 1.
MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds));
// Steps 3-4.
Rooted<JSObject*> proto(cx);
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_ZonedDateTime,
&proto)) {
return nullptr;
}
auto* obj = NewObjectWithClassProto<ZonedDateTimeObject>(cx, proto);
if (!obj) {
return nullptr;
}
// Step 4.
auto instant = ToInstant(epochNanoseconds);
obj->setFixedSlot(ZonedDateTimeObject::SECONDS_SLOT,
NumberValue(instant.seconds));
obj->setFixedSlot(ZonedDateTimeObject::NANOSECONDS_SLOT,
Int32Value(instant.nanoseconds));
// Step 5.
obj->setFixedSlot(ZonedDateTimeObject::TIMEZONE_SLOT, timeZone.toSlotValue());
// Step 6.
obj->setFixedSlot(ZonedDateTimeObject::CALENDAR_SLOT, calendar.toValue());
// Step 7.
return obj;
}
/**
* CreateTemporalZonedDateTime ( epochNanoseconds, timeZone, calendar [ ,
* newTarget ] )
*/
ZonedDateTimeObject* js::temporal::CreateTemporalZonedDateTime(
JSContext* cx, const Instant& instant, Handle<TimeZoneValue> timeZone,
Handle<CalendarValue> calendar) {
// Step 1.
MOZ_ASSERT(IsValidEpochInstant(instant));
// Steps 2-3.
auto* obj = NewBuiltinClassInstance<ZonedDateTimeObject>(cx);
if (!obj) {
return nullptr;
}
// Step 4.
obj->setFixedSlot(ZonedDateTimeObject::SECONDS_SLOT,
NumberValue(instant.seconds));
obj->setFixedSlot(ZonedDateTimeObject::NANOSECONDS_SLOT,
Int32Value(instant.nanoseconds));
// Step 5.
obj->setFixedSlot(ZonedDateTimeObject::TIMEZONE_SLOT, timeZone.toSlotValue());
// Step 6.
obj->setFixedSlot(ZonedDateTimeObject::CALENDAR_SLOT, calendar.toValue());
// Step 7.
return obj;
}
struct PlainDateTimeAndInstant {
PlainDateTime dateTime;
Instant instant;
};
/**
* AddDaysToZonedDateTime ( instant, dateTime, timeZoneRec, calendar, days [ ,
* overflow ] )
*/
static bool AddDaysToZonedDateTime(JSContext* cx, const Instant& instant,
const PlainDateTime& dateTime,
Handle<TimeZoneRecord> timeZone,
Handle<CalendarValue> calendar, double days,
TemporalOverflow overflow,
PlainDateTimeAndInstant* result) {
// Step 1. (Not applicable in our implementation.)
// Step 2. (Not applicable)
// Step 3.
if (days == 0) {
*result = {dateTime, instant};
return true;
}
// Step 4.
PlainDate addedDate;
if (!AddISODate(cx, dateTime.date, {0, 0, 0, days}, overflow, &addedDate)) {
return false;
}
// Step 5.
Rooted<PlainDateTimeWithCalendar> dateTimeResult(cx);
if (!CreateTemporalDateTime(cx, {addedDate, dateTime.time}, calendar,
&dateTimeResult)) {
return false;
}
// Step 6.
Instant instantResult;
if (!GetInstantFor(cx, timeZone, dateTimeResult,
TemporalDisambiguation::Compatible, &instantResult)) {
return false;
}
// Step 7.
*result = {ToPlainDateTime(dateTimeResult), instantResult};
return true;
}
/**
* AddDaysToZonedDateTime ( instant, dateTime, timeZoneRec, calendar, days [ ,
* overflow ] )
*/
bool js::temporal::AddDaysToZonedDateTime(
JSContext* cx, const Instant& instant, const PlainDateTime& dateTime,
Handle<TimeZoneRecord> timeZone, Handle<CalendarValue> calendar,
double days, TemporalOverflow overflow, Instant* result) {
// Steps 1-7.
PlainDateTimeAndInstant dateTimeAndInstant;
if (!::AddDaysToZonedDateTime(cx, instant, dateTime, timeZone, calendar, days,
overflow, &dateTimeAndInstant)) {
return false;
}
*result = dateTimeAndInstant.instant;
return true;
}
/**
* AddDaysToZonedDateTime ( instant, dateTime, timeZoneRec, calendar, days [ ,
* overflow ] )
*/
bool js::temporal::AddDaysToZonedDateTime(JSContext* cx, const Instant& instant,
const PlainDateTime& dateTime,
Handle<TimeZoneRecord> timeZone,
Handle<CalendarValue> calendar,
double days, Instant* result) {
// Step 2.
auto overflow = TemporalOverflow::Constrain;
// Steps 1 and 3-7.
return AddDaysToZonedDateTime(cx, instant, dateTime, timeZone, calendar, days,
overflow, result);
}
/**
* AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months,
* weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds
* [ , precalculatedPlainDateTime [ , options ] ] )
*/
static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds,
Handle<TimeZoneRecord> timeZone,
Handle<CalendarRecord> calendar,
const Duration& 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()));
// Step 1.
MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
timeZone, TimeZoneMethod::GetPossibleInstantsFor));
// Steps 2-3.
MOZ_ASSERT_IF(!dateTime,
TimeZoneMethodsRecordHasLookedUp(
timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
// Steps 4-5. (Not applicable in our implementation)
// Step 6.
if (duration.years == 0 && duration.months == 0 && duration.weeks == 0 &&
duration.days == 0) {
// Step 6.a.
return AddInstant(cx, epochNanoseconds, duration, result);
}
// Step 7. (Not applicable in our implementation)
// Steps 8-9.
PlainDateTime temporalDateTime;
if (dateTime) {
// Step 8.a.
temporalDateTime = *dateTime;
} else {
// Step 9.a.
if (!GetPlainDateTimeFor(cx, timeZone, epochNanoseconds,
&temporalDateTime)) {
return false;
}
}
auto& [date, time] = temporalDateTime;
// Step 10.
if (duration.years == 0 && duration.months == 0 && duration.weeks == 0) {
// Step 10.a.
auto overflow = TemporalOverflow::Constrain;
if (maybeOptions) {
if (!ToTemporalOverflow(cx, maybeOptions, &overflow)) {
return false;
}
}
// Step 10.b.
Instant intermediate;
if (!AddDaysToZonedDateTime(cx, epochNanoseconds, temporalDateTime,
timeZone, calendar.receiver(), duration.days,
overflow, &intermediate)) {
return false;
}
// Step 10.c.
return AddInstant(cx, intermediate, duration.time(), result);
}
// Step 11.
MOZ_ASSERT(
CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
// Step 12.
const auto& datePart = date;
// Step 13.
auto dateDuration = duration.date();
// Step 14.
PlainDate addedDate;
if (maybeOptions) {
if (!CalendarDateAdd(cx, calendar, datePart, dateDuration, maybeOptions,
&addedDate)) {
return false;
}
} else {
if (!CalendarDateAdd(cx, calendar, datePart, dateDuration, &addedDate)) {
return false;
}
}
// Step 15.
Rooted<PlainDateTimeWithCalendar> intermediateDateTime(cx);
if (!CreateTemporalDateTime(cx, {addedDate, time}, calendar.receiver(),
&intermediateDateTime)) {
return false;
}
// Step 16.
Instant intermediateInstant;
if (!GetInstantFor(cx, timeZone, intermediateDateTime,
TemporalDisambiguation::Compatible,
&intermediateInstant)) {
return false;
}
// Step 17.
return AddInstant(cx, intermediateInstant, duration.time(), result);
}
/**
* AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months,
* weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds
* [ , precalculatedPlainDateTime [ , options ] ] )
*/
static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds,
Handle<TimeZoneRecord> timeZone,
Handle<CalendarRecord> calendar,
const Duration& duration,
Handle<JSObject*> maybeOptions, Instant* result) {
return ::AddZonedDateTime(cx, epochNanoseconds, timeZone, calendar, duration,
mozilla::Nothing(), maybeOptions, result);
}
/**
* AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months,
* weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds
* [ , precalculatedPlainDateTime [ , options ] ] )
*/
bool js::temporal::AddZonedDateTime(JSContext* cx,
const Instant& epochNanoseconds,
Handle<TimeZoneRecord> timeZone,
Handle<CalendarRecord> calendar,
const Duration& 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 ] ] )
*/
bool js::temporal::AddZonedDateTime(
JSContext* cx, const Instant& epochNanoseconds,
Handle<TimeZoneRecord> timeZone, Handle<CalendarRecord> calendar,
const Duration& 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);
}
void js::temporal::NanosecondsAndDays::trace(JSTracer* trc) {
if (days) {
TraceRoot(trc, &days, "NanosecondsAndDays::days");
}
}
/**
* NanosecondsToDays ( nanoseconds, zonedRelativeTo, timeZoneRec [ ,
* precalculatedPlainDateTime ] )
*/
static bool NanosecondsToDays(
JSContext* cx, const InstantSpan& nanoseconds,
Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone,
mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
MutableHandle<NanosecondsAndDays> result) {
MOZ_ASSERT(IsValidInstantSpan(nanoseconds));
// Step 1.
if (nanoseconds == InstantSpan{}) {
result.set(NanosecondsAndDays::from(
int64_t(0), InstantSpan{},
InstantSpan::fromNanoseconds(ToNanoseconds(TemporalUnit::Day))));
return true;
}
// Step 2.
int32_t sign = nanoseconds < InstantSpan{} ? -1 : 1;
// Step 3.
auto startNs = zonedRelativeTo.instant();
auto calendar = zonedRelativeTo.calendar();
// 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);
return false;
}
// Steps 4 and 8.
PlainDateTime startDateTime;
if (!precalculatedPlainDateTime) {
if (!GetPlainDateTimeFor(cx, timeZone, startNs, &startDateTime)) {
return false;
}
} else {
startDateTime = *precalculatedPlainDateTime;
}
// Steps 7 and 9.
PlainDateTime endDateTime;
if (!GetPlainDateTimeFor(cx, timeZone, endNs, &endDateTime)) {
return false;
}
// Steps 10-11. (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 13.
int32_t timeSign = CompareTemporalTime(startDateTime.time, endDateTime.time);
// Steps 14-15.
if (days > 0 && timeSign > 0) {
days -= 1;
} else if (days < 0 && timeSign < 0) {
days += 1;
}
// Step 16.
PlainDateTimeAndInstant relativeResult;
if (!::AddDaysToZonedDateTime(cx, startNs, startDateTime, timeZone, 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 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));
}
MOZ_ASSERT_IF(days > 0, relativeResult.instant <= endNs);
}
MOZ_ASSERT_IF(days == 0, relativeResult.instant == startNs);
// Step 18.
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 21.a.
PlainDateTimeAndInstant oneDayFarther;
if (!::AddDaysToZonedDateTime(
cx, relativeResult.instant, relativeResult.dateTime, timeZone,
calendar, sign, TemporalOverflow::Constrain, &oneDayFarther)) {
return false;
}
MOZ_ASSERT(IsValidISODateTime(oneDayFarther.dateTime));
MOZ_ASSERT(IsValidEpochInstant(oneDayFarther.instant));
// Step 21.b.
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'
// = 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.
//
// This repeats for all following iterations.
//
// |endNs| and |oneDayFarther.instant| are both valid epoch instant values,
// so the difference is a valid epoch instant difference value, too.
//
// 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 22.
if (days < 0 && sign > 0) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_ZONED_DATE_TIME_INCORRECT_SIGN,
"days");
return false;
}
// Step 23.
if (days > 0 && sign < 0) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_ZONED_DATE_TIME_INCORRECT_SIGN,
"days");
return false;
}
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.
//
// Steps 24-25.
if (sign < 0) {
if (ns > InstantSpan{}) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_ZONED_DATE_TIME_INCORRECT_SIGN,
"nanoseconds");
return false;
}
} else {
MOZ_ASSERT(ns >= InstantSpan{});
}
// Step 26.
MOZ_ASSERT(ns.abs() < dayLengthNs.abs());
// Step 27.
result.set(NanosecondsAndDays::from(days, ns, dayLengthNs.abs()));
return true;
}
/**
* NanosecondsToDays ( nanoseconds, 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);
}
/**
* NanosecondsToDays ( nanoseconds, zonedRelativeTo, timeZoneRec [ ,
* precalculatedPlainDateTime ] )
*/
bool js::temporal::NanosecondsToDays(
JSContext* cx, const InstantSpan& nanoseconds,
Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone,
const PlainDateTime& precalculatedPlainDateTime,
MutableHandle<NanosecondsAndDays> result) {
return ::NanosecondsToDays(cx, nanoseconds, zonedRelativeTo, timeZone,
mozilla::SomeRef(precalculatedPlainDateTime),
result);
}
/**
* DifferenceZonedDateTime ( ns1, ns2, timeZoneRec, calendarRec, largestUnit,
* options, precalculatedPlainDateTime )
*/
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) {
MOZ_ASSERT(IsValidEpochInstant(ns1));
MOZ_ASSERT(IsValidEpochInstant(ns2));
// Steps 1.
if (ns1 == ns2) {
*result = {};
return true;
}
// Steps 2-3.
PlainDateTime startDateTime;
if (!precalculatedPlainDateTime) {
// Steps 2.a-b.
if (!GetPlainDateTimeFor(cx, timeZone, ns1, &startDateTime)) {
return false;
}
} else {
startDateTime = *precalculatedPlainDateTime;
}
// Steps 4-5.
PlainDateTime endDateTime;
if (!GetPlainDateTimeFor(cx, timeZone, ns2, &endDateTime)) {
return false;
}
// 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;
}
}
// Step 7.
Instant intermediateNs;
if (!AddZonedDateTime(cx, ns1, timeZone, calendar,
{
dateDifference.years,
dateDifference.months,
dateDifference.weeks,
},
startDateTime, &intermediateNs)) {
return false;
}
MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
// Step 8.
auto timeRemainder = ns2 - intermediateNs;
MOZ_ASSERT(IsValidInstantSpan(timeRemainder));
// Step 9.
Rooted<ZonedDateTime> intermediate(
cx,
ZonedDateTime{intermediateNs, timeZone.receiver(), calendar.receiver()});
// Step 10.
Rooted<NanosecondsAndDays> nanosAndDays(cx);
if (!NanosecondsToDays(cx, timeRemainder, intermediate, timeZone,
&nanosAndDays)) {
return false;
}
// Step 11.
TimeDuration timeDifference;
if (!BalanceTimeDuration(cx, nanosAndDays.nanoseconds(), TemporalUnit::Hour,
&timeDifference)) {
return false;
}
// 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;
}
/**
* DifferenceZonedDateTime ( ns1, ns2, timeZoneRec, calendarRec, largestUnit,
* options, precalculatedPlainDateTime )
*/
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);
}
/**
* TimeZoneEquals ( one, two )
*/
static bool TimeZoneEqualsOrThrow(JSContext* cx, Handle<TimeZoneValue> one,
Handle<TimeZoneValue> two) {
// Step 1.
if (one.isObject() && two.isObject() && one.toObject() == two.toObject()) {
return true;
}
// Step 2.
Rooted<JSString*> timeZoneOne(cx, ToTemporalTimeZoneIdentifier(cx, one));
if (!timeZoneOne) {
return false;
}
// Step 3.
Rooted<JSString*> timeZoneTwo(cx, ToTemporalTimeZoneIdentifier(cx, two));
if (!timeZoneTwo) {
return false;
}
// Steps 4-9.
bool equals;
if (!TimeZoneEquals(cx, timeZoneOne, timeZoneTwo, &equals)) {
return false;
}
if (equals) {
return true;
}
// Throw an error when the time zone identifiers don't match. Used when
// unequal time zones throw a RangeError.
if (auto charsOne = QuoteString(cx, timeZoneOne)) {
if (auto charsTwo = QuoteString(cx, timeZoneTwo)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_TIMEZONE_INCOMPATIBLE,
charsOne.get(), charsTwo.get());
}
}
return false;
}
/**
* 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,
TemporalDifference operation,
const CallArgs& args) {
Rooted<ZonedDateTime> zonedDateTime(
cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
// Step 1. (Not applicable in our implementation.)
// Step 2.
Rooted<ZonedDateTime> other(cx);
if (!ToTemporalZonedDateTime(cx, args.get(0), &other)) {
return false;
}
// Step 3.
if (!CalendarEqualsOrThrow(cx, zonedDateTime.calendar(), other.calendar())) {
return false;
}
// Steps 4-5.
Rooted<PlainObject*> resolvedOptions(cx);
DifferenceSettings settings;
if (args.hasDefined(1)) {
Rooted<JSObject*> options(
cx, RequireObjectArg(cx, "options", ToName(operation), args[1]));
if (!options) {
return false;
}
// Step 4.
resolvedOptions = SnapshotOwnProperties(cx, options);
if (!resolvedOptions) {
return false;
}
// Step 5.
if (!GetDifferenceSettings(
cx, operation, resolvedOptions, TemporalUnitGroup::DateTime,
TemporalUnit::Nanosecond, TemporalUnit::Hour, &settings)) {
return false;
}
} else {
// Steps 4-5.
settings = {
TemporalUnit::Nanosecond,
TemporalUnit::Hour,
TemporalRoundingMode::Trunc,
Increment{1},
};
}
// Step 6.
if (settings.largestUnit > TemporalUnit::Day) {
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)) {
return false;
}
// Step 6.b.
if (operation == TemporalDifference::Since) {
difference = difference.negate();
}
auto* result = CreateTemporalDuration(cx, difference);
if (!result) {
return false;
}
args.rval().setObject(*result);
return true;
}
// FIXME: spec issue - move this step next to the calendar validation?
// Step 7.
if (!TimeZoneEqualsOrThrow(cx, zonedDateTime.timeZone(), other.timeZone())) {
return false;
}
// Step 8.
if (zonedDateTime.instant() == other.instant()) {
auto* obj = CreateTemporalDuration(cx, {});
if (!obj) {
return false;
}
args.rval().setObject(*obj);
return true;
}
// Step 9.
Rooted<TimeZoneRecord> timeZone(cx);
if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(),
{
TimeZoneMethod::GetOffsetNanosecondsFor,
TimeZoneMethod::GetPossibleInstantsFor,
},
&timeZone)) {
return false;
}
// Step 10.
Rooted<CalendarRecord> calendar(cx);
if (!CreateCalendarMethodsRecord(cx, zonedDateTime.calendar(),
{
CalendarMethod::DateAdd,
CalendarMethod::DateUntil,
},
&calendar)) {
return false;
}
// Steps 11-12.
PlainDateTime precalculatedPlainDateTime;
if (!GetPlainDateTimeFor(cx, timeZone, zonedDateTime.instant(),
&precalculatedPlainDateTime)) {
return false;
}
// Step 13.
Rooted<PlainDateObject*> plainRelativeTo(
cx, CreateTemporalDate(cx, precalculatedPlainDateTime.date,
calendar.receiver()));
if (!plainRelativeTo) {
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)) {
return false;
}
// Step 16.
bool roundingGranularityIsNoop =
settings.smallestUnit == TemporalUnit::Nanosecond &&
settings.roundingIncrement == Increment{1};
// Step 17.
if (roundingGranularityIsNoop) {
if (operation == TemporalDifference::Since) {
difference = difference.negate();
}
auto* obj = CreateTemporalDuration(cx, difference);
if (!obj) {
return false;
}
args.rval().setObject(*obj);
return true;
}
// Steps 18-19.
Duration roundResult;
if (!RoundDuration(cx, difference, settings.roundingIncrement,
settings.smallestUnit, settings.roundingMode,
plainRelativeTo, calendar, zonedDateTime, timeZone,
precalculatedPlainDateTime, &roundResult)) {
return false;
}
// Step 20.
Duration adjustResult;
if (!AdjustRoundedDurationDays(cx, roundResult, settings.roundingIncrement,
settings.smallestUnit, settings.roundingMode,
zonedDateTime, calendar, timeZone,
precalculatedPlainDateTime, &adjustResult)) {
return false;
}
// Step 21.
DateDuration balanceResult;
if (!temporal::BalanceDateDurationRelative(
cx, adjustResult.date(), settings.largestUnit, settings.smallestUnit,
plainRelativeTo, calendar, &balanceResult)) {
return false;
}
// 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,
};
if (operation == TemporalDifference::Since) {
result = result.negate();
}
auto* obj = CreateTemporalDuration(cx, result);
if (!obj) {
return false;
}
args.rval().setObject(*obj);
return true;
}
enum class ZonedDateTimeDuration { Add, Subtract };
/**
* AddDurationToOrSubtractDurationFromZonedDateTime ( operation, zonedDateTime,
* temporalDurationLike, options )
*/
static bool AddDurationToOrSubtractDurationFromZonedDateTime(
JSContext* cx, ZonedDateTimeDuration operation, const CallArgs& args) {
Rooted<ZonedDateTime> zonedDateTime(
cx, &args.thisv().toObject().as<ZonedDateTimeObject>());
// Step 1. (Not applicable in our implementation.)
// Step 2.
Duration duration;
if (!ToTemporalDurationRecord(cx, args.get(0), &duration)) {
return false;
}
// Step 3.
Rooted<JSObject*> options(cx);
if (args.hasDefined(1)) {
const char* name =
operation == ZonedDateTimeDuration::Add ? "add" : "subtract";
options = RequireObjectArg(cx, "options", name, args[1]);
} else {
options = NewPlainObjectWithProto(cx, nullptr);
}
if (!options) {
return false;
}
// Step 4.
Rooted<TimeZoneRecord> timeZone(cx);
if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(),
{
TimeZoneMethod::GetOffsetNanosecondsFor,
TimeZoneMethod::GetPossibleInstantsFor,
},
&timeZone)) {
return false;
}
// Step 5.
Rooted<CalendarRecord> calendar(cx);
if (!CreateCalendarMethodsRecord(cx, zonedDateTime.calendar(),
{
CalendarMethod::DateAdd,
},
&calendar)) {
return false;
}
// Step 6.
if (operation == ZonedDateTimeDuration::Subtract) {
duration = duration.negate();
}
Instant resultInstant;
if (!::AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar,
duration, options, &resultInstant)) {
return false;
}
MOZ_ASSERT(IsValidEpochInstant(resultInstant));
// Step 7.
auto* result = CreateTemporalZonedDateTime(
cx, resultInstant, timeZone.receiver(), calendar.receiver());
if (!result) {
return false;
}
args.rval().setObject(*result);
return true;
}
/**
* Temporal.ZonedDateTime ( epochNanoseconds, timeZoneLike [ , calendarLike ] )
*/
static bool ZonedDateTimeConstructor(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
if (!ThrowIfNotConstructing(cx, args, "Temporal.ZonedDateTime")) {
return false;
}
// Step 2.
Rooted<BigInt*> epochNanoseconds(cx, js::ToBigInt(cx, args.get(0)));
if (!epochNanoseconds) {
return false;
}
// Step 3.
if (!IsValidEpochNanoseconds(epochNanoseconds)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_INSTANT_INVALID);
return false;
}
// Step 4.
Rooted<TimeZoneValue> timeZone(cx);
if (!ToTemporalTimeZone(cx, args.get(1), &timeZone)) {
return false;
}
// Step 5.
Rooted<CalendarValue> calendar(cx);
if (!ToTemporalCalendarWithISODefault(cx, args.get(2), &calendar)) {
return false;
}
// Step 6.
auto* obj = CreateTemporalZonedDateTime(cx, args, epochNanoseconds, timeZone,
calendar);
if (!obj) {
return false;
}
args.rval().setObject(*obj);
return true;
}
/**
* Temporal.ZonedDateTime.from ( item [ , options ] )
*/
static bool ZonedDateTime_from(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
Rooted<JSObject*> options(cx);
if (args.hasDefined(1)) {
options = RequireObjectArg(cx, "options", "from", args[1]);
if (!options) {
return false;
}
}
// Step 2.
if (args.get(0).isObject()) {
JSObject* item = &args[0].toObject();
if (auto* zonedDateTime = item->maybeUnwrapIf<ZonedDateTimeObject>()) {
auto epochInstant = ToInstant(zonedDateTime);
Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());
if (!timeZone.wrap(cx)) {
return false;
}
if (!calendar.wrap(cx)) {
return false;
}
if (options) {
// Steps 2.a-b.
TemporalDisambiguation ignoredDisambiguation;
if (!ToTemporalDisambiguation(cx, options, &ignoredDisambiguation)) {
return false;
}
// Step 2.c.
TemporalOffset ignoredOffset;
if (!ToTemporalOffset(cx, options, &ignoredOffset)) {
return false;
}
// Step 2.d.
TemporalOverflow ignoredOverflow;
if (!ToTemporalOverflow(cx, options, &ignoredOverflow)) {
return false;
}
}
// Step 2.e.
auto* result =
CreateTemporalZonedDateTime(cx, epochInstant, timeZone, calendar);
if (!result) {
return false;
}
args.rval().setObject(*result);
return true;
}
}
// Step 3.
auto* result = ToTemporalZonedDateTime(cx, args.get(0), options);
if (!result) {
return false;
}
args.rval().setObject(*result);
return true;
}
/**
* Temporal.ZonedDateTime.compare ( one, two )
*/
static bool ZonedDateTime_compare(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
Rooted<ZonedDateTime> one(cx);
if (!ToTemporalZonedDateTime(cx, args.get(0), &one)) {
return false;
}
// Step 2.
Rooted<ZonedDateTime> two(cx);
if (!ToTemporalZonedDateTime(cx, args.get(1), &two)) {
return false;
}
// Step 3.
auto oneNs = one.instant();
auto twoNs = two.instant();
args.rval().setInt32(oneNs > twoNs ? 1 : oneNs < twoNs ? -1 : 0);
return true;
}
/**
* get Temporal.ZonedDateTime.prototype.calendarId
*/
static bool ZonedDateTime_calendarId(JSContext* cx, const CallArgs& args) {
auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>();
// Step 3.
Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());
auto* calendarId = ToTemporalCalendarIdentifier(cx, calendar);
if (!calendarId) {
return false;
}
args.rval().setString(calendarId);
return true;
}
/**
* get Temporal.ZonedDateTime.prototype.calendarId
*/
static bool ZonedDateTime_calendarId(JSContext* cx, unsigned argc, Value* vp) {
// Steps 1-2.
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_calendarId>(cx,
args);
}
/**
* get Temporal.ZonedDateTime.prototype.timeZoneId
*/
static bool ZonedDateTime_timeZoneId(JSContext* cx, const CallArgs& args) {
auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>();
// Step 3.
Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
auto* timeZoneId = ToTemporalTimeZoneIdentifier(cx, timeZone);
if (!timeZoneId) {
return false;
}
args.rval().setString(timeZoneId);
return true;
}
/**
* get Temporal.ZonedDateTime.prototype.timeZoneId
*/
static bool ZonedDateTime_timeZoneId(JSContext* cx, unsigned argc, Value* vp) {
// Steps 1-2.
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_timeZoneId>(cx,
args);
}
/**
* get Temporal.ZonedDateTime.prototype.year
*/
static bool ZonedDateTime_year(JSContext* cx, const CallArgs& args) {
Rooted<ZonedDateTime> zonedDateTime(
cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
// Steps 3-6.
PlainDateTime dateTime;
if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
zonedDateTime.instant(), &dateTime)) {
return false;
}
// Step 7.
return CalendarYear(cx, zonedDateTime.calendar(), dateTime, args.rval());
}
/**
* get Temporal.ZonedDateTime.prototype.year
*/
static bool ZonedDateTime_year(JSContext* cx, unsigned argc, Value* vp) {
// Steps 1-2.
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_year>(cx, args);
}
/**
* get Temporal.ZonedDateTime.prototype.month
*/
static bool ZonedDateTime_month(JSContext* cx, const CallArgs& args) {
Rooted<ZonedDateTime> zonedDateTime(
cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
// Steps 3-6.
PlainDateTime dateTime;
if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
zonedDateTime.instant(), &dateTime)) {
return false;
}
// Step 7.
return CalendarMonth(cx, zonedDateTime.calendar(), dateTime, args.rval());
}
/**
* get Temporal.ZonedDateTime.prototype.month
*/
static bool ZonedDateTime_month(JSContext* cx, unsigned argc, Value* vp) {
// Steps 1-2.
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_month>(cx, args);
}
/**
* get Temporal.ZonedDateTime.prototype.monthCode
*/
static bool ZonedDateTime_monthCode(JSContext* cx, const CallArgs& args) {
Rooted<ZonedDateTime> zonedDateTime(
cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
// Steps 3-6.
PlainDateTime dateTime;
if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
zonedDateTime.instant(), &dateTime)) {
return false;
}
// Step 7.
return CalendarMonthCode(cx, zonedDateTime.calendar(), dateTime, args.rval());
}
/**
* get Temporal.ZonedDateTime.prototype.monthCode
*/
static bool ZonedDateTime_monthCode(JSContext* cx, unsigned argc, Value* vp) {
// Steps 1-2.
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_monthCode>(cx,
args);
}
/**
* get Temporal.ZonedDateTime.prototype.day
*/
static bool ZonedDateTime_day(JSContext* cx, const CallArgs& args) {
Rooted<ZonedDateTime> zonedDateTime(
cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
// Step 4. (Reordered)
Rooted<CalendarRecord> calendar(cx);
if (!CreateCalendarMethodsRecord(cx, zonedDateTime.calendar(),
{
CalendarMethod::Day,
},
&calendar)) {
return false;
}
// Steps 3 and 5-6.
PlainDateTime dateTime;
if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
zonedDateTime.instant(), &dateTime)) {
return false;
}
// Step 7.
return CalendarDay(cx, calendar, dateTime, args.rval());
}
/**
* get Temporal.ZonedDateTime.prototype.day
*/
static bool ZonedDateTime_day(JSContext* cx, unsigned argc, Value* vp) {
// Steps 1-2.
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_day>(cx, args);
}
/**
* get Temporal.ZonedDateTime.prototype.hour
*/
static bool ZonedDateTime_hour(JSContext* cx, const CallArgs& args) {
Rooted<ZonedDateTime> zonedDateTime(
cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
// Steps 3-6.
PlainDateTime dateTime;
if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
zonedDateTime.instant(), &dateTime)) {
return false;
}
// Step 7.
args.rval().setInt32(dateTime.time.hour);
return true;
}
/**
* get Temporal.ZonedDateTime.prototype.hour
*/
static bool ZonedDateTime_hour(JSContext* cx, unsigned argc, Value* vp) {
// Steps 1-2.
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_hour>(cx, args);
}
/**
* get Temporal.ZonedDateTime.prototype.minute
*/
static bool ZonedDateTime_minute(JSContext* cx, const CallArgs& args) {
Rooted<ZonedDateTime> zonedDateTime(
cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
// Steps 3-6.
PlainDateTime dateTime;
if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
zonedDateTime.instant(), &dateTime)) {
return false;
}
// Step 7.
args.rval().setInt32(dateTime.time.minute);
return true;
}
/**
* get Temporal.ZonedDateTime.prototype.minute
*/
static bool ZonedDateTime_minute(JSContext* cx, unsigned argc, Value* vp) {
// Steps 1-2.
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_minute>(cx, args);
}
/**
* get Temporal.ZonedDateTime.prototype.second
*/
static bool ZonedDateTime_second(JSContext* cx, const CallArgs& args) {
Rooted<ZonedDateTime> zonedDateTime(
cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
// Steps 3-6.
PlainDateTime dateTime;
if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
zonedDateTime.instant(), &dateTime)) {
return false;
}
// Step 7.
args.rval().setInt32(dateTime.time.second);
return true;
}
/**
* get Temporal.ZonedDateTime.prototype.second
*/
static bool ZonedDateTime_second(JSContext* cx, unsigned argc, Value* vp) {
// Steps 1-2.
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_second>(cx, args);
}
/**
* get Temporal.ZonedDateTime.prototype.millisecond
*/
static bool ZonedDateTime_millisecond(JSContext* cx, const CallArgs& args) {
Rooted<ZonedDateTime> zonedDateTime(
cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
// Steps 3-6.
PlainDateTime dateTime;
if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
zonedDateTime.instant(), &dateTime)) {
return false;
}
// Step 7.
args.rval().setInt32(dateTime.time.millisecond);
return true;
}
/**
* get Temporal.ZonedDateTime.prototype.millisecond
*/
static bool ZonedDateTime_millisecond(JSContext* cx, unsigned argc, Value* vp) {
// Steps 1-2.
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_millisecond>(cx,
args);
}
/**
* get Temporal.ZonedDateTime.prototype.microsecond
*/
static bool ZonedDateTime_microsecond(JSContext* cx, const CallArgs& args) {
Rooted<ZonedDateTime> zonedDateTime(
cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
// Steps 3-6.
PlainDateTime dateTime;
if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
zonedDateTime.instant(), &dateTime)) {
return false;
}
// Step 7.
args.rval().setInt32(dateTime.time.microsecond);
return true;
}
/**
* get Temporal.ZonedDateTime.prototype.microsecond
*/
static bool ZonedDateTime_microsecond(JSContext* cx, unsigned argc, Value* vp) {
// Steps 1-2.
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_microsecond>(cx,
args);
}
/**
* get Temporal.ZonedDateTime.prototype.nanosecond
*/
static bool ZonedDateTime_nanosecond(JSContext* cx, const CallArgs& args) {
Rooted<ZonedDateTime> zonedDateTime(
cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
// Steps 3-6.
PlainDateTime dateTime;
if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
zonedDateTime.instant(), &dateTime)) {
return false;
}
// Step 7.
args.rval().setInt32(dateTime.time.nanosecond);
return true;
}
/**
* get Temporal.ZonedDateTime.prototype.nanosecond
*/
static bool ZonedDateTime_nanosecond(JSContext* cx, unsigned argc, Value* vp) {
// Steps 1-2.
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_nanosecond>(cx,
args);
}
/**
* get Temporal.ZonedDateTime.prototype.epochSeconds
*/
static bool ZonedDateTime_epochSeconds(JSContext* cx, const CallArgs& args) {
auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>();
// Step 3.