1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "builtin/temporal/TimeZone.h"
9 #include "mozilla/Array.h"
10 #include "mozilla/Assertions.h"
11 #include "mozilla/intl/TimeZone.h"
12 #include "mozilla/Likely.h"
13 #include "mozilla/Maybe.h"
14 #include "mozilla/Range.h"
15 #include "mozilla/Result.h"
16 #include "mozilla/Span.h"
17 #include "mozilla/UniquePtr.h"
21 #include <initializer_list>
28 #include "NamespaceImports.h"
30 #include "builtin/Array.h"
31 #include "builtin/intl/CommonFunctions.h"
32 #include "builtin/intl/FormatBuffer.h"
33 #include "builtin/intl/SharedIntlData.h"
34 #include "builtin/temporal/Calendar.h"
35 #include "builtin/temporal/Instant.h"
36 #include "builtin/temporal/PlainDate.h"
37 #include "builtin/temporal/PlainDateTime.h"
38 #include "builtin/temporal/PlainTime.h"
39 #include "builtin/temporal/Temporal.h"
40 #include "builtin/temporal/TemporalParser.h"
41 #include "builtin/temporal/TemporalTypes.h"
42 #include "builtin/temporal/TemporalUnit.h"
43 #include "builtin/temporal/Wrapped.h"
44 #include "builtin/temporal/ZonedDateTime.h"
45 #include "gc/AllocKind.h"
46 #include "gc/Barrier.h"
47 #include "gc/GCContext.h"
48 #include "gc/GCEnum.h"
49 #include "gc/Tracer.h"
50 #include "js/AllocPolicy.h"
51 #include "js/CallArgs.h"
52 #include "js/CallNonGenericMethod.h"
54 #include "js/ComparisonOperators.h"
56 #include "js/ErrorReport.h"
57 #include "js/ForOfIterator.h"
58 #include "js/friend/ErrorMessages.h"
59 #include "js/Printer.h"
60 #include "js/PropertyDescriptor.h"
61 #include "js/PropertySpec.h"
62 #include "js/RootingAPI.h"
63 #include "js/StableStringChars.h"
64 #include "threading/ProtectedData.h"
65 #include "vm/ArrayObject.h"
66 #include "vm/BytecodeUtil.h"
67 #include "vm/Compartment.h"
68 #include "vm/DateTime.h"
69 #include "vm/GlobalObject.h"
70 #include "vm/Interpreter.h"
71 #include "vm/JSAtomState.h"
72 #include "vm/JSContext.h"
73 #include "vm/JSObject.h"
74 #include "vm/PlainObject.h"
75 #include "vm/Runtime.h"
76 #include "vm/StringType.h"
78 #include "vm/JSObject-inl.h"
79 #include "vm/NativeObject-inl.h"
80 #include "vm/ObjectOperations-inl.h"
83 using namespace js::temporal
;
85 static inline bool IsTimeZone(Handle
<Value
> v
) {
86 return v
.isObject() && v
.toObject().is
<TimeZoneObject
>();
89 void js::temporal::TimeZoneValue::trace(JSTracer
* trc
) {
90 TraceNullableRoot(trc
, &object_
, "TimeZoneValue::object");
93 void js::temporal::TimeZoneRecord::trace(JSTracer
* trc
) {
95 TraceNullableRoot(trc
, &getOffsetNanosecondsFor_
,
96 "TimeZoneMethods::getOffsetNanosecondsFor");
97 TraceNullableRoot(trc
, &getPossibleInstantsFor_
,
98 "TimeZoneMethods::getPossibleInstantsFor");
101 static mozilla::UniquePtr
<mozilla::intl::TimeZone
> CreateIntlTimeZone(
102 JSContext
* cx
, JSString
* identifier
) {
103 JS::AutoStableStringChars
stableChars(cx
);
104 if (!stableChars
.initTwoByte(cx
, identifier
)) {
108 auto result
= mozilla::intl::TimeZone::TryCreate(
109 mozilla::Some(stableChars
.twoByteRange()));
110 if (result
.isErr()) {
111 intl::ReportInternalError(cx
, result
.unwrapErr());
114 return result
.unwrap();
117 static mozilla::intl::TimeZone
* GetOrCreateIntlTimeZone(
118 JSContext
* cx
, Handle
<TimeZoneObjectMaybeBuiltin
*> timeZone
) {
119 // Obtain a cached mozilla::intl::TimeZone object.
120 if (auto* tz
= timeZone
->getTimeZone()) {
124 auto* tz
= CreateIntlTimeZone(cx
, timeZone
->identifier()).release();
128 timeZone
->setTimeZone(tz
);
130 intl::AddICUCellMemory(timeZone
,
131 TimeZoneObjectMaybeBuiltin::EstimatedMemoryUse
);
136 * IsValidTimeZoneName ( timeZone )
137 * IsAvailableTimeZoneName ( timeZone )
139 bool js::temporal::IsValidTimeZoneName(
140 JSContext
* cx
, Handle
<JSString
*> timeZone
,
141 MutableHandle
<JSAtom
*> validatedTimeZone
) {
142 intl::SharedIntlData
& sharedIntlData
= cx
->runtime()->sharedIntlData
.ref();
144 if (!sharedIntlData
.validateTimeZoneName(cx
, timeZone
, validatedTimeZone
)) {
148 if (validatedTimeZone
) {
149 cx
->markAtom(validatedTimeZone
);
155 * 6.5.2 CanonicalizeTimeZoneName ( timeZone )
157 * Canonicalizes the given IANA time zone name.
159 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
161 JSString
* js::temporal::CanonicalizeTimeZoneName(
162 JSContext
* cx
, Handle
<JSLinearString
*> timeZone
) {
163 // Step 1. (Not applicable, the input is already a valid IANA time zone.)
165 MOZ_ASSERT(!StringEqualsLiteral(timeZone
, "Etc/Unknown"),
166 "Invalid time zone");
168 Rooted
<JSAtom
*> checkTimeZone(cx
);
169 if (!IsValidTimeZoneName(cx
, timeZone
, &checkTimeZone
)) {
172 MOZ_ASSERT(EqualStrings(timeZone
, checkTimeZone
),
173 "Time zone name not normalized");
177 Rooted
<JSLinearString
*> ianaTimeZone(cx
);
179 intl::SharedIntlData
& sharedIntlData
= cx
->runtime()->sharedIntlData
.ref();
181 // Some time zone names are canonicalized differently by ICU -- handle
183 Rooted
<JSAtom
*> canonicalTimeZone(cx
);
184 if (!sharedIntlData
.tryCanonicalizeTimeZoneConsistentWithIANA(
185 cx
, timeZone
, &canonicalTimeZone
)) {
189 if (canonicalTimeZone
) {
190 cx
->markAtom(canonicalTimeZone
);
191 ianaTimeZone
= canonicalTimeZone
;
195 JS::AutoStableStringChars
stableChars(cx
);
196 if (!stableChars
.initTwoByte(cx
, timeZone
)) {
200 intl::FormatBuffer
<char16_t
, intl::INITIAL_CHAR_BUFFER_SIZE
> buffer(cx
);
201 auto result
= mozilla::intl::TimeZone::GetCanonicalTimeZoneID(
202 stableChars
.twoByteRange(), buffer
);
203 if (result
.isErr()) {
204 intl::ReportInternalError(cx
, result
.unwrapErr());
208 ianaTimeZone
= buffer
.toString(cx
);
215 MOZ_ASSERT(!StringEqualsLiteral(ianaTimeZone
, "Etc/Unknown"),
216 "Invalid canonical time zone");
218 if (!IsValidTimeZoneName(cx
, ianaTimeZone
, &checkTimeZone
)) {
221 MOZ_ASSERT(EqualStrings(ianaTimeZone
, checkTimeZone
),
222 "Unsupported canonical time zone");
226 if (StringEqualsLiteral(ianaTimeZone
, "Etc/UTC") ||
227 StringEqualsLiteral(ianaTimeZone
, "Etc/GMT")) {
228 return cx
->names().UTC
;
231 // We don't need to check against "GMT", because ICU uses the tzdata rearguard
232 // format, where "GMT" is a link to "Etc/GMT".
233 MOZ_ASSERT(!StringEqualsLiteral(ianaTimeZone
, "GMT"));
240 * IsValidTimeZoneName ( timeZone )
241 * IsAvailableTimeZoneName ( timeZone )
242 * CanonicalizeTimeZoneName ( timeZone )
244 JSString
* js::temporal::ValidateAndCanonicalizeTimeZoneName(
245 JSContext
* cx
, Handle
<JSString
*> timeZone
) {
246 Rooted
<JSAtom
*> validatedTimeZone(cx
);
247 if (!IsValidTimeZoneName(cx
, timeZone
, &validatedTimeZone
)) {
251 if (!validatedTimeZone
) {
252 if (auto chars
= QuoteString(cx
, timeZone
)) {
253 JS_ReportErrorNumberUTF8(cx
, GetErrorMessage
, nullptr,
254 JSMSG_TEMPORAL_TIMEZONE_INVALID_IDENTIFIER
,
260 return CanonicalizeTimeZoneName(cx
, validatedTimeZone
);
263 class EpochInstantList final
{
264 // GetNamedTimeZoneEpochNanoseconds can return up-to two elements.
265 static constexpr size_t MaxLength
= 2;
267 mozilla::Array
<Instant
, MaxLength
> array_
= {};
271 EpochInstantList() = default;
273 size_t length() const { return length_
; }
275 void append(const Instant
& instant
) { array_
[length_
++] = instant
; }
277 auto& operator[](size_t i
) { return array_
[i
]; }
278 const auto& operator[](size_t i
) const { return array_
[i
]; }
280 auto begin() const { return array_
.begin(); }
281 auto end() const { return array_
.begin() + length_
; }
285 * GetNamedTimeZoneEpochNanoseconds ( timeZoneIdentifier, year, month, day,
286 * hour, minute, second, millisecond, microsecond, nanosecond )
288 static bool GetNamedTimeZoneEpochNanoseconds(
289 JSContext
* cx
, Handle
<TimeZoneObjectMaybeBuiltin
*> timeZone
,
290 const PlainDateTime
& dateTime
, EpochInstantList
& instants
) {
291 MOZ_ASSERT(timeZone
->offsetMinutes().isUndefined());
292 MOZ_ASSERT(IsValidISODateTime(dateTime
));
293 MOZ_ASSERT(ISODateTimeWithinLimits(dateTime
));
294 MOZ_ASSERT(instants
.length() == 0);
296 // FIXME: spec issue - assert ISODateTimeWithinLimits instead of
299 int64_t ms
= MakeDate(dateTime
);
301 auto* tz
= GetOrCreateIntlTimeZone(cx
, timeZone
);
306 auto getOffset
= [&](mozilla::intl::TimeZone::LocalOption skippedTime
,
307 mozilla::intl::TimeZone::LocalOption repeatedTime
,
309 auto result
= tz
->GetUTCOffsetMs(ms
, skippedTime
, repeatedTime
);
310 if (result
.isErr()) {
311 intl::ReportInternalError(cx
, result
.unwrapErr());
315 *offset
= result
.unwrap();
316 MOZ_ASSERT(std::abs(*offset
) < UnitsPerDay(TemporalUnit::Millisecond
));
321 constexpr auto formerTime
= mozilla::intl::TimeZone::LocalOption::Former
;
322 constexpr auto latterTime
= mozilla::intl::TimeZone::LocalOption::Latter
;
324 int32_t formerOffset
;
325 if (!getOffset(formerTime
, formerTime
, &formerOffset
)) {
329 int32_t latterOffset
;
330 if (!getOffset(latterTime
, latterTime
, &latterOffset
)) {
334 if (formerOffset
== latterOffset
) {
335 auto instant
= GetUTCEpochNanoseconds(
336 dateTime
, InstantSpan::fromMilliseconds(formerOffset
));
337 instants
.append(instant
);
341 int32_t disambiguationOffset
;
342 if (!getOffset(formerTime
, latterTime
, &disambiguationOffset
)) {
347 if (disambiguationOffset
== formerOffset
) {
352 for (auto offset
: {formerOffset
, latterOffset
}) {
354 GetUTCEpochNanoseconds(dateTime
, InstantSpan::fromMilliseconds(offset
));
355 instants
.append(instant
);
358 MOZ_ASSERT(instants
.length() == 2);
360 // Ensure the returned instants are sorted in numerical order.
361 if (instants
[0] > instants
[1]) {
362 std::swap(instants
[0], instants
[1]);
369 * GetNamedTimeZoneOffsetNanoseconds ( timeZoneIdentifier, epochNanoseconds )
371 static bool GetNamedTimeZoneOffsetNanoseconds(
372 JSContext
* cx
, Handle
<TimeZoneObjectMaybeBuiltin
*> timeZone
,
373 const Instant
& epochInstant
, int64_t* offset
) {
374 MOZ_ASSERT(timeZone
->offsetMinutes().isUndefined());
376 // Round down (floor) to the previous full milliseconds.
377 int64_t millis
= epochInstant
.floorToMilliseconds();
379 auto* tz
= GetOrCreateIntlTimeZone(cx
, timeZone
);
384 auto result
= tz
->GetOffsetMs(millis
);
385 if (result
.isErr()) {
386 intl::ReportInternalError(cx
, result
.unwrapErr());
390 // FIXME: spec issue - should constrain the range to not exceed 24-hours.
391 // https://github.com/tc39/ecma262/issues/3101
393 int64_t nanoPerMs
= 1'000'000;
394 *offset
= result
.unwrap() * nanoPerMs
;
399 * GetNamedTimeZoneNextTransition ( timeZoneIdentifier, epochNanoseconds )
401 static bool GetNamedTimeZoneNextTransition(JSContext
* cx
,
402 Handle
<TimeZoneObject
*> timeZone
,
403 const Instant
& epochInstant
,
404 mozilla::Maybe
<Instant
>* result
) {
405 MOZ_ASSERT(timeZone
->offsetMinutes().isUndefined());
407 // Round down (floor) to the previous full millisecond.
409 // IANA has experimental support for transitions at sub-second precision, but
410 // the default configuration doesn't enable it, therefore it's safe to round
411 // to milliseconds here. In addition to that, ICU also only supports
412 // transitions at millisecond precision.
413 int64_t millis
= epochInstant
.floorToMilliseconds();
415 auto* tz
= GetOrCreateIntlTimeZone(cx
, timeZone
);
420 auto next
= tz
->GetNextTransition(millis
);
422 intl::ReportInternalError(cx
, next
.unwrapErr());
426 auto transition
= next
.unwrap();
428 *result
= mozilla::Nothing();
432 auto transitionInstant
= Instant::fromMilliseconds(*transition
);
433 if (!IsValidEpochInstant(transitionInstant
)) {
434 *result
= mozilla::Nothing();
438 *result
= mozilla::Some(transitionInstant
);
443 * GetNamedTimeZonePreviousTransition ( timeZoneIdentifier, epochNanoseconds )
445 static bool GetNamedTimeZonePreviousTransition(
446 JSContext
* cx
, Handle
<TimeZoneObject
*> timeZone
,
447 const Instant
& epochInstant
, mozilla::Maybe
<Instant
>* result
) {
448 MOZ_ASSERT(timeZone
->offsetMinutes().isUndefined());
450 // Round up (ceil) to the next full millisecond.
452 // IANA has experimental support for transitions at sub-second precision, but
453 // the default configuration doesn't enable it, therefore it's safe to round
454 // to milliseconds here. In addition to that, ICU also only supports
455 // transitions at millisecond precision.
456 int64_t millis
= epochInstant
.ceilToMilliseconds();
458 auto* tz
= GetOrCreateIntlTimeZone(cx
, timeZone
);
463 auto previous
= tz
->GetPreviousTransition(millis
);
464 if (previous
.isErr()) {
465 intl::ReportInternalError(cx
, previous
.unwrapErr());
469 auto transition
= previous
.unwrap();
471 *result
= mozilla::Nothing();
475 auto transitionInstant
= Instant::fromMilliseconds(*transition
);
476 if (!IsValidEpochInstant(transitionInstant
)) {
477 *result
= mozilla::Nothing();
481 *result
= mozilla::Some(transitionInstant
);
486 * FormatOffsetTimeZoneIdentifier ( offsetMinutes [ , style ] )
488 static JSString
* FormatOffsetTimeZoneIdentifier(JSContext
* cx
,
489 int32_t offsetMinutes
) {
490 MOZ_ASSERT(std::abs(offsetMinutes
) < UnitsPerDay(TemporalUnit::Minute
));
493 char sign
= offsetMinutes
>= 0 ? '+' : '-';
496 int32_t absoluteMinutes
= std::abs(offsetMinutes
);
499 int32_t hour
= absoluteMinutes
/ 60;
502 int32_t minute
= absoluteMinutes
% 60;
504 // Step 5. (Inlined FormatTimeString).
506 // Format: "sign hour{2} : minute{2}"
508 sign
, char('0' + (hour
/ 10)), char('0' + (hour
% 10)),
509 ':', char('0' + (minute
/ 10)), char('0' + (minute
% 10)),
513 return NewStringCopyN
<CanGC
>(cx
, result
, std::size(result
));
517 * CreateTemporalTimeZone ( identifier [ , newTarget ] )
519 static TimeZoneObject
* CreateTemporalTimeZone(JSContext
* cx
,
520 const CallArgs
& args
,
521 Handle
<JSString
*> identifier
,
522 Handle
<Value
> offsetMinutes
) {
523 MOZ_ASSERT(offsetMinutes
.isUndefined() || offsetMinutes
.isInt32());
524 MOZ_ASSERT_IF(offsetMinutes
.isInt32(), std::abs(offsetMinutes
.toInt32()) <
525 UnitsPerDay(TemporalUnit::Minute
));
528 Rooted
<JSObject
*> proto(cx
);
529 if (!GetPrototypeFromBuiltinConstructor(cx
, args
, JSProto_TimeZone
, &proto
)) {
533 auto* timeZone
= NewObjectWithClassProto
<TimeZoneObject
>(cx
, proto
);
538 // Step 4.a. (Not applicable in our implementation.)
541 timeZone
->setFixedSlot(TimeZoneObject::IDENTIFIER_SLOT
,
542 StringValue(identifier
));
545 timeZone
->setFixedSlot(TimeZoneObject::OFFSET_MINUTES_SLOT
, offsetMinutes
);
551 static BuiltinTimeZoneObject
* CreateBuiltinTimeZone(
552 JSContext
* cx
, Handle
<JSString
*> identifier
) {
553 // TODO: Implement a built-in time zone object cache.
555 auto* object
= NewObjectWithGivenProto
<BuiltinTimeZoneObject
>(cx
, nullptr);
560 object
->setFixedSlot(BuiltinTimeZoneObject::IDENTIFIER_SLOT
,
561 StringValue(identifier
));
563 object
->setFixedSlot(BuiltinTimeZoneObject::OFFSET_MINUTES_SLOT
,
569 static BuiltinTimeZoneObject
* CreateBuiltinTimeZone(JSContext
* cx
,
570 int32_t offsetMinutes
) {
571 // TODO: It's unclear if offset time zones should also be cached. Real world
572 // experience will tell if a cache should be added.
574 MOZ_ASSERT(std::abs(offsetMinutes
) < UnitsPerDay(TemporalUnit::Minute
));
576 Rooted
<JSString
*> identifier(
577 cx
, FormatOffsetTimeZoneIdentifier(cx
, offsetMinutes
));
582 auto* object
= NewObjectWithGivenProto
<BuiltinTimeZoneObject
>(cx
, nullptr);
587 object
->setFixedSlot(BuiltinTimeZoneObject::IDENTIFIER_SLOT
,
588 StringValue(identifier
));
590 object
->setFixedSlot(BuiltinTimeZoneObject::OFFSET_MINUTES_SLOT
,
591 Int32Value(offsetMinutes
));
597 * CreateTemporalTimeZone ( identifier [ , newTarget ] )
599 static TimeZoneObject
* CreateTemporalTimeZone(
600 JSContext
* cx
, Handle
<BuiltinTimeZoneObject
*> timeZone
) {
602 auto* object
= NewBuiltinClassInstance
<TimeZoneObject
>(cx
);
607 // Step 4.a. (Not applicable in our implementation.)
610 object
->setFixedSlot(
611 TimeZoneObject::IDENTIFIER_SLOT
,
612 timeZone
->getFixedSlot(BuiltinTimeZoneObject::IDENTIFIER_SLOT
));
615 object
->setFixedSlot(
616 TimeZoneObject::OFFSET_MINUTES_SLOT
,
617 timeZone
->getFixedSlot(BuiltinTimeZoneObject::OFFSET_MINUTES_SLOT
));
624 * CreateTemporalTimeZone ( identifier [ , newTarget ] )
626 BuiltinTimeZoneObject
* js::temporal::CreateTemporalTimeZone(
627 JSContext
* cx
, Handle
<JSString
*> identifier
) {
628 return ::CreateBuiltinTimeZone(cx
, identifier
);
632 * ToTemporalTimeZoneSlotValue ( temporalTimeZoneLike )
634 bool js::temporal::ToTemporalTimeZone(JSContext
* cx
,
635 Handle
<ParsedTimeZone
> string
,
636 MutableHandle
<TimeZoneValue
> result
) {
637 // Steps 1-3. (Not applicable)
641 // Steps 4.a-c. (Not applicable in our implementation.)
644 Rooted
<JSString
*> timeZoneName(
645 cx
, ValidateAndCanonicalizeTimeZoneName(cx
, string
.name()));
651 auto* obj
= ::CreateBuiltinTimeZone(cx
, timeZoneName
);
656 result
.set(TimeZoneValue(obj
));
660 // Steps 4.b-c and 8.
661 auto* obj
= ::CreateBuiltinTimeZone(cx
, string
.offset());
666 result
.set(TimeZoneValue(obj
));
671 * ObjectImplementsTemporalTimeZoneProtocol ( object )
673 static bool ObjectImplementsTemporalTimeZoneProtocol(JSContext
* cx
,
674 Handle
<JSObject
*> object
,
676 // Step 1. (Not applicable in our implementation.)
677 MOZ_ASSERT(!object
->canUnwrapAs
<TimeZoneObject
>(),
678 "TimeZone objects handled in the caller");
682 &JSAtomState::getOffsetNanosecondsFor
,
683 &JSAtomState::getPossibleInstantsFor
,
688 if (!HasProperty(cx
, object
, cx
->names().*key
, &has
)) {
703 * ToTemporalTimeZoneSlotValue ( temporalTimeZoneLike )
705 bool js::temporal::ToTemporalTimeZone(JSContext
* cx
,
706 Handle
<Value
> temporalTimeZoneLike
,
707 MutableHandle
<TimeZoneValue
> result
) {
709 Rooted
<Value
> timeZoneLike(cx
, temporalTimeZoneLike
);
710 if (timeZoneLike
.isObject()) {
711 Rooted
<JSObject
*> obj(cx
, &timeZoneLike
.toObject());
713 // Step 1.b. (Partial)
714 if (obj
->canUnwrapAs
<TimeZoneObject
>()) {
715 result
.set(TimeZoneValue(obj
));
720 if (auto* zonedDateTime
= obj
->maybeUnwrapIf
<ZonedDateTimeObject
>()) {
721 result
.set(zonedDateTime
->timeZone());
722 return result
.wrap(cx
);
726 bool implementsTimeZoneProtocol
;
727 if (!ObjectImplementsTemporalTimeZoneProtocol(
728 cx
, obj
, &implementsTimeZoneProtocol
)) {
731 if (!implementsTimeZoneProtocol
) {
732 JS_ReportErrorNumberUTF8(cx
, GetErrorMessage
, nullptr,
733 JSMSG_TEMPORAL_INVALID_OBJECT
,
734 "Temporal.TimeZone", obj
->getClass()->name
);
739 result
.set(TimeZoneValue(obj
));
744 if (!timeZoneLike
.isString()) {
745 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_IGNORE_STACK
,
746 timeZoneLike
, nullptr, "not a string");
749 Rooted
<JSString
*> identifier(cx
, timeZoneLike
.toString());
752 Rooted
<ParsedTimeZone
> timeZoneName(cx
);
753 if (!ParseTemporalTimeZoneString(cx
, identifier
, &timeZoneName
)) {
758 return ToTemporalTimeZone(cx
, timeZoneName
, result
);
762 * ToTemporalTimeZoneObject ( timeZoneSlotValue )
764 JSObject
* js::temporal::ToTemporalTimeZoneObject(
765 JSContext
* cx
, Handle
<TimeZoneValue
> timeZone
) {
767 if (timeZone
.isObject()) {
768 return timeZone
.toObject();
772 return CreateTemporalTimeZone(cx
, timeZone
.toString());
776 * ToTemporalTimeZoneIdentifier ( timeZoneSlotValue )
778 JSString
* js::temporal::ToTemporalTimeZoneIdentifier(
779 JSContext
* cx
, Handle
<TimeZoneValue
> timeZone
) {
781 if (timeZone
.isString()) {
782 // Step 1.a. (Not applicable in our implementation.)
785 return timeZone
.toString()->identifier();
789 Rooted
<JSObject
*> timeZoneObj(cx
, timeZone
.toObject());
790 Rooted
<Value
> identifier(cx
);
791 if (!GetProperty(cx
, timeZoneObj
, timeZoneObj
, cx
->names().id
, &identifier
)) {
796 if (!identifier
.isString()) {
797 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_IGNORE_STACK
, identifier
,
798 nullptr, "not a string");
803 return identifier
.toString();
806 static bool TimeZone_getOffsetNanosecondsFor(JSContext
* cx
, unsigned argc
,
809 static bool TimeZone_getPossibleInstantsFor(JSContext
* cx
, unsigned argc
,
813 * TimeZoneMethodsRecordLookup ( timeZoneRec, methodName )
815 static bool TimeZoneMethodsRecordLookup(JSContext
* cx
,
816 MutableHandle
<TimeZoneRecord
> timeZone
,
817 TimeZoneMethod methodName
) {
818 // Step 1. (Not applicable in our implementation.)
821 auto object
= timeZone
.receiver().toObject();
823 auto lookup
= [&](Handle
<PropertyName
*> name
, JSNative native
,
824 MutableHandle
<JSObject
*> result
) {
825 auto* method
= GetMethod(cx
, object
, name
);
830 // As an optimization we only store the method if the receiver is either
831 // a custom time zone object or if the method isn't the default, built-in
833 if (!object
->is
<TimeZoneObject
>() || !IsNativeFunction(method
, native
)) {
839 switch (methodName
) {
841 case TimeZoneMethod::GetOffsetNanosecondsFor
:
842 return lookup(cx
->names().getOffsetNanosecondsFor
,
843 TimeZone_getOffsetNanosecondsFor
,
844 timeZone
.getOffsetNanosecondsFor());
847 case TimeZoneMethod::GetPossibleInstantsFor
:
848 return lookup(cx
->names().getPossibleInstantsFor
,
849 TimeZone_getPossibleInstantsFor
,
850 timeZone
.getPossibleInstantsFor());
853 MOZ_CRASH("invalid time zone method");
857 * CreateTimeZoneMethodsRecord ( timeZone, methods )
859 bool js::temporal::CreateTimeZoneMethodsRecord(
860 JSContext
* cx
, Handle
<TimeZoneValue
> timeZone
,
861 mozilla::EnumSet
<TimeZoneMethod
> methods
,
862 MutableHandle
<TimeZoneRecord
> result
) {
863 MOZ_ASSERT(!methods
.isEmpty());
866 result
.set(TimeZoneRecord
{timeZone
});
869 // Remember the set of looked-up methods for assertions.
870 result
.get().lookedUp() += methods
;
873 // Built-in time zones don't perform observable lookups.
874 if (timeZone
.isString()) {
879 for (auto method
: methods
) {
880 if (!TimeZoneMethodsRecordLookup(cx
, result
, method
)) {
889 bool js::temporal::WrapTimeZoneValueObject(JSContext
* cx
,
890 MutableHandle
<JSObject
*> timeZone
) {
891 // First handle the common case when |timeZone| is TimeZoneObjectMaybeBuiltin
892 // from the current compartment.
893 if (MOZ_LIKELY(timeZone
->is
<TimeZoneObjectMaybeBuiltin
>() &&
894 timeZone
->compartment() == cx
->compartment())) {
898 // If it's not a built-in time zone, simply wrap the object into the current
900 auto* unwrappedTimeZone
= timeZone
->maybeUnwrapIf
<BuiltinTimeZoneObject
>();
901 if (!unwrappedTimeZone
) {
902 return cx
->compartment()->wrap(cx
, timeZone
);
905 // If this is a built-in time zone from a different compartment, create a
906 // fresh copy using the current compartment.
908 // We create a fresh copy, so we don't have to support the cross-compartment
909 // case, which makes detection of "string" time zones easier.
911 const auto& offsetMinutes
= unwrappedTimeZone
->offsetMinutes();
912 if (offsetMinutes
.isInt32()) {
913 auto* obj
= CreateBuiltinTimeZone(cx
, offsetMinutes
.toInt32());
921 MOZ_ASSERT(offsetMinutes
.isUndefined());
923 Rooted
<JSString
*> identifier(cx
, unwrappedTimeZone
->identifier());
924 if (!cx
->compartment()->wrap(cx
, &identifier
)) {
928 auto* obj
= ::CreateBuiltinTimeZone(cx
, identifier
);
938 * Temporal.TimeZone.prototype.getOffsetNanosecondsFor ( instant )
940 static bool BuiltinGetOffsetNanosecondsFor(
941 JSContext
* cx
, Handle
<TimeZoneObjectMaybeBuiltin
*> timeZone
,
942 const Instant
& instant
, int64_t* offsetNanoseconds
) {
943 // Steps 1-3. (Not applicable.)
946 if (timeZone
->offsetMinutes().isInt32()) {
947 int32_t offset
= timeZone
->offsetMinutes().toInt32();
948 MOZ_ASSERT(std::abs(offset
) < UnitsPerDay(TemporalUnit::Minute
));
950 *offsetNanoseconds
= int64_t(offset
) * ToNanoseconds(TemporalUnit::Minute
);
953 MOZ_ASSERT(timeZone
->offsetMinutes().isUndefined());
957 if (!GetNamedTimeZoneOffsetNanoseconds(cx
, timeZone
, instant
, &offset
)) {
960 MOZ_ASSERT(std::abs(offset
) < ToNanoseconds(TemporalUnit::Day
));
962 *offsetNanoseconds
= offset
;
967 * GetOffsetNanosecondsFor ( timeZoneRec, instant )
969 static bool GetOffsetNanosecondsForSlow(JSContext
* cx
,
970 Handle
<TimeZoneRecord
> timeZone
,
971 Handle
<Wrapped
<InstantObject
*>> instant
,
972 int64_t* offsetNanoseconds
) {
973 // Step 1. (Inlined call to TimeZoneMethodsRecordCall)
974 Rooted
<Value
> fval(cx
, ObjectValue(*timeZone
.getOffsetNanosecondsFor()));
975 auto thisv
= timeZone
.receiver().toObject();
976 Rooted
<Value
> instantVal(cx
, ObjectValue(*instant
));
977 Rooted
<Value
> rval(cx
);
978 if (!Call(cx
, fval
, thisv
, instantVal
, &rval
)) {
982 // Step 2. (Not applicable)
985 if (!rval
.isNumber()) {
986 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_IGNORE_STACK
, rval
,
987 nullptr, "not a number");
992 double num
= rval
.toNumber();
993 if (!IsInteger(num
) || std::abs(num
) >= ToNanoseconds(TemporalUnit::Day
)) {
995 const char* numStr
= NumberToCString(&cbuf
, num
);
997 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
998 JSMSG_TEMPORAL_TIMEZONE_NANOS_RANGE
, numStr
);
1003 *offsetNanoseconds
= int64_t(num
);
1008 * GetOffsetNanosecondsFor ( timeZoneRec, instant )
1010 bool js::temporal::GetOffsetNanosecondsFor(
1011 JSContext
* cx
, Handle
<TimeZoneRecord
> timeZone
,
1012 Handle
<Wrapped
<InstantObject
*>> instant
, int64_t* offsetNanoseconds
) {
1013 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1014 timeZone
, TimeZoneMethod::GetOffsetNanosecondsFor
));
1016 // Step 2. (Reordered)
1017 auto getOffsetNanosecondsFor
= timeZone
.getOffsetNanosecondsFor();
1018 if (!getOffsetNanosecondsFor
) {
1019 auto* unwrapped
= instant
.unwrap(cx
);
1023 auto instant
= ToInstant(unwrapped
);
1024 auto builtin
= timeZone
.receiver().toTimeZoneObjectMaybeBuiltin();
1026 return BuiltinGetOffsetNanosecondsFor(cx
, builtin
, instant
,
1031 return ::GetOffsetNanosecondsForSlow(cx
, timeZone
, instant
,
1036 * GetOffsetNanosecondsFor ( timeZoneRec, instant )
1038 bool js::temporal::GetOffsetNanosecondsFor(
1039 JSContext
* cx
, Handle
<TimeZoneValue
> timeZone
,
1040 Handle
<Wrapped
<InstantObject
*>> instant
, int64_t* offsetNanoseconds
) {
1041 Rooted
<TimeZoneRecord
> timeZoneRec(cx
);
1042 if (!CreateTimeZoneMethodsRecord(cx
, timeZone
,
1044 TimeZoneMethod::GetOffsetNanosecondsFor
,
1050 return GetOffsetNanosecondsFor(cx
, timeZoneRec
, instant
, offsetNanoseconds
);
1054 * GetOffsetNanosecondsFor ( timeZoneRec, instant )
1056 bool js::temporal::GetOffsetNanosecondsFor(JSContext
* cx
,
1057 Handle
<TimeZoneRecord
> timeZone
,
1058 const Instant
& instant
,
1059 int64_t* offsetNanoseconds
) {
1060 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1061 timeZone
, TimeZoneMethod::GetOffsetNanosecondsFor
));
1063 // Step 2. (Reordered)
1064 auto getOffsetNanosecondsFor
= timeZone
.getOffsetNanosecondsFor();
1065 if (!getOffsetNanosecondsFor
) {
1066 auto builtin
= timeZone
.receiver().toTimeZoneObjectMaybeBuiltin();
1067 return BuiltinGetOffsetNanosecondsFor(cx
, builtin
, instant
,
1072 Rooted
<InstantObject
*> obj(cx
, CreateTemporalInstant(cx
, instant
));
1076 return ::GetOffsetNanosecondsForSlow(cx
, timeZone
, obj
, offsetNanoseconds
);
1080 * GetOffsetNanosecondsFor ( timeZoneRec, instant )
1082 bool js::temporal::GetOffsetNanosecondsFor(JSContext
* cx
,
1083 Handle
<TimeZoneValue
> timeZone
,
1084 const Instant
& instant
,
1085 int64_t* offsetNanoseconds
) {
1086 Rooted
<TimeZoneRecord
> timeZoneRec(cx
);
1087 if (!CreateTimeZoneMethodsRecord(cx
, timeZone
,
1089 TimeZoneMethod::GetOffsetNanosecondsFor
,
1095 return GetOffsetNanosecondsFor(cx
, timeZoneRec
, instant
, offsetNanoseconds
);
1099 * FormatUTCOffsetNanoseconds ( offsetNanoseconds )
1101 JSString
* js::temporal::FormatUTCOffsetNanoseconds(JSContext
* cx
,
1102 int64_t offsetNanoseconds
) {
1103 MOZ_ASSERT(std::abs(offsetNanoseconds
) < ToNanoseconds(TemporalUnit::Day
));
1106 char sign
= offsetNanoseconds
>= 0 ? '+' : '-';
1109 int64_t absoluteNanoseconds
= std::abs(offsetNanoseconds
);
1111 // Step 6. (Reordered)
1112 int32_t subSecondNanoseconds
= int32_t(absoluteNanoseconds
% 1'000'000'000);
1114 // Step 5. (Reordered)
1115 int32_t quotient
= int32_t(absoluteNanoseconds
/ 1'000'000'000);
1116 int32_t second
= quotient
% 60;
1118 // Step 4. (Reordered)
1120 int32_t minute
= quotient
% 60;
1123 int32_t hour
= quotient
/ 60;
1124 MOZ_ASSERT(hour
< 24, "time zone offset mustn't exceed 24-hours");
1126 // Format: "sign hour{2} : minute{2} : second{2} . fractional{9}"
1127 constexpr size_t maxLength
= 1 + 2 + 1 + 2 + 1 + 2 + 1 + 9;
1128 char result
[maxLength
];
1132 // Steps 7-8. (Inlined FormatTimeString).
1134 result
[n
++] = char('0' + (hour
/ 10));
1135 result
[n
++] = char('0' + (hour
% 10));
1137 result
[n
++] = char('0' + (minute
/ 10));
1138 result
[n
++] = char('0' + (minute
% 10));
1140 if (second
!= 0 || subSecondNanoseconds
!= 0) {
1142 result
[n
++] = char('0' + (second
/ 10));
1143 result
[n
++] = char('0' + (second
% 10));
1145 if (uint32_t fractional
= subSecondNanoseconds
) {
1148 uint32_t k
= 100'000'000;
1150 result
[n
++] = char('0' + (fractional
/ k
));
1153 } while (fractional
);
1157 MOZ_ASSERT(n
<= maxLength
);
1160 return NewStringCopyN
<CanGC
>(cx
, result
, n
);
1164 * GetOffsetStringFor ( timeZoneRec, instant )
1166 JSString
* js::temporal::GetOffsetStringFor(JSContext
* cx
,
1167 Handle
<TimeZoneValue
> timeZone
,
1168 const Instant
& instant
) {
1170 int64_t offsetNanoseconds
;
1171 if (!GetOffsetNanosecondsFor(cx
, timeZone
, instant
, &offsetNanoseconds
)) {
1174 MOZ_ASSERT(std::abs(offsetNanoseconds
) < ToNanoseconds(TemporalUnit::Day
));
1177 return FormatUTCOffsetNanoseconds(cx
, offsetNanoseconds
);
1181 * GetOffsetStringFor ( timeZoneRec, instant )
1183 JSString
* js::temporal::GetOffsetStringFor(
1184 JSContext
* cx
, Handle
<TimeZoneRecord
> timeZone
,
1185 Handle
<Wrapped
<InstantObject
*>> instant
) {
1187 int64_t offsetNanoseconds
;
1188 if (!GetOffsetNanosecondsFor(cx
, timeZone
, instant
, &offsetNanoseconds
)) {
1191 MOZ_ASSERT(std::abs(offsetNanoseconds
) < ToNanoseconds(TemporalUnit::Day
));
1194 return FormatUTCOffsetNanoseconds(cx
, offsetNanoseconds
);
1198 * TimeZoneEquals ( one, two )
1200 bool js::temporal::TimeZoneEquals(JSContext
* cx
, Handle
<JSString
*> one
,
1201 Handle
<JSString
*> two
, bool* equals
) {
1202 // Steps 1-3. (Not applicable)
1205 if (!EqualStrings(cx
, one
, two
, equals
)) {
1213 Rooted
<ParsedTimeZone
> timeZoneOne(cx
);
1214 if (!ParseTimeZoneIdentifier(cx
, one
, &timeZoneOne
)) {
1219 Rooted
<ParsedTimeZone
> timeZoneTwo(cx
);
1220 if (!ParseTimeZoneIdentifier(cx
, two
, &timeZoneTwo
)) {
1225 if (timeZoneOne
.name() && timeZoneTwo
.name()) {
1227 Rooted
<JSAtom
*> validTimeZoneOne(cx
);
1228 if (!IsValidTimeZoneName(cx
, timeZoneOne
.name(), &validTimeZoneOne
)) {
1231 if (!validTimeZoneOne
) {
1237 Rooted
<JSAtom
*> validTimeZoneTwo(cx
);
1238 if (!IsValidTimeZoneName(cx
, timeZoneTwo
.name(), &validTimeZoneTwo
)) {
1241 if (!validTimeZoneTwo
) {
1247 Rooted
<JSString
*> canonicalOne(
1248 cx
, CanonicalizeTimeZoneName(cx
, validTimeZoneOne
));
1249 if (!canonicalOne
) {
1253 JSString
* canonicalTwo
= CanonicalizeTimeZoneName(cx
, validTimeZoneTwo
);
1254 if (!canonicalTwo
) {
1258 return EqualStrings(cx
, canonicalOne
, canonicalTwo
, equals
);
1262 if (!timeZoneOne
.name() && !timeZoneTwo
.name()) {
1263 *equals
= (timeZoneOne
.offset() == timeZoneTwo
.offset());
1273 * TimeZoneEquals ( one, two )
1275 bool js::temporal::TimeZoneEquals(JSContext
* cx
, Handle
<TimeZoneValue
> one
,
1276 Handle
<TimeZoneValue
> two
, bool* equals
) {
1278 if (one
.isObject() && two
.isObject() && one
.toObject() == two
.toObject()) {
1284 Rooted
<JSString
*> timeZoneOne(cx
, ToTemporalTimeZoneIdentifier(cx
, one
));
1290 Rooted
<JSString
*> timeZoneTwo(cx
, ToTemporalTimeZoneIdentifier(cx
, two
));
1296 return TimeZoneEquals(cx
, timeZoneOne
, timeZoneTwo
, equals
);
1299 // ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
1300 // 5.2.5 Mathematical Operations
1301 static inline double PositiveModulo(double dividend
, double divisor
) {
1302 MOZ_ASSERT(divisor
> 0);
1303 MOZ_ASSERT(std::isfinite(divisor
));
1305 double result
= std::fmod(dividend
, divisor
);
1309 return result
+ (+0.0);
1312 /* ES5 15.9.1.10. */
1313 static double HourFromTime(double t
) {
1314 return PositiveModulo(std::floor(t
/ msPerHour
), HoursPerDay
);
1317 static double MinFromTime(double t
) {
1318 return PositiveModulo(std::floor(t
/ msPerMinute
), MinutesPerHour
);
1321 static double SecFromTime(double t
) {
1322 return PositiveModulo(std::floor(t
/ msPerSecond
), SecondsPerMinute
);
1325 static double msFromTime(double t
) { return PositiveModulo(t
, msPerSecond
); }
1328 * GetISOPartsFromEpoch ( epochNanoseconds )
1330 static PlainDateTime
GetISOPartsFromEpoch(const Instant
& instant
) {
1331 // TODO: YearFromTime/MonthFromTime/DayFromTime recompute the same values
1332 // multiple times. Consider adding a new function avoids this.
1335 MOZ_ASSERT(IsValidEpochInstant(instant
));
1338 int32_t remainderNs
= instant
.nanoseconds
% 1'000'000;
1341 double epochMilliseconds
= double(instant
.floorToMilliseconds());
1344 int32_t year
= int32_t(JS::YearFromTime(epochMilliseconds
));
1347 int32_t month
= int32_t(JS::MonthFromTime(epochMilliseconds
)) + 1;
1350 int32_t day
= int32_t(JS::DayFromTime(epochMilliseconds
));
1353 int32_t hour
= int32_t(HourFromTime(epochMilliseconds
));
1356 int32_t minute
= int32_t(MinFromTime(epochMilliseconds
));
1359 int32_t second
= int32_t(SecFromTime(epochMilliseconds
));
1362 int32_t millisecond
= int32_t(msFromTime(epochMilliseconds
));
1365 int32_t microsecond
= remainderNs
/ 1000;
1368 int32_t nanosecond
= remainderNs
% 1000;
1371 PlainDateTime result
= {
1373 {hour
, minute
, second
, millisecond
, microsecond
, nanosecond
}};
1375 // Always valid when the epoch nanoseconds are within the representable limit.
1376 MOZ_ASSERT(IsValidISODateTime(result
));
1377 MOZ_ASSERT(ISODateTimeWithinLimits(result
));
1383 * BalanceISODateTime ( year, month, day, hour, minute, second, millisecond,
1384 * microsecond, nanosecond )
1386 static PlainDateTime
BalanceISODateTime(const PlainDateTime
& dateTime
,
1387 int64_t nanoseconds
) {
1388 MOZ_ASSERT(IsValidISODateTime(dateTime
));
1389 MOZ_ASSERT(ISODateTimeWithinLimits(dateTime
));
1390 MOZ_ASSERT(std::abs(nanoseconds
) < ToNanoseconds(TemporalUnit::Day
));
1392 const auto& [date
, time
] = dateTime
;
1395 auto balancedTime
= BalanceTime(time
, nanoseconds
);
1396 MOZ_ASSERT(-1 <= balancedTime
.days
&& balancedTime
.days
<= 1);
1400 BalanceISODate(date
.year
, date
.month
, date
.day
+ balancedTime
.days
);
1403 return {balancedDate
, balancedTime
.time
};
1407 * GetPlainDateTimeFor ( timeZoneRec, instant, calendar [ ,
1408 * precalculatedOffsetNanoseconds ] )
1410 static PlainDateTimeObject
* GetPlainDateTimeFor(
1411 JSContext
* cx
, Handle
<TimeZoneValue
> timeZone
,
1412 Handle
<Wrapped
<InstantObject
*>> instant
, Handle
<CalendarValue
> calendar
) {
1413 // Step 1. (Not applicable in our implementation.)
1416 int64_t offsetNanoseconds
;
1417 if (!GetOffsetNanosecondsFor(cx
, timeZone
, instant
, &offsetNanoseconds
)) {
1422 MOZ_ASSERT(std::abs(offsetNanoseconds
) < ToNanoseconds(TemporalUnit::Day
));
1424 auto* unwrappedInstant
= instant
.unwrap(cx
);
1425 if (!unwrappedInstant
) {
1431 GetPlainDateTimeFor(ToInstant(unwrappedInstant
), offsetNanoseconds
);
1433 // FIXME: spec issue - CreateTemporalDateTime is infallible
1434 // https://github.com/tc39/proposal-temporal/issues/2523
1435 MOZ_ASSERT(ISODateTimeWithinLimits(dateTime
));
1437 return CreateTemporalDateTime(cx
, dateTime
, calendar
);
1441 * GetPlainDateTimeFor ( timeZoneRec, instant, calendar [ ,
1442 * precalculatedOffsetNanoseconds ] )
1444 PlainDateTime
js::temporal::GetPlainDateTimeFor(const Instant
& instant
,
1445 int64_t offsetNanoseconds
) {
1446 // Steps 1-3. (Not applicable)
1449 MOZ_ASSERT(std::abs(offsetNanoseconds
) < ToNanoseconds(TemporalUnit::Day
));
1451 // TODO: Steps 5-6 can be combined into a single operation to improve perf.
1454 PlainDateTime dateTime
= GetISOPartsFromEpoch(instant
);
1457 auto balanced
= BalanceISODateTime(dateTime
, offsetNanoseconds
);
1459 // FIXME: spec issue - CreateTemporalDateTime is infallible
1460 // https://github.com/tc39/proposal-temporal/issues/2523
1461 MOZ_ASSERT(ISODateTimeWithinLimits(balanced
));
1468 * GetPlainDateTimeFor ( timeZone, instant, calendar [ ,
1469 * precalculatedOffsetNanoseconds ] )
1471 bool js::temporal::GetPlainDateTimeFor(JSContext
* cx
,
1472 Handle
<TimeZoneRecord
> timeZone
,
1473 const Instant
& instant
,
1474 PlainDateTime
* result
) {
1475 MOZ_ASSERT(IsValidEpochInstant(instant
));
1478 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1479 timeZone
, TimeZoneMethod::GetOffsetNanosecondsFor
));
1482 int64_t offsetNanoseconds
;
1483 if (!GetOffsetNanosecondsFor(cx
, timeZone
, instant
, &offsetNanoseconds
)) {
1488 MOZ_ASSERT(std::abs(offsetNanoseconds
) < ToNanoseconds(TemporalUnit::Day
));
1491 *result
= GetPlainDateTimeFor(instant
, offsetNanoseconds
);
1496 * GetPlainDateTimeFor ( timeZone, instant, calendar [ ,
1497 * precalculatedOffsetNanoseconds ] )
1499 bool js::temporal::GetPlainDateTimeFor(JSContext
* cx
,
1500 Handle
<TimeZoneValue
> timeZone
,
1501 const Instant
& instant
,
1502 PlainDateTime
* result
) {
1503 Rooted
<TimeZoneRecord
> timeZoneRec(cx
);
1504 if (!CreateTimeZoneMethodsRecord(cx
, timeZone
,
1506 TimeZoneMethod::GetOffsetNanosecondsFor
,
1512 return GetPlainDateTimeFor(cx
, timeZoneRec
, instant
, result
);
1516 * GetPlainDateTimeFor ( timeZone, instant, calendar [ ,
1517 * precalculatedOffsetNanoseconds ] )
1519 PlainDateTimeObject
* js::temporal::GetPlainDateTimeFor(
1520 JSContext
* cx
, Handle
<TimeZoneValue
> timeZone
, const Instant
& instant
,
1521 Handle
<CalendarValue
> calendar
) {
1523 PlainDateTime dateTime
;
1524 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &dateTime
)) {
1528 // FIXME: spec issue - CreateTemporalDateTime is infallible
1529 // https://github.com/tc39/proposal-temporal/issues/2523
1530 MOZ_ASSERT(ISODateTimeWithinLimits(dateTime
));
1533 return CreateTemporalDateTime(cx
, dateTime
, calendar
);
1537 * GetPlainDateTimeFor ( timeZone, instant, calendar [ ,
1538 * precalculatedOffsetNanoseconds ] )
1540 PlainDateTimeObject
* js::temporal::GetPlainDateTimeFor(
1541 JSContext
* cx
, const Instant
& instant
, Handle
<CalendarValue
> calendar
,
1542 int64_t offsetNanoseconds
) {
1543 MOZ_ASSERT(IsValidEpochInstant(instant
));
1546 auto dateTime
= GetPlainDateTimeFor(instant
, offsetNanoseconds
);
1548 // FIXME: spec issue - CreateTemporalDateTime is infallible
1549 // https://github.com/tc39/proposal-temporal/issues/2523
1550 MOZ_ASSERT(ISODateTimeWithinLimits(dateTime
));
1553 return CreateTemporalDateTime(cx
, dateTime
, calendar
);
1557 * Temporal.TimeZone.prototype.getPossibleInstantsFor ( dateTime )
1559 static bool BuiltinGetPossibleInstantsFor(
1560 JSContext
* cx
, Handle
<TimeZoneObjectMaybeBuiltin
*> timeZone
,
1561 const PlainDateTime
& dateTime
, EpochInstantList
& possibleInstants
) {
1562 MOZ_ASSERT(possibleInstants
.length() == 0);
1564 // Steps 1-3. (Not applicable)
1567 if (timeZone
->offsetMinutes().isInt32()) {
1568 int32_t offsetMin
= timeZone
->offsetMinutes().toInt32();
1569 MOZ_ASSERT(std::abs(offsetMin
) < UnitsPerDay(TemporalUnit::Minute
));
1573 GetUTCEpochNanoseconds(dateTime
, InstantSpan::fromMinutes(offsetMin
));
1576 possibleInstants
.append(epochInstant
);
1579 if (!GetNamedTimeZoneEpochNanoseconds(cx
, timeZone
, dateTime
,
1580 possibleInstants
)) {
1585 MOZ_ASSERT(possibleInstants
.length() <= 2);
1588 for (const auto& epochInstant
: possibleInstants
) {
1589 if (!IsValidEpochInstant(epochInstant
)) {
1590 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1591 JSMSG_TEMPORAL_INSTANT_INVALID
);
1596 // Steps 6-8. (Handled in the caller).
1600 static bool BuiltinGetPossibleInstantsFor(
1601 JSContext
* cx
, Handle
<TimeZoneObjectMaybeBuiltin
*> timeZone
,
1602 const PlainDateTime
& dateTime
, MutableHandle
<InstantVector
> list
) {
1603 // Temporal.TimeZone.prototype.getInstantFor, step 4.
1604 EpochInstantList possibleInstants
;
1605 if (!BuiltinGetPossibleInstantsFor(cx
, timeZone
, dateTime
,
1606 possibleInstants
)) {
1610 // Temporal.TimeZone.prototype.getInstantFor, step 7.
1611 for (const auto& possibleInstant
: possibleInstants
) {
1612 auto* instant
= CreateTemporalInstant(cx
, possibleInstant
);
1617 if (!list
.append(instant
)) {
1625 * GetPossibleInstantsFor ( timeZoneRec, dateTime )
1627 static bool GetPossibleInstantsForSlow(
1628 JSContext
* cx
, Handle
<TimeZoneRecord
> timeZone
,
1629 Handle
<Wrapped
<PlainDateTimeObject
*>> dateTime
,
1630 MutableHandle
<InstantVector
> list
) {
1631 // Step 1. (Inlined call to TimeZoneMethodsRecordCall)
1632 Rooted
<Value
> fval(cx
, ObjectValue(*timeZone
.getPossibleInstantsFor()));
1633 auto thisv
= timeZone
.receiver().toObject();
1634 Rooted
<Value
> arg(cx
, ObjectValue(*dateTime
));
1635 Rooted
<Value
> rval(cx
);
1636 if (!Call(cx
, fval
, thisv
, arg
, &rval
)) {
1640 // Step 2. (Not applicable)
1643 JS::ForOfIterator
iterator(cx
);
1644 if (!iterator
.init(rval
)) {
1648 // Step 4. (Not applicable in our implementation.)
1651 Rooted
<Value
> nextValue(cx
);
1653 // Steps 6.a and 6.b.i.
1655 if (!iterator
.next(&nextValue
, &done
)) {
1663 if (nextValue
.isObject()) {
1664 JSObject
* obj
= &nextValue
.toObject();
1665 if (obj
->canUnwrapAs
<InstantObject
>()) {
1667 if (!list
.append(obj
)) {
1675 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_IGNORE_STACK
, nextValue
,
1676 nullptr, "not an instant");
1679 iterator
.closeThrow();
1688 * GetPossibleInstantsFor ( timeZoneRec, dateTime )
1690 static bool GetPossibleInstantsFor(
1691 JSContext
* cx
, Handle
<TimeZoneRecord
> timeZone
,
1692 Handle
<Wrapped
<PlainDateTimeObject
*>> dateTimeObj
,
1693 const PlainDateTime
& dateTime
, MutableHandle
<InstantVector
> list
) {
1694 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1695 timeZone
, TimeZoneMethod::GetPossibleInstantsFor
));
1697 // Step 2. (Reordered)
1698 auto getPossibleInstantsFor
= timeZone
.getPossibleInstantsFor();
1699 if (!getPossibleInstantsFor
) {
1700 bool arrayIterationSane
;
1701 if (timeZone
.receiver().isString()) {
1702 // "String" time zones don't perform observable array iteration.
1703 arrayIterationSane
= true;
1705 // "Object" time zones need to ensure array iteration is still sane.
1706 if (!IsArrayIterationSane(cx
, &arrayIterationSane
)) {
1711 if (arrayIterationSane
) {
1712 auto builtin
= timeZone
.receiver().toTimeZoneObjectMaybeBuiltin();
1713 return BuiltinGetPossibleInstantsFor(cx
, builtin
, dateTime
, list
);
1718 return GetPossibleInstantsForSlow(cx
, timeZone
, dateTimeObj
, list
);
1722 * GetPossibleInstantsFor ( timeZoneRec, dateTime )
1724 bool js::temporal::GetPossibleInstantsFor(
1725 JSContext
* cx
, Handle
<TimeZoneRecord
> timeZone
,
1726 Handle
<PlainDateTimeWithCalendar
> dateTime
,
1727 MutableHandle
<InstantVector
> list
) {
1728 // Step 2. (Reordered)
1729 auto getPossibleInstantsFor
= timeZone
.getPossibleInstantsFor();
1730 if (!getPossibleInstantsFor
) {
1731 bool arrayIterationSane
;
1732 if (timeZone
.receiver().isString()) {
1733 // "String" time zones don't perform observable array iteration.
1734 arrayIterationSane
= true;
1736 // "Object" time zones need to ensure array iteration is still sane.
1737 if (!IsArrayIterationSane(cx
, &arrayIterationSane
)) {
1742 if (arrayIterationSane
) {
1743 auto builtin
= timeZone
.receiver().toTimeZoneObjectMaybeBuiltin();
1744 return BuiltinGetPossibleInstantsFor(cx
, builtin
,
1745 ToPlainDateTime(dateTime
), list
);
1749 Rooted
<PlainDateTimeObject
*> dateTimeObj(
1750 cx
, CreateTemporalDateTime(cx
, ToPlainDateTime(dateTime
),
1751 dateTime
.calendar()));
1757 return GetPossibleInstantsForSlow(cx
, timeZone
, dateTimeObj
, list
);
1761 * AddTime ( hour, minute, second, millisecond, microsecond, nanosecond, hours,
1762 * minutes, seconds, milliseconds, microseconds, nanoseconds )
1764 static auto AddTime(const PlainTime
& time
, int64_t nanoseconds
) {
1765 MOZ_ASSERT(IsValidTime(time
));
1766 MOZ_ASSERT(std::abs(nanoseconds
) <= 2 * ToNanoseconds(TemporalUnit::Day
));
1769 return BalanceTime(time
, nanoseconds
);
1773 * DisambiguatePossibleInstants ( possibleInstants, timeZoneRec, dateTime,
1776 bool js::temporal::DisambiguatePossibleInstants(
1777 JSContext
* cx
, Handle
<InstantVector
> possibleInstants
,
1778 Handle
<TimeZoneRecord
> timeZone
, const PlainDateTime
& dateTime
,
1779 TemporalDisambiguation disambiguation
,
1780 MutableHandle
<Wrapped
<InstantObject
*>> result
) {
1782 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1783 timeZone
, TimeZoneMethod::GetPossibleInstantsFor
));
1786 MOZ_ASSERT_IF(possibleInstants
.empty() &&
1787 disambiguation
!= TemporalDisambiguation::Reject
,
1788 TimeZoneMethodsRecordHasLookedUp(
1789 timeZone
, TimeZoneMethod::GetOffsetNanosecondsFor
));
1792 if (possibleInstants
.length() == 1) {
1793 result
.set(possibleInstants
[0]);
1798 if (!possibleInstants
.empty()) {
1800 if (disambiguation
== TemporalDisambiguation::Earlier
||
1801 disambiguation
== TemporalDisambiguation::Compatible
) {
1802 result
.set(possibleInstants
[0]);
1807 if (disambiguation
== TemporalDisambiguation::Later
) {
1808 size_t last
= possibleInstants
.length() - 1;
1809 result
.set(possibleInstants
[last
]);
1814 MOZ_ASSERT(disambiguation
== TemporalDisambiguation::Reject
);
1817 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1818 JSMSG_TEMPORAL_TIMEZONE_INSTANT_AMBIGUOUS
);
1823 if (disambiguation
== TemporalDisambiguation::Reject
) {
1824 // TODO: Improve error message to say the date was skipped.
1825 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1826 JSMSG_TEMPORAL_TIMEZONE_INSTANT_AMBIGUOUS
);
1830 constexpr auto oneDay
=
1831 InstantSpan::fromNanoseconds(ToNanoseconds(TemporalUnit::Day
));
1834 auto epochNanoseconds
= GetUTCEpochNanoseconds(dateTime
);
1837 auto dayBefore
= epochNanoseconds
- oneDay
;
1840 if (!IsValidEpochInstant(dayBefore
)) {
1841 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1842 JSMSG_TEMPORAL_INSTANT_INVALID
);
1847 auto dayAfter
= epochNanoseconds
+ oneDay
;
1850 if (!IsValidEpochInstant(dayAfter
)) {
1851 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1852 JSMSG_TEMPORAL_INSTANT_INVALID
);
1857 int64_t offsetBefore
;
1858 if (!GetOffsetNanosecondsFor(cx
, timeZone
, dayBefore
, &offsetBefore
)) {
1861 MOZ_ASSERT(std::abs(offsetBefore
) < ToNanoseconds(TemporalUnit::Day
));
1864 int64_t offsetAfter
;
1865 if (!GetOffsetNanosecondsFor(cx
, timeZone
, dayAfter
, &offsetAfter
)) {
1868 MOZ_ASSERT(std::abs(offsetAfter
) < ToNanoseconds(TemporalUnit::Day
));
1871 int64_t nanoseconds
= offsetAfter
- offsetBefore
;
1874 if (disambiguation
== TemporalDisambiguation::Earlier
) {
1876 auto earlierTime
= ::AddTime(dateTime
.time
, -nanoseconds
);
1877 MOZ_ASSERT(std::abs(earlierTime
.days
) <= 2,
1878 "subtracting nanoseconds is at most two days");
1881 PlainDate earlierDate
;
1882 if (!AddISODate(cx
, dateTime
.date
, {0, 0, 0, earlierTime
.days
},
1883 TemporalOverflow::Constrain
, &earlierDate
)) {
1888 Rooted
<CalendarValue
> calendar(cx
, CalendarValue(cx
->names().iso8601
));
1889 Rooted
<PlainDateTimeWithCalendar
> earlierDateTime(
1891 PlainDateTimeWithCalendar
{{earlierDate
, earlierTime
.time
}, calendar
});
1894 Rooted
<InstantVector
> earlierInstants(cx
, InstantVector(cx
));
1895 if (!GetPossibleInstantsFor(cx
, timeZone
, earlierDateTime
,
1896 &earlierInstants
)) {
1901 if (earlierInstants
.empty()) {
1902 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1903 JSMSG_TEMPORAL_TIMEZONE_INSTANT_AMBIGUOUS
);
1908 result
.set(earlierInstants
[0]);
1913 MOZ_ASSERT(disambiguation
== TemporalDisambiguation::Compatible
||
1914 disambiguation
== TemporalDisambiguation::Later
);
1917 auto laterTime
= ::AddTime(dateTime
.time
, nanoseconds
);
1918 MOZ_ASSERT(std::abs(laterTime
.days
) <= 2,
1919 "adding nanoseconds is at most two days");
1922 PlainDate laterDate
;
1923 if (!AddISODate(cx
, dateTime
.date
, {0, 0, 0, laterTime
.days
},
1924 TemporalOverflow::Constrain
, &laterDate
)) {
1929 Rooted
<CalendarValue
> calendar(cx
, CalendarValue(cx
->names().iso8601
));
1930 Rooted
<PlainDateTimeWithCalendar
> laterDateTime(
1931 cx
, PlainDateTimeWithCalendar
{{laterDate
, laterTime
.time
}, calendar
});
1934 Rooted
<InstantVector
> laterInstants(cx
, InstantVector(cx
));
1935 if (!GetPossibleInstantsFor(cx
, timeZone
, laterDateTime
, &laterInstants
)) {
1940 if (laterInstants
.empty()) {
1941 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1942 JSMSG_TEMPORAL_TIMEZONE_INSTANT_AMBIGUOUS
);
1947 size_t last
= laterInstants
.length() - 1;
1948 result
.set(laterInstants
[last
]);
1953 * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
1955 static bool GetInstantFor(JSContext
* cx
, Handle
<TimeZoneRecord
> timeZone
,
1956 Handle
<Wrapped
<PlainDateTimeObject
*>> dateTime
,
1957 TemporalDisambiguation disambiguation
,
1958 MutableHandle
<Wrapped
<InstantObject
*>> result
) {
1960 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1961 timeZone
, TimeZoneMethod::GetOffsetNanosecondsFor
));
1964 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1965 timeZone
, TimeZoneMethod::GetPossibleInstantsFor
));
1967 auto* unwrappedDateTime
= dateTime
.unwrap(cx
);
1968 if (!unwrappedDateTime
) {
1971 auto plainDateTime
= ToPlainDateTime(unwrappedDateTime
);
1974 Rooted
<InstantVector
> possibleInstants(cx
, InstantVector(cx
));
1975 if (!GetPossibleInstantsFor(cx
, timeZone
, dateTime
, plainDateTime
,
1976 &possibleInstants
)) {
1981 return DisambiguatePossibleInstants(cx
, possibleInstants
, timeZone
,
1982 plainDateTime
, disambiguation
, result
);
1986 * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
1988 static bool GetInstantFor(JSContext
* cx
, Handle
<TimeZoneValue
> timeZone
,
1989 Handle
<Wrapped
<PlainDateTimeObject
*>> dateTime
,
1990 TemporalDisambiguation disambiguation
,
1991 MutableHandle
<Wrapped
<InstantObject
*>> result
) {
1992 Rooted
<TimeZoneRecord
> timeZoneRec(cx
);
1993 if (!CreateTimeZoneMethodsRecord(cx
, timeZone
,
1995 TimeZoneMethod::GetOffsetNanosecondsFor
,
1996 TimeZoneMethod::GetPossibleInstantsFor
,
2002 return GetInstantFor(cx
, timeZoneRec
, dateTime
, disambiguation
, result
);
2006 * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
2008 bool js::temporal::GetInstantFor(JSContext
* cx
, Handle
<TimeZoneValue
> timeZone
,
2009 Handle
<PlainDateTimeObject
*> dateTime
,
2010 TemporalDisambiguation disambiguation
,
2012 Rooted
<TimeZoneRecord
> timeZoneRec(cx
);
2013 if (!CreateTimeZoneMethodsRecord(cx
, timeZone
,
2015 TimeZoneMethod::GetOffsetNanosecondsFor
,
2016 TimeZoneMethod::GetPossibleInstantsFor
,
2022 Rooted
<Wrapped
<InstantObject
*>> instant(cx
);
2023 if (!::GetInstantFor(cx
, timeZoneRec
, dateTime
, disambiguation
, &instant
)) {
2027 auto* unwrappedInstant
= instant
.unwrap(cx
);
2028 if (!unwrappedInstant
) {
2032 *result
= ToInstant(unwrappedInstant
);
2037 * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
2039 bool js::temporal::GetInstantFor(JSContext
* cx
, Handle
<TimeZoneRecord
> timeZone
,
2040 Handle
<PlainDateTimeWithCalendar
> dateTime
,
2041 TemporalDisambiguation disambiguation
,
2044 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
2045 timeZone
, TimeZoneMethod::GetOffsetNanosecondsFor
));
2048 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
2049 timeZone
, TimeZoneMethod::GetPossibleInstantsFor
));
2052 Rooted
<InstantVector
> possibleInstants(cx
, InstantVector(cx
));
2053 if (!GetPossibleInstantsFor(cx
, timeZone
, dateTime
, &possibleInstants
)) {
2058 Rooted
<Wrapped
<InstantObject
*>> instant(cx
);
2059 if (!DisambiguatePossibleInstants(cx
, possibleInstants
, timeZone
,
2060 ToPlainDateTime(dateTime
), disambiguation
,
2065 auto* unwrappedInstant
= instant
.unwrap(cx
);
2066 if (!unwrappedInstant
) {
2070 *result
= ToInstant(unwrappedInstant
);
2075 * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
2077 bool js::temporal::GetInstantFor(JSContext
* cx
, Handle
<TimeZoneValue
> timeZone
,
2078 Handle
<PlainDateTimeWithCalendar
> dateTime
,
2079 TemporalDisambiguation disambiguation
,
2081 Rooted
<TimeZoneRecord
> timeZoneRec(cx
);
2082 if (!CreateTimeZoneMethodsRecord(cx
, timeZone
,
2084 TimeZoneMethod::GetOffsetNanosecondsFor
,
2085 TimeZoneMethod::GetPossibleInstantsFor
,
2091 return GetInstantFor(cx
, timeZoneRec
, dateTime
, disambiguation
, result
);
2095 * IsOffsetTimeZoneIdentifier ( offsetString )
2097 * Return true if |offsetString| is the prefix of a time zone offset string.
2098 * Time zone offset strings are be parsed through the |TimeZoneUTCOffsetName|
2101 * TimeZoneUTCOffsetName :
2102 * UTCOffsetMinutePrecision
2104 * UTCOffsetMinutePrecision :
2105 * Sign Hour[+Padded]
2106 * Sign Hour[+Padded] TimeSeparator[+Extended] MinuteSecond
2107 * Sign Hour[+Padded] TimeSeparator[~Extended] MinuteSecond
2113 * ASCIISign : one of + -
2115 * NOTE: IANA time zone identifiers can't start with |Sign|.
2117 static bool IsOffsetTimeZoneIdentifierPrefix(JSLinearString
* offsetString
) {
2118 // Empty string can't be the prefix of |TimeZoneUTCOffsetName|.
2119 if (offsetString
->empty()) {
2123 // Return true iff |offsetString| starts with |Sign|.
2124 char16_t ch
= offsetString
->latin1OrTwoByteChar(0);
2125 return ch
== '+' || ch
== '-' || ch
== 0x2212;
2129 * Temporal.TimeZone ( identifier )
2131 static bool TimeZoneConstructor(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2132 CallArgs args
= CallArgsFromVp(argc
, vp
);
2135 if (!ThrowIfNotConstructing(cx
, args
, "Temporal.TimeZone")) {
2140 if (!args
.requireAtLeast(cx
, "Temporal.TimeZone", 1)) {
2144 if (!args
[0].isString()) {
2145 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_SEARCH_STACK
, args
[0],
2146 nullptr, "not a string");
2150 Rooted
<JSLinearString
*> identifier(cx
, args
[0].toString()->ensureLinear(cx
));
2155 Rooted
<JSString
*> canonical(cx
);
2156 Rooted
<Value
> offsetMinutes(cx
);
2157 if (IsOffsetTimeZoneIdentifierPrefix(identifier
)) {
2160 if (!ParseTimeZoneOffsetString(cx
, identifier
, &minutes
)) {
2163 MOZ_ASSERT(std::abs(minutes
) < UnitsPerDay(TemporalUnit::Minute
));
2165 canonical
= FormatOffsetTimeZoneIdentifier(cx
, minutes
);
2170 offsetMinutes
.setInt32(minutes
);
2173 canonical
= ValidateAndCanonicalizeTimeZoneName(cx
, identifier
);
2178 offsetMinutes
.setUndefined();
2182 auto* timeZone
= CreateTemporalTimeZone(cx
, args
, canonical
, offsetMinutes
);
2187 args
.rval().setObject(*timeZone
);
2192 * Temporal.TimeZone.from ( item )
2194 static bool TimeZone_from(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2195 CallArgs args
= CallArgsFromVp(argc
, vp
);
2198 Rooted
<TimeZoneValue
> timeZone(cx
);
2199 if (!ToTemporalTimeZone(cx
, args
.get(0), &timeZone
)) {
2204 auto* obj
= ToTemporalTimeZoneObject(cx
, timeZone
);
2209 args
.rval().setObject(*obj
);
2214 * Temporal.TimeZone.prototype.equals ( timeZoneLike )
2216 static bool TimeZone_equals(JSContext
* cx
, const CallArgs
& args
) {
2217 Rooted
<TimeZoneValue
> timeZone(cx
, &args
.thisv().toObject());
2220 Rooted
<TimeZoneValue
> other(cx
);
2221 if (!ToTemporalTimeZone(cx
, args
.get(0), &other
)) {
2227 if (!TimeZoneEquals(cx
, timeZone
, other
, &equals
)) {
2231 args
.rval().setBoolean(equals
);
2236 * Temporal.TimeZone.prototype.equals ( timeZoneLike )
2238 static bool TimeZone_equals(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2240 CallArgs args
= CallArgsFromVp(argc
, vp
);
2241 return CallNonGenericMethod
<IsTimeZone
, TimeZone_equals
>(cx
, args
);
2245 * Temporal.TimeZone.prototype.getOffsetNanosecondsFor ( instant )
2247 static bool TimeZone_getOffsetNanosecondsFor(JSContext
* cx
,
2248 const CallArgs
& args
) {
2249 Rooted
<TimeZoneObject
*> timeZone(
2250 cx
, &args
.thisv().toObject().as
<TimeZoneObject
>());
2254 if (!ToTemporalInstant(cx
, args
.get(0), &instant
)) {
2260 if (!BuiltinGetOffsetNanosecondsFor(cx
, timeZone
, instant
, &offset
)) {
2264 args
.rval().setNumber(offset
);
2269 * Temporal.TimeZone.prototype.getOffsetNanosecondsFor ( instant )
2271 static bool TimeZone_getOffsetNanosecondsFor(JSContext
* cx
, unsigned argc
,
2274 CallArgs args
= CallArgsFromVp(argc
, vp
);
2275 return CallNonGenericMethod
<IsTimeZone
, TimeZone_getOffsetNanosecondsFor
>(
2280 * Temporal.TimeZone.prototype.getOffsetStringFor ( instant )
2282 static bool TimeZone_getOffsetStringFor(JSContext
* cx
, const CallArgs
& args
) {
2283 Rooted
<TimeZoneValue
> timeZone(cx
, &args
.thisv().toObject());
2285 // FIXME: spec issue - CreateTimeZoneMethodsRecord called before
2286 // ToTemporalInstant whereas TimeZone.p.{getPlainDateTimeFor,getInstantFor}
2287 // first convert the input arguments.
2290 Rooted
<TimeZoneRecord
> timeZoneRec(cx
);
2291 if (!CreateTimeZoneMethodsRecord(cx
, timeZone
,
2293 TimeZoneMethod::GetOffsetNanosecondsFor
,
2300 Rooted
<Wrapped
<InstantObject
*>> instant(cx
,
2301 ToTemporalInstant(cx
, args
.get(0)));
2307 JSString
* str
= GetOffsetStringFor(cx
, timeZoneRec
, instant
);
2312 args
.rval().setString(str
);
2317 * Temporal.TimeZone.prototype.getOffsetStringFor ( instant )
2319 static bool TimeZone_getOffsetStringFor(JSContext
* cx
, unsigned argc
,
2322 CallArgs args
= CallArgsFromVp(argc
, vp
);
2323 return CallNonGenericMethod
<IsTimeZone
, TimeZone_getOffsetStringFor
>(cx
,
2328 * Temporal.TimeZone.prototype.getPlainDateTimeFor ( instant [, calendarLike ] )
2330 static bool TimeZone_getPlainDateTimeFor(JSContext
* cx
, const CallArgs
& args
) {
2331 Rooted
<TimeZoneValue
> timeZone(cx
, &args
.thisv().toObject());
2334 Rooted
<Wrapped
<InstantObject
*>> instant(cx
,
2335 ToTemporalInstant(cx
, args
.get(0)));
2341 Rooted
<CalendarValue
> calendar(cx
);
2342 if (!ToTemporalCalendarWithISODefault(cx
, args
.get(1), &calendar
)) {
2347 auto* result
= GetPlainDateTimeFor(cx
, timeZone
, instant
, calendar
);
2352 args
.rval().setObject(*result
);
2357 * Temporal.TimeZone.prototype.getPlainDateTimeFor ( instant [, calendarLike ] )
2359 static bool TimeZone_getPlainDateTimeFor(JSContext
* cx
, unsigned argc
,
2362 CallArgs args
= CallArgsFromVp(argc
, vp
);
2363 return CallNonGenericMethod
<IsTimeZone
, TimeZone_getPlainDateTimeFor
>(cx
,
2368 * Temporal.TimeZone.prototype.getInstantFor ( dateTime [ , options ] )
2370 static bool TimeZone_getInstantFor(JSContext
* cx
, const CallArgs
& args
) {
2371 Rooted
<TimeZoneValue
> timeZone(cx
, &args
.thisv().toObject());
2374 Rooted
<Wrapped
<PlainDateTimeObject
*>> dateTime(
2375 cx
, ToTemporalDateTime(cx
, args
.get(0)));
2381 auto disambiguation
= TemporalDisambiguation::Compatible
;
2382 if (args
.hasDefined(1)) {
2384 Rooted
<JSObject
*> options(
2385 cx
, RequireObjectArg(cx
, "options", "getInstantFor", args
[1]));
2391 if (!ToTemporalDisambiguation(cx
, options
, &disambiguation
)) {
2397 Rooted
<Wrapped
<InstantObject
*>> result(cx
);
2398 if (!::GetInstantFor(cx
, timeZone
, dateTime
, disambiguation
, &result
)) {
2402 args
.rval().setObject(*result
);
2407 * Temporal.TimeZone.prototype.getInstantFor ( dateTime [ , options ] )
2409 static bool TimeZone_getInstantFor(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2411 CallArgs args
= CallArgsFromVp(argc
, vp
);
2412 return CallNonGenericMethod
<IsTimeZone
, TimeZone_getInstantFor
>(cx
, args
);
2416 * Temporal.TimeZone.prototype.getPossibleInstantsFor ( dateTime )
2418 static bool TimeZone_getPossibleInstantsFor(JSContext
* cx
,
2419 const CallArgs
& args
) {
2420 Rooted
<TimeZoneObject
*> timeZone(
2421 cx
, &args
.thisv().toObject().as
<TimeZoneObject
>());
2424 PlainDateTime dateTime
;
2425 if (!ToTemporalDateTime(cx
, args
.get(0), &dateTime
)) {
2430 EpochInstantList possibleInstants
;
2431 if (!BuiltinGetPossibleInstantsFor(cx
, timeZone
, dateTime
,
2432 possibleInstants
)) {
2437 size_t length
= possibleInstants
.length();
2438 Rooted
<ArrayObject
*> result(cx
, NewDenseFullyAllocatedArray(cx
, length
));
2442 result
->ensureDenseInitializedLength(0, length
);
2445 for (size_t i
= 0; i
< length
; i
++) {
2446 // Step 7.a. (Already performed in step 4 in our implementation.)
2447 MOZ_ASSERT(IsValidEpochInstant(possibleInstants
[i
]));
2450 auto* instant
= CreateTemporalInstant(cx
, possibleInstants
[i
]);
2456 result
->initDenseElement(i
, ObjectValue(*instant
));
2460 args
.rval().setObject(*result
);
2465 * Temporal.TimeZone.prototype.getPossibleInstantsFor ( dateTime )
2467 static bool TimeZone_getPossibleInstantsFor(JSContext
* cx
, unsigned argc
,
2470 CallArgs args
= CallArgsFromVp(argc
, vp
);
2471 return CallNonGenericMethod
<IsTimeZone
, TimeZone_getPossibleInstantsFor
>(
2476 * Temporal.TimeZone.prototype.getNextTransition ( startingPoint )
2478 static bool TimeZone_getNextTransition(JSContext
* cx
, const CallArgs
& args
) {
2479 Rooted
<TimeZoneObject
*> timeZone(
2480 cx
, &args
.thisv().toObject().as
<TimeZoneObject
>());
2483 Instant startingPoint
;
2484 if (!ToTemporalInstant(cx
, args
.get(0), &startingPoint
)) {
2489 if (!timeZone
->offsetMinutes().isUndefined()) {
2490 args
.rval().setNull();
2495 mozilla::Maybe
<Instant
> transition
;
2496 if (!GetNamedTimeZoneNextTransition(cx
, timeZone
, startingPoint
,
2503 args
.rval().setNull();
2508 auto* instant
= CreateTemporalInstant(cx
, *transition
);
2513 args
.rval().setObject(*instant
);
2518 * Temporal.TimeZone.prototype.getNextTransition ( startingPoint )
2520 static bool TimeZone_getNextTransition(JSContext
* cx
, unsigned argc
,
2523 CallArgs args
= CallArgsFromVp(argc
, vp
);
2524 return CallNonGenericMethod
<IsTimeZone
, TimeZone_getNextTransition
>(cx
, args
);
2528 * Temporal.TimeZone.prototype.getPreviousTransition ( startingPoint )
2530 static bool TimeZone_getPreviousTransition(JSContext
* cx
,
2531 const CallArgs
& args
) {
2532 Rooted
<TimeZoneObject
*> timeZone(
2533 cx
, &args
.thisv().toObject().as
<TimeZoneObject
>());
2536 Instant startingPoint
;
2537 if (!ToTemporalInstant(cx
, args
.get(0), &startingPoint
)) {
2542 if (!timeZone
->offsetMinutes().isUndefined()) {
2543 args
.rval().setNull();
2548 mozilla::Maybe
<Instant
> transition
;
2549 if (!GetNamedTimeZonePreviousTransition(cx
, timeZone
, startingPoint
,
2556 args
.rval().setNull();
2561 auto* instant
= CreateTemporalInstant(cx
, *transition
);
2566 args
.rval().setObject(*instant
);
2571 * Temporal.TimeZone.prototype.getPreviousTransition ( startingPoint )
2573 static bool TimeZone_getPreviousTransition(JSContext
* cx
, unsigned argc
,
2576 CallArgs args
= CallArgsFromVp(argc
, vp
);
2577 return CallNonGenericMethod
<IsTimeZone
, TimeZone_getPreviousTransition
>(cx
,
2582 * Temporal.TimeZone.prototype.toString ( )
2584 static bool TimeZone_toString(JSContext
* cx
, const CallArgs
& args
) {
2585 auto* timeZone
= &args
.thisv().toObject().as
<TimeZoneObject
>();
2588 args
.rval().setString(timeZone
->identifier());
2593 * Temporal.TimeZone.prototype.toString ( )
2595 static bool TimeZone_toString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2597 CallArgs args
= CallArgsFromVp(argc
, vp
);
2598 return CallNonGenericMethod
<IsTimeZone
, TimeZone_toString
>(cx
, args
);
2602 * Temporal.TimeZone.prototype.toJSON ( )
2604 static bool TimeZone_toJSON(JSContext
* cx
, const CallArgs
& args
) {
2605 auto* timeZone
= &args
.thisv().toObject().as
<TimeZoneObject
>();
2608 args
.rval().setString(timeZone
->identifier());
2613 * Temporal.TimeZone.prototype.toJSON ( )
2615 static bool TimeZone_toJSON(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2617 CallArgs args
= CallArgsFromVp(argc
, vp
);
2618 return CallNonGenericMethod
<IsTimeZone
, TimeZone_toJSON
>(cx
, args
);
2622 * get Temporal.TimeZone.prototype.id
2624 static bool TimeZone_id(JSContext
* cx
, const CallArgs
& args
) {
2625 auto* timeZone
= &args
.thisv().toObject().as
<TimeZoneObject
>();
2628 args
.rval().setString(timeZone
->identifier());
2633 * get Temporal.TimeZone.prototype.id
2635 static bool TimeZone_id(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2637 CallArgs args
= CallArgsFromVp(argc
, vp
);
2638 return CallNonGenericMethod
<IsTimeZone
, TimeZone_id
>(cx
, args
);
2641 void js::temporal::TimeZoneObjectMaybeBuiltin::finalize(JS::GCContext
* gcx
,
2643 MOZ_ASSERT(gcx
->onMainThread());
2645 if (auto* timeZone
= obj
->as
<TimeZoneObjectMaybeBuiltin
>().getTimeZone()) {
2646 intl::RemoveICUCellMemory(gcx
, obj
, TimeZoneObject::EstimatedMemoryUse
);
2651 const JSClassOps
TimeZoneObject::classOps_
= {
2652 nullptr, // addProperty
2653 nullptr, // delProperty
2654 nullptr, // enumerate
2655 nullptr, // newEnumerate
2657 nullptr, // mayResolve
2658 TimeZoneObject::finalize
, // finalize
2660 nullptr, // construct
2664 const JSClass
TimeZoneObject::class_
= {
2665 "Temporal.TimeZone",
2666 JSCLASS_HAS_RESERVED_SLOTS(TimeZoneObject::SLOT_COUNT
) |
2667 JSCLASS_HAS_CACHED_PROTO(JSProto_TimeZone
) |
2668 JSCLASS_FOREGROUND_FINALIZE
,
2669 &TimeZoneObject::classOps_
,
2670 &TimeZoneObject::classSpec_
,
2673 const JSClass
& TimeZoneObject::protoClass_
= PlainObject::class_
;
2675 static const JSFunctionSpec TimeZone_methods
[] = {
2676 JS_FN("from", TimeZone_from
, 1, 0),
2680 static const JSFunctionSpec TimeZone_prototype_methods
[] = {
2681 JS_FN("equals", TimeZone_equals
, 1, 0),
2682 JS_FN("getOffsetNanosecondsFor", TimeZone_getOffsetNanosecondsFor
, 1, 0),
2683 JS_FN("getOffsetStringFor", TimeZone_getOffsetStringFor
, 1, 0),
2684 JS_FN("getPlainDateTimeFor", TimeZone_getPlainDateTimeFor
, 1, 0),
2685 JS_FN("getInstantFor", TimeZone_getInstantFor
, 1, 0),
2686 JS_FN("getPossibleInstantsFor", TimeZone_getPossibleInstantsFor
, 1, 0),
2687 JS_FN("getNextTransition", TimeZone_getNextTransition
, 1, 0),
2688 JS_FN("getPreviousTransition", TimeZone_getPreviousTransition
, 1, 0),
2689 JS_FN("toString", TimeZone_toString
, 0, 0),
2690 JS_FN("toJSON", TimeZone_toJSON
, 0, 0),
2694 static const JSPropertySpec TimeZone_prototype_properties
[] = {
2695 JS_PSG("id", TimeZone_id
, 0),
2696 JS_STRING_SYM_PS(toStringTag
, "Temporal.TimeZone", JSPROP_READONLY
),
2700 const ClassSpec
TimeZoneObject::classSpec_
= {
2701 GenericCreateConstructor
<TimeZoneConstructor
, 1, gc::AllocKind::FUNCTION
>,
2702 GenericCreatePrototype
<TimeZoneObject
>,
2705 TimeZone_prototype_methods
,
2706 TimeZone_prototype_properties
,
2708 ClassSpec::DontDefineConstructor
,
2711 const JSClassOps
BuiltinTimeZoneObject::classOps_
= {
2712 nullptr, // addProperty
2713 nullptr, // delProperty
2714 nullptr, // enumerate
2715 nullptr, // newEnumerate
2717 nullptr, // mayResolve
2718 BuiltinTimeZoneObject::finalize
, // finalize
2720 nullptr, // construct
2724 const JSClass
BuiltinTimeZoneObject::class_
= {
2725 "Temporal.BuiltinTimeZone",
2726 JSCLASS_HAS_RESERVED_SLOTS(BuiltinTimeZoneObject::SLOT_COUNT
) |
2727 JSCLASS_FOREGROUND_FINALIZE
,
2728 &BuiltinTimeZoneObject::classOps_
,