Bug 1874684 - Part 24: Prevent arbitrary loops in NormalizedTimeDurationToDays. r...
[gecko.git] / js / src / builtin / temporal / TimeZone.cpp
blobc7f86f34f008adf9669a55e40b6524d66e4e7435
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"
19 #include <cmath>
20 #include <cstdlib>
21 #include <initializer_list>
22 #include <iterator>
23 #include <utility>
25 #include "jsnum.h"
26 #include "jspubtd.h"
27 #include "jstypes.h"
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"
53 #include "js/Class.h"
54 #include "js/ComparisonOperators.h"
55 #include "js/Date.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"
82 using namespace js;
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) {
94 receiver_.trace(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)) {
105 return nullptr;
108 auto result = mozilla::intl::TimeZone::TryCreate(
109 mozilla::Some(stableChars.twoByteRange()));
110 if (result.isErr()) {
111 intl::ReportInternalError(cx, result.unwrapErr());
112 return nullptr;
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()) {
121 return tz;
124 auto* tz = CreateIntlTimeZone(cx, timeZone->identifier()).release();
125 if (!tz) {
126 return nullptr;
128 timeZone->setTimeZone(tz);
130 intl::AddICUCellMemory(timeZone,
131 TimeZoneObjectMaybeBuiltin::EstimatedMemoryUse);
132 return tz;
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)) {
145 return false;
148 if (validatedTimeZone) {
149 cx->markAtom(validatedTimeZone);
151 return true;
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.)
164 #ifdef DEBUG
165 MOZ_ASSERT(!StringEqualsLiteral(timeZone, "Etc/Unknown"),
166 "Invalid time zone");
168 Rooted<JSAtom*> checkTimeZone(cx);
169 if (!IsValidTimeZoneName(cx, timeZone, &checkTimeZone)) {
170 return nullptr;
172 MOZ_ASSERT(EqualStrings(timeZone, checkTimeZone),
173 "Time zone name not normalized");
174 #endif
176 // Step 2.
177 Rooted<JSLinearString*> ianaTimeZone(cx);
178 do {
179 intl::SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
181 // Some time zone names are canonicalized differently by ICU -- handle
182 // those first:
183 Rooted<JSAtom*> canonicalTimeZone(cx);
184 if (!sharedIntlData.tryCanonicalizeTimeZoneConsistentWithIANA(
185 cx, timeZone, &canonicalTimeZone)) {
186 return nullptr;
189 if (canonicalTimeZone) {
190 cx->markAtom(canonicalTimeZone);
191 ianaTimeZone = canonicalTimeZone;
192 break;
195 JS::AutoStableStringChars stableChars(cx);
196 if (!stableChars.initTwoByte(cx, timeZone)) {
197 return nullptr;
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());
205 return nullptr;
208 ianaTimeZone = buffer.toString(cx);
209 if (!ianaTimeZone) {
210 return nullptr;
212 } while (false);
214 #ifdef DEBUG
215 MOZ_ASSERT(!StringEqualsLiteral(ianaTimeZone, "Etc/Unknown"),
216 "Invalid canonical time zone");
218 if (!IsValidTimeZoneName(cx, ianaTimeZone, &checkTimeZone)) {
219 return nullptr;
221 MOZ_ASSERT(EqualStrings(ianaTimeZone, checkTimeZone),
222 "Unsupported canonical time zone");
223 #endif
225 // Step 3.
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"));
235 // Step 4.
236 return ianaTimeZone;
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)) {
248 return nullptr;
251 if (!validatedTimeZone) {
252 if (auto chars = QuoteString(cx, timeZone)) {
253 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
254 JSMSG_TEMPORAL_TIMEZONE_INVALID_IDENTIFIER,
255 chars.get());
257 return nullptr;
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_ = {};
268 size_t length_ = 0;
270 public:
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
297 // IsValidISODate
299 int64_t ms = MakeDate(dateTime);
301 auto* tz = GetOrCreateIntlTimeZone(cx, timeZone);
302 if (!tz) {
303 return false;
306 auto getOffset = [&](mozilla::intl::TimeZone::LocalOption skippedTime,
307 mozilla::intl::TimeZone::LocalOption repeatedTime,
308 int32_t* offset) {
309 auto result = tz->GetUTCOffsetMs(ms, skippedTime, repeatedTime);
310 if (result.isErr()) {
311 intl::ReportInternalError(cx, result.unwrapErr());
312 return false;
315 *offset = result.unwrap();
316 MOZ_ASSERT(std::abs(*offset) < UnitsPerDay(TemporalUnit::Millisecond));
318 return true;
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)) {
326 return false;
329 int32_t latterOffset;
330 if (!getOffset(latterTime, latterTime, &latterOffset)) {
331 return false;
334 if (formerOffset == latterOffset) {
335 auto instant = GetUTCEpochNanoseconds(
336 dateTime, InstantSpan::fromMilliseconds(formerOffset));
337 instants.append(instant);
338 return true;
341 int32_t disambiguationOffset;
342 if (!getOffset(formerTime, latterTime, &disambiguationOffset)) {
343 return false;
346 // Skipped time.
347 if (disambiguationOffset == formerOffset) {
348 return true;
351 // Repeated time.
352 for (auto offset : {formerOffset, latterOffset}) {
353 auto instant =
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]);
365 return true;
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);
380 if (!tz) {
381 return false;
384 auto result = tz->GetOffsetMs(millis);
385 if (result.isErr()) {
386 intl::ReportInternalError(cx, result.unwrapErr());
387 return false;
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;
395 return true;
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);
416 if (!tz) {
417 return false;
420 auto next = tz->GetNextTransition(millis);
421 if (next.isErr()) {
422 intl::ReportInternalError(cx, next.unwrapErr());
423 return false;
426 auto transition = next.unwrap();
427 if (!transition) {
428 *result = mozilla::Nothing();
429 return true;
432 auto transitionInstant = Instant::fromMilliseconds(*transition);
433 if (!IsValidEpochInstant(transitionInstant)) {
434 *result = mozilla::Nothing();
435 return true;
438 *result = mozilla::Some(transitionInstant);
439 return true;
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);
459 if (!tz) {
460 return false;
463 auto previous = tz->GetPreviousTransition(millis);
464 if (previous.isErr()) {
465 intl::ReportInternalError(cx, previous.unwrapErr());
466 return false;
469 auto transition = previous.unwrap();
470 if (!transition) {
471 *result = mozilla::Nothing();
472 return true;
475 auto transitionInstant = Instant::fromMilliseconds(*transition);
476 if (!IsValidEpochInstant(transitionInstant)) {
477 *result = mozilla::Nothing();
478 return true;
481 *result = mozilla::Some(transitionInstant);
482 return true;
486 * FormatOffsetTimeZoneIdentifier ( offsetMinutes [ , style ] )
488 static JSString* FormatOffsetTimeZoneIdentifier(JSContext* cx,
489 int32_t offsetMinutes) {
490 MOZ_ASSERT(std::abs(offsetMinutes) < UnitsPerDay(TemporalUnit::Minute));
492 // Step 1.
493 char sign = offsetMinutes >= 0 ? '+' : '-';
495 // Step 2.
496 int32_t absoluteMinutes = std::abs(offsetMinutes);
498 // Step 3.
499 int32_t hour = absoluteMinutes / 60;
501 // Step 4.
502 int32_t minute = absoluteMinutes % 60;
504 // Step 5. (Inlined FormatTimeString).
506 // Format: "sign hour{2} : minute{2}"
507 char result[] = {
508 sign, char('0' + (hour / 10)), char('0' + (hour % 10)),
509 ':', char('0' + (minute / 10)), char('0' + (minute % 10)),
512 // Step 6.
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));
527 // Steps 1-2.
528 Rooted<JSObject*> proto(cx);
529 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_TimeZone, &proto)) {
530 return nullptr;
533 auto* timeZone = NewObjectWithClassProto<TimeZoneObject>(cx, proto);
534 if (!timeZone) {
535 return nullptr;
538 // Step 4.a. (Not applicable in our implementation.)
540 // Steps 3.a or 4.b.
541 timeZone->setFixedSlot(TimeZoneObject::IDENTIFIER_SLOT,
542 StringValue(identifier));
544 // Step 3.b or 4.c.
545 timeZone->setFixedSlot(TimeZoneObject::OFFSET_MINUTES_SLOT, offsetMinutes);
547 // Step 5.
548 return timeZone;
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);
556 if (!object) {
557 return nullptr;
560 object->setFixedSlot(BuiltinTimeZoneObject::IDENTIFIER_SLOT,
561 StringValue(identifier));
563 object->setFixedSlot(BuiltinTimeZoneObject::OFFSET_MINUTES_SLOT,
564 UndefinedValue());
566 return object;
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));
578 if (!identifier) {
579 return nullptr;
582 auto* object = NewObjectWithGivenProto<BuiltinTimeZoneObject>(cx, nullptr);
583 if (!object) {
584 return nullptr;
587 object->setFixedSlot(BuiltinTimeZoneObject::IDENTIFIER_SLOT,
588 StringValue(identifier));
590 object->setFixedSlot(BuiltinTimeZoneObject::OFFSET_MINUTES_SLOT,
591 Int32Value(offsetMinutes));
593 return object;
597 * CreateTemporalTimeZone ( identifier [ , newTarget ] )
599 static TimeZoneObject* CreateTemporalTimeZone(
600 JSContext* cx, Handle<BuiltinTimeZoneObject*> timeZone) {
601 // Steps 1-2.
602 auto* object = NewBuiltinClassInstance<TimeZoneObject>(cx);
603 if (!object) {
604 return nullptr;
607 // Step 4.a. (Not applicable in our implementation.)
609 // Steps 3.a or 4.b.
610 object->setFixedSlot(
611 TimeZoneObject::IDENTIFIER_SLOT,
612 timeZone->getFixedSlot(BuiltinTimeZoneObject::IDENTIFIER_SLOT));
614 // Step 3.b or 4.c.
615 object->setFixedSlot(
616 TimeZoneObject::OFFSET_MINUTES_SLOT,
617 timeZone->getFixedSlot(BuiltinTimeZoneObject::OFFSET_MINUTES_SLOT));
619 // Step 5.
620 return object;
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)
639 // Steps 4-5.
640 if (string.name()) {
641 // Steps 4.a-c. (Not applicable in our implementation.)
643 // Steps 4.d-e.
644 Rooted<JSString*> timeZoneName(
645 cx, ValidateAndCanonicalizeTimeZoneName(cx, string.name()));
646 if (!timeZoneName) {
647 return false;
650 // Steps 4.f and 5.
651 auto* obj = ::CreateBuiltinTimeZone(cx, timeZoneName);
652 if (!obj) {
653 return false;
656 result.set(TimeZoneValue(obj));
657 return true;
660 // Steps 4.b-c and 8.
661 auto* obj = ::CreateBuiltinTimeZone(cx, string.offset());
662 if (!obj) {
663 return false;
666 result.set(TimeZoneValue(obj));
667 return true;
671 * ObjectImplementsTemporalTimeZoneProtocol ( object )
673 static bool ObjectImplementsTemporalTimeZoneProtocol(JSContext* cx,
674 Handle<JSObject*> object,
675 bool* result) {
676 // Step 1. (Not applicable in our implementation.)
677 MOZ_ASSERT(!object->canUnwrapAs<TimeZoneObject>(),
678 "TimeZone objects handled in the caller");
680 // Step 2.
681 for (auto key : {
682 &JSAtomState::getOffsetNanosecondsFor,
683 &JSAtomState::getPossibleInstantsFor,
684 &JSAtomState::id,
685 }) {
686 // Step 2.a.
687 bool has;
688 if (!HasProperty(cx, object, cx->names().*key, &has)) {
689 return false;
691 if (!has) {
692 *result = false;
693 return true;
697 // Step 3.
698 *result = true;
699 return true;
703 * ToTemporalTimeZoneSlotValue ( temporalTimeZoneLike )
705 bool js::temporal::ToTemporalTimeZone(JSContext* cx,
706 Handle<Value> temporalTimeZoneLike,
707 MutableHandle<TimeZoneValue> result) {
708 // Step 1.
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));
716 return true;
719 // Step 1.a.
720 if (auto* zonedDateTime = obj->maybeUnwrapIf<ZonedDateTimeObject>()) {
721 result.set(zonedDateTime->timeZone());
722 return result.wrap(cx);
725 // Step 1.b.
726 bool implementsTimeZoneProtocol;
727 if (!ObjectImplementsTemporalTimeZoneProtocol(
728 cx, obj, &implementsTimeZoneProtocol)) {
729 return false;
731 if (!implementsTimeZoneProtocol) {
732 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
733 JSMSG_TEMPORAL_INVALID_OBJECT,
734 "Temporal.TimeZone", obj->getClass()->name);
735 return false;
738 // Step 1.c.
739 result.set(TimeZoneValue(obj));
740 return true;
743 // Step 2.
744 if (!timeZoneLike.isString()) {
745 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK,
746 timeZoneLike, nullptr, "not a string");
747 return false;
749 Rooted<JSString*> identifier(cx, timeZoneLike.toString());
751 // Step 3.
752 Rooted<ParsedTimeZone> timeZoneName(cx);
753 if (!ParseTemporalTimeZoneString(cx, identifier, &timeZoneName)) {
754 return false;
757 // Steps 4-8.
758 return ToTemporalTimeZone(cx, timeZoneName, result);
762 * ToTemporalTimeZoneObject ( timeZoneSlotValue )
764 JSObject* js::temporal::ToTemporalTimeZoneObject(
765 JSContext* cx, Handle<TimeZoneValue> timeZone) {
766 // Step 1.
767 if (timeZone.isObject()) {
768 return timeZone.toObject();
771 // Step 2.
772 return CreateTemporalTimeZone(cx, timeZone.toString());
776 * ToTemporalTimeZoneIdentifier ( timeZoneSlotValue )
778 JSString* js::temporal::ToTemporalTimeZoneIdentifier(
779 JSContext* cx, Handle<TimeZoneValue> timeZone) {
780 // Step 1.
781 if (timeZone.isString()) {
782 // Step 1.a. (Not applicable in our implementation.)
784 // Step 1.b.
785 return timeZone.toString()->identifier();
788 // Step 2.
789 Rooted<JSObject*> timeZoneObj(cx, timeZone.toObject());
790 Rooted<Value> identifier(cx);
791 if (!GetProperty(cx, timeZoneObj, timeZoneObj, cx->names().id, &identifier)) {
792 return nullptr;
795 // Step 3.
796 if (!identifier.isString()) {
797 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, identifier,
798 nullptr, "not a string");
799 return nullptr;
802 // Step 4.
803 return identifier.toString();
806 static bool TimeZone_getOffsetNanosecondsFor(JSContext* cx, unsigned argc,
807 Value* vp);
809 static bool TimeZone_getPossibleInstantsFor(JSContext* cx, unsigned argc,
810 Value* vp);
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.)
820 // Steps 2-4.
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);
826 if (!method) {
827 return false;
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
832 // time zone method.
833 if (!object->is<TimeZoneObject>() || !IsNativeFunction(method, native)) {
834 result.set(method);
836 return true;
839 switch (methodName) {
840 // Steps 2 and 4.
841 case TimeZoneMethod::GetOffsetNanosecondsFor:
842 return lookup(cx->names().getOffsetNanosecondsFor,
843 TimeZone_getOffsetNanosecondsFor,
844 timeZone.getOffsetNanosecondsFor());
846 // Steps 3 and 4.
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());
865 // Step 1.
866 result.set(TimeZoneRecord{timeZone});
868 #ifdef DEBUG
869 // Remember the set of looked-up methods for assertions.
870 result.get().lookedUp() += methods;
871 #endif
873 // Built-in time zones don't perform observable lookups.
874 if (timeZone.isString()) {
875 return true;
878 // Step 2.
879 for (auto method : methods) {
880 if (!TimeZoneMethodsRecordLookup(cx, result, method)) {
881 return false;
885 // Step 3.
886 return true;
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())) {
895 return true;
898 // If it's not a built-in time zone, simply wrap the object into the current
899 // compartment.
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());
914 if (!obj) {
915 return false;
918 timeZone.set(obj);
919 return true;
921 MOZ_ASSERT(offsetMinutes.isUndefined());
923 Rooted<JSString*> identifier(cx, unwrappedTimeZone->identifier());
924 if (!cx->compartment()->wrap(cx, &identifier)) {
925 return false;
928 auto* obj = ::CreateBuiltinTimeZone(cx, identifier);
929 if (!obj) {
930 return false;
933 timeZone.set(obj);
934 return true;
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.)
945 // Step 4.
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);
951 return true;
953 MOZ_ASSERT(timeZone->offsetMinutes().isUndefined());
955 // Step 5.
956 int64_t offset;
957 if (!GetNamedTimeZoneOffsetNanoseconds(cx, timeZone, instant, &offset)) {
958 return false;
960 MOZ_ASSERT(std::abs(offset) < ToNanoseconds(TemporalUnit::Day));
962 *offsetNanoseconds = offset;
963 return true;
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)) {
979 return false;
982 // Step 2. (Not applicable)
984 // Step 3.
985 if (!rval.isNumber()) {
986 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, rval,
987 nullptr, "not a number");
988 return false;
991 // Steps 4-6.
992 double num = rval.toNumber();
993 if (!IsInteger(num) || std::abs(num) >= ToNanoseconds(TemporalUnit::Day)) {
994 ToCStringBuf cbuf;
995 const char* numStr = NumberToCString(&cbuf, num);
997 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
998 JSMSG_TEMPORAL_TIMEZONE_NANOS_RANGE, numStr);
999 return false;
1002 // Step 7.
1003 *offsetNanoseconds = int64_t(num);
1004 return true;
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);
1020 if (!unwrapped) {
1021 return false;
1023 auto instant = ToInstant(unwrapped);
1024 auto builtin = timeZone.receiver().toTimeZoneObjectMaybeBuiltin();
1026 return BuiltinGetOffsetNanosecondsFor(cx, builtin, instant,
1027 offsetNanoseconds);
1030 // Steps 1 and 3-7.
1031 return ::GetOffsetNanosecondsForSlow(cx, timeZone, instant,
1032 offsetNanoseconds);
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,
1046 &timeZoneRec)) {
1047 return false;
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,
1068 offsetNanoseconds);
1071 // Steps 1 and 3-7.
1072 Rooted<InstantObject*> obj(cx, CreateTemporalInstant(cx, instant));
1073 if (!obj) {
1074 return false;
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,
1091 &timeZoneRec)) {
1092 return false;
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));
1105 // Step 1.
1106 char sign = offsetNanoseconds >= 0 ? '+' : '-';
1108 // Step 2.
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)
1119 quotient /= 60;
1120 int32_t minute = quotient % 60;
1122 // Step 3.
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];
1130 size_t n = 0;
1132 // Steps 7-8. (Inlined FormatTimeString).
1133 result[n++] = sign;
1134 result[n++] = char('0' + (hour / 10));
1135 result[n++] = char('0' + (hour % 10));
1136 result[n++] = ':';
1137 result[n++] = char('0' + (minute / 10));
1138 result[n++] = char('0' + (minute % 10));
1140 if (second != 0 || subSecondNanoseconds != 0) {
1141 result[n++] = ':';
1142 result[n++] = char('0' + (second / 10));
1143 result[n++] = char('0' + (second % 10));
1145 if (uint32_t fractional = subSecondNanoseconds) {
1146 result[n++] = '.';
1148 uint32_t k = 100'000'000;
1149 do {
1150 result[n++] = char('0' + (fractional / k));
1151 fractional %= k;
1152 k /= 10;
1153 } while (fractional);
1157 MOZ_ASSERT(n <= maxLength);
1159 // Step 9.
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) {
1169 // Step 1.
1170 int64_t offsetNanoseconds;
1171 if (!GetOffsetNanosecondsFor(cx, timeZone, instant, &offsetNanoseconds)) {
1172 return nullptr;
1174 MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
1176 // Step 2.
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) {
1186 // Step 1.
1187 int64_t offsetNanoseconds;
1188 if (!GetOffsetNanosecondsFor(cx, timeZone, instant, &offsetNanoseconds)) {
1189 return nullptr;
1191 MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
1193 // Step 2.
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)
1204 // Step 4.
1205 if (!EqualStrings(cx, one, two, equals)) {
1206 return false;
1208 if (*equals) {
1209 return true;
1212 // Step 5.
1213 Rooted<ParsedTimeZone> timeZoneOne(cx);
1214 if (!ParseTimeZoneIdentifier(cx, one, &timeZoneOne)) {
1215 return false;
1218 // Step 6.
1219 Rooted<ParsedTimeZone> timeZoneTwo(cx);
1220 if (!ParseTimeZoneIdentifier(cx, two, &timeZoneTwo)) {
1221 return false;
1224 // Step 7.
1225 if (timeZoneOne.name() && timeZoneTwo.name()) {
1226 // Step 7.a.
1227 Rooted<JSAtom*> validTimeZoneOne(cx);
1228 if (!IsValidTimeZoneName(cx, timeZoneOne.name(), &validTimeZoneOne)) {
1229 return false;
1231 if (!validTimeZoneOne) {
1232 *equals = false;
1233 return true;
1236 // Step 7.b.
1237 Rooted<JSAtom*> validTimeZoneTwo(cx);
1238 if (!IsValidTimeZoneName(cx, timeZoneTwo.name(), &validTimeZoneTwo)) {
1239 return false;
1241 if (!validTimeZoneTwo) {
1242 *equals = false;
1243 return true;
1246 // Step 7.c and 9.
1247 Rooted<JSString*> canonicalOne(
1248 cx, CanonicalizeTimeZoneName(cx, validTimeZoneOne));
1249 if (!canonicalOne) {
1250 return false;
1253 JSString* canonicalTwo = CanonicalizeTimeZoneName(cx, validTimeZoneTwo);
1254 if (!canonicalTwo) {
1255 return false;
1258 return EqualStrings(cx, canonicalOne, canonicalTwo, equals);
1261 // Step 8.a.
1262 if (!timeZoneOne.name() && !timeZoneTwo.name()) {
1263 *equals = (timeZoneOne.offset() == timeZoneTwo.offset());
1264 return true;
1267 // Step 9.
1268 *equals = false;
1269 return true;
1273 * TimeZoneEquals ( one, two )
1275 bool js::temporal::TimeZoneEquals(JSContext* cx, Handle<TimeZoneValue> one,
1276 Handle<TimeZoneValue> two, bool* equals) {
1277 // Step 1.
1278 if (one.isObject() && two.isObject() && one.toObject() == two.toObject()) {
1279 *equals = true;
1280 return true;
1283 // Step 2.
1284 Rooted<JSString*> timeZoneOne(cx, ToTemporalTimeZoneIdentifier(cx, one));
1285 if (!timeZoneOne) {
1286 return false;
1289 // Step 3.
1290 Rooted<JSString*> timeZoneTwo(cx, ToTemporalTimeZoneIdentifier(cx, two));
1291 if (!timeZoneTwo) {
1292 return false;
1295 // Steps 4-9.
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);
1306 if (result < 0) {
1307 result += 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.
1334 // Step 1.
1335 MOZ_ASSERT(IsValidEpochInstant(instant));
1337 // Step 2.
1338 int32_t remainderNs = instant.nanoseconds % 1'000'000;
1340 // Step 3.
1341 double epochMilliseconds = double(instant.floorToMilliseconds());
1343 // Step 4.
1344 int32_t year = int32_t(JS::YearFromTime(epochMilliseconds));
1346 // Step 5.
1347 int32_t month = int32_t(JS::MonthFromTime(epochMilliseconds)) + 1;
1349 // Step 6.
1350 int32_t day = int32_t(JS::DayFromTime(epochMilliseconds));
1352 // Step 7.
1353 int32_t hour = int32_t(HourFromTime(epochMilliseconds));
1355 // Step 8.
1356 int32_t minute = int32_t(MinFromTime(epochMilliseconds));
1358 // Step 9.
1359 int32_t second = int32_t(SecFromTime(epochMilliseconds));
1361 // Step 10.
1362 int32_t millisecond = int32_t(msFromTime(epochMilliseconds));
1364 // Step 11.
1365 int32_t microsecond = remainderNs / 1000;
1367 // Step 12.
1368 int32_t nanosecond = remainderNs % 1000;
1370 // Step 13.
1371 PlainDateTime result = {
1372 {year, month, day},
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));
1379 return 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;
1394 // Step 1.
1395 auto balancedTime = BalanceTime(time, nanoseconds);
1396 MOZ_ASSERT(std::abs(balancedTime.days) <= 1);
1398 // Step 2.
1399 auto balancedDate =
1400 BalanceISODate(date.year, date.month, date.day + balancedTime.days);
1402 // Step 3.
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.)
1415 // Steps 2-3.
1416 int64_t offsetNanoseconds;
1417 if (!GetOffsetNanosecondsFor(cx, timeZone, instant, &offsetNanoseconds)) {
1418 return nullptr;
1421 // Step 4.
1422 MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
1424 auto* unwrappedInstant = instant.unwrap(cx);
1425 if (!unwrappedInstant) {
1426 return nullptr;
1429 // Steps 5-7.
1430 auto dateTime =
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)
1448 // Step 4.
1449 MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
1451 // TODO: Steps 5-6 can be combined into a single operation to improve perf.
1453 // Step 5.
1454 PlainDateTime dateTime = GetISOPartsFromEpoch(instant);
1456 // Step 6.
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));
1463 // Step 7.
1464 return 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));
1477 // Step 1.
1478 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1479 timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
1481 // Steps 2-3.
1482 int64_t offsetNanoseconds;
1483 if (!GetOffsetNanosecondsFor(cx, timeZone, instant, &offsetNanoseconds)) {
1484 return false;
1487 // Step 4.
1488 MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
1490 // Steps 5-7.
1491 *result = GetPlainDateTimeFor(instant, offsetNanoseconds);
1492 return true;
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,
1508 &timeZoneRec)) {
1509 return false;
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) {
1522 // Steps 1-6.
1523 PlainDateTime dateTime;
1524 if (!GetPlainDateTimeFor(cx, timeZone, instant, &dateTime)) {
1525 return nullptr;
1528 // FIXME: spec issue - CreateTemporalDateTime is infallible
1529 // https://github.com/tc39/proposal-temporal/issues/2523
1530 MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
1532 // Step 7.
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));
1545 // Steps 1-6.
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));
1552 // Step 7.
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)
1566 // Step 4.
1567 if (timeZone->offsetMinutes().isInt32()) {
1568 int32_t offsetMin = timeZone->offsetMinutes().toInt32();
1569 MOZ_ASSERT(std::abs(offsetMin) < UnitsPerDay(TemporalUnit::Minute));
1571 // Step 4.a.
1572 auto epochInstant =
1573 GetUTCEpochNanoseconds(dateTime, InstantSpan::fromMinutes(offsetMin));
1575 // Step 4.b.
1576 possibleInstants.append(epochInstant);
1577 } else {
1578 // Step 5.
1579 if (!GetNamedTimeZoneEpochNanoseconds(cx, timeZone, dateTime,
1580 possibleInstants)) {
1581 return false;
1585 MOZ_ASSERT(possibleInstants.length() <= 2);
1587 // Step 7.b.
1588 for (const auto& epochInstant : possibleInstants) {
1589 if (!IsValidEpochInstant(epochInstant)) {
1590 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1591 JSMSG_TEMPORAL_INSTANT_INVALID);
1592 return false;
1596 // Steps 6-8. (Handled in the caller).
1597 return true;
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)) {
1607 return false;
1610 // Temporal.TimeZone.prototype.getInstantFor, step 7.
1611 for (const auto& possibleInstant : possibleInstants) {
1612 auto* instant = CreateTemporalInstant(cx, possibleInstant);
1613 if (!instant) {
1614 return false;
1617 if (!list.append(instant)) {
1618 return false;
1621 return true;
1625 * GetPossibleInstantsFor ( timeZoneRec, dateTime )
1627 static bool GetPossibleInstantsForSlow(
1628 JSContext* cx, Handle<TimeZoneRecord> timeZone,
1629 Handle<Wrapped<PlainDateTimeObject*>> dateTime,
1630 MutableHandle<InstantVector> list) {
1631 MOZ_ASSERT(list.empty());
1633 // Step 1. (Inlined call to TimeZoneMethodsRecordCall)
1634 Rooted<Value> fval(cx, ObjectValue(*timeZone.getPossibleInstantsFor()));
1635 auto thisv = timeZone.receiver().toObject();
1636 Rooted<Value> arg(cx, ObjectValue(*dateTime));
1637 Rooted<Value> rval(cx);
1638 if (!Call(cx, fval, thisv, arg, &rval)) {
1639 return false;
1642 // Step 2. (Not applicable)
1644 // Step 3.
1645 JS::ForOfIterator iterator(cx);
1646 if (!iterator.init(rval)) {
1647 return false;
1650 // Step 4. (Not applicable in our implementation.)
1652 // Step 5.
1653 auto min = Instant::max();
1654 auto max = Instant::min();
1655 Rooted<Value> nextValue(cx);
1656 while (true) {
1657 // Step 5.a.
1658 bool done;
1659 if (!iterator.next(&nextValue, &done)) {
1660 return false;
1663 // Step 5.b.
1664 if (done) {
1665 // Steps 5.b.i-ii.
1666 if (list.length() > 1) {
1667 // Steps 5.b.ii.1-4. (Not applicable in our implementation.)
1669 // Step 5.b.ii.5.
1670 constexpr auto nsPerDay =
1671 InstantSpan::fromNanoseconds(ToNanoseconds(TemporalUnit::Day));
1672 if ((max - min).abs() > nsPerDay) {
1673 JS_ReportErrorNumberASCII(
1674 cx, GetErrorMessage, nullptr,
1675 JSMSG_TEMPORAL_TIMEZONE_OFFSET_SHIFT_ONE_DAY);
1676 return false;
1680 // Step 5.b.iii.
1681 return true;
1684 // Step 5.d. (Reordered)
1685 if (nextValue.isObject()) {
1686 JSObject* obj = &nextValue.toObject();
1687 if (auto* unwrapped = obj->maybeUnwrapIf<InstantObject>()) {
1688 auto instant = ToInstant(unwrapped);
1689 min = std::min(min, instant);
1690 max = std::max(max, instant);
1692 if (!list.append(obj)) {
1693 return false;
1695 continue;
1699 // Step 5.c.1.
1700 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, nextValue,
1701 nullptr, "not an instant");
1703 // Step 5.c.2.
1704 iterator.closeThrow();
1705 return false;
1710 * GetPossibleInstantsFor ( timeZoneRec, dateTime )
1712 static bool GetPossibleInstantsFor(
1713 JSContext* cx, Handle<TimeZoneRecord> timeZone,
1714 Handle<Wrapped<PlainDateTimeObject*>> dateTimeObj,
1715 const PlainDateTime& dateTime, MutableHandle<InstantVector> list) {
1716 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1717 timeZone, TimeZoneMethod::GetPossibleInstantsFor));
1719 // Step 2. (Reordered)
1720 auto getPossibleInstantsFor = timeZone.getPossibleInstantsFor();
1721 if (!getPossibleInstantsFor) {
1722 bool arrayIterationSane;
1723 if (timeZone.receiver().isString()) {
1724 // "String" time zones don't perform observable array iteration.
1725 arrayIterationSane = true;
1726 } else {
1727 // "Object" time zones need to ensure array iteration is still sane.
1728 if (!IsArrayIterationSane(cx, &arrayIterationSane)) {
1729 return false;
1733 if (arrayIterationSane) {
1734 auto builtin = timeZone.receiver().toTimeZoneObjectMaybeBuiltin();
1735 return BuiltinGetPossibleInstantsFor(cx, builtin, dateTime, list);
1739 // Steps 1 and 3-5.
1740 return GetPossibleInstantsForSlow(cx, timeZone, dateTimeObj, list);
1744 * GetPossibleInstantsFor ( timeZoneRec, dateTime )
1746 bool js::temporal::GetPossibleInstantsFor(
1747 JSContext* cx, Handle<TimeZoneRecord> timeZone,
1748 Handle<PlainDateTimeWithCalendar> dateTime,
1749 MutableHandle<InstantVector> list) {
1750 // Step 2. (Reordered)
1751 auto getPossibleInstantsFor = timeZone.getPossibleInstantsFor();
1752 if (!getPossibleInstantsFor) {
1753 bool arrayIterationSane;
1754 if (timeZone.receiver().isString()) {
1755 // "String" time zones don't perform observable array iteration.
1756 arrayIterationSane = true;
1757 } else {
1758 // "Object" time zones need to ensure array iteration is still sane.
1759 if (!IsArrayIterationSane(cx, &arrayIterationSane)) {
1760 return false;
1764 if (arrayIterationSane) {
1765 auto builtin = timeZone.receiver().toTimeZoneObjectMaybeBuiltin();
1766 return BuiltinGetPossibleInstantsFor(cx, builtin,
1767 ToPlainDateTime(dateTime), list);
1771 Rooted<PlainDateTimeObject*> dateTimeObj(
1772 cx, CreateTemporalDateTime(cx, ToPlainDateTime(dateTime),
1773 dateTime.calendar()));
1774 if (!dateTimeObj) {
1775 return false;
1778 // Steps 1 and 3-5.
1779 return GetPossibleInstantsForSlow(cx, timeZone, dateTimeObj, list);
1783 * AddTime ( hour, minute, second, millisecond, microsecond, nanosecond, hours,
1784 * minutes, seconds, milliseconds, microseconds, nanoseconds )
1786 static auto AddTime(const PlainTime& time, int64_t nanoseconds) {
1787 MOZ_ASSERT(IsValidTime(time));
1788 MOZ_ASSERT(std::abs(nanoseconds) <= ToNanoseconds(TemporalUnit::Day));
1790 // Steps 1-3.
1791 return BalanceTime(time, nanoseconds);
1795 * DisambiguatePossibleInstants ( possibleInstants, timeZoneRec, dateTime,
1796 * disambiguation )
1798 bool js::temporal::DisambiguatePossibleInstants(
1799 JSContext* cx, Handle<InstantVector> possibleInstants,
1800 Handle<TimeZoneRecord> timeZone, const PlainDateTime& dateTime,
1801 TemporalDisambiguation disambiguation,
1802 MutableHandle<Wrapped<InstantObject*>> result) {
1803 // Step 1.
1804 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1805 timeZone, TimeZoneMethod::GetPossibleInstantsFor));
1807 // Step 2.
1808 MOZ_ASSERT_IF(possibleInstants.empty() &&
1809 disambiguation != TemporalDisambiguation::Reject,
1810 TimeZoneMethodsRecordHasLookedUp(
1811 timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
1813 // Steps 3-4.
1814 if (possibleInstants.length() == 1) {
1815 result.set(possibleInstants[0]);
1816 return true;
1819 // Steps 5-6.
1820 if (!possibleInstants.empty()) {
1821 // Step 5.a.
1822 if (disambiguation == TemporalDisambiguation::Earlier ||
1823 disambiguation == TemporalDisambiguation::Compatible) {
1824 result.set(possibleInstants[0]);
1825 return true;
1828 // Step 5.b.
1829 if (disambiguation == TemporalDisambiguation::Later) {
1830 size_t last = possibleInstants.length() - 1;
1831 result.set(possibleInstants[last]);
1832 return true;
1835 // Step 5.c.
1836 MOZ_ASSERT(disambiguation == TemporalDisambiguation::Reject);
1838 // Step 5.d.
1839 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1840 JSMSG_TEMPORAL_TIMEZONE_INSTANT_AMBIGUOUS);
1841 return false;
1844 // Step 7.
1845 if (disambiguation == TemporalDisambiguation::Reject) {
1846 // TODO: Improve error message to say the date was skipped.
1847 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1848 JSMSG_TEMPORAL_TIMEZONE_INSTANT_AMBIGUOUS);
1849 return false;
1852 constexpr auto oneDay =
1853 InstantSpan::fromNanoseconds(ToNanoseconds(TemporalUnit::Day));
1855 // Step 8.
1856 auto epochNanoseconds = GetUTCEpochNanoseconds(dateTime);
1858 // Steps 9 and 11.
1859 auto dayBefore = epochNanoseconds - oneDay;
1861 // Step 10.
1862 if (!IsValidEpochInstant(dayBefore)) {
1863 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1864 JSMSG_TEMPORAL_INSTANT_INVALID);
1865 return false;
1868 // Step 12 and 14.
1869 auto dayAfter = epochNanoseconds + oneDay;
1871 // Step 13.
1872 if (!IsValidEpochInstant(dayAfter)) {
1873 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1874 JSMSG_TEMPORAL_INSTANT_INVALID);
1875 return false;
1878 // Step 15.
1879 int64_t offsetBefore;
1880 if (!GetOffsetNanosecondsFor(cx, timeZone, dayBefore, &offsetBefore)) {
1881 return false;
1883 MOZ_ASSERT(std::abs(offsetBefore) < ToNanoseconds(TemporalUnit::Day));
1885 // Step 16.
1886 int64_t offsetAfter;
1887 if (!GetOffsetNanosecondsFor(cx, timeZone, dayAfter, &offsetAfter)) {
1888 return false;
1890 MOZ_ASSERT(std::abs(offsetAfter) < ToNanoseconds(TemporalUnit::Day));
1892 // Step 17.
1893 int64_t nanoseconds = offsetAfter - offsetBefore;
1895 // Step 18.
1896 if (std::abs(nanoseconds) > ToNanoseconds(TemporalUnit::Day)) {
1897 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1898 JSMSG_TEMPORAL_TIMEZONE_OFFSET_SHIFT_ONE_DAY);
1899 return false;
1902 // Step 19.
1903 if (disambiguation == TemporalDisambiguation::Earlier) {
1904 // Steps 19.a-b.
1905 auto earlierTime = ::AddTime(dateTime.time, -nanoseconds);
1906 MOZ_ASSERT(std::abs(earlierTime.days) <= 1,
1907 "subtracting nanoseconds is at most one day");
1909 // Step 19.c.
1910 PlainDate earlierDate;
1911 if (!AddISODate(cx, dateTime.date, {0, 0, 0, earlierTime.days},
1912 TemporalOverflow::Constrain, &earlierDate)) {
1913 return false;
1916 // Step 19.d.
1917 Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
1918 Rooted<PlainDateTimeWithCalendar> earlierDateTime(
1920 PlainDateTimeWithCalendar{{earlierDate, earlierTime.time}, calendar});
1922 // Step 19.e.
1923 Rooted<InstantVector> earlierInstants(cx, InstantVector(cx));
1924 if (!GetPossibleInstantsFor(cx, timeZone, earlierDateTime,
1925 &earlierInstants)) {
1926 return false;
1929 // Step 19.f.
1930 if (earlierInstants.empty()) {
1931 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1932 JSMSG_TEMPORAL_TIMEZONE_INSTANT_AMBIGUOUS);
1933 return false;
1936 // Step 19.g.
1937 result.set(earlierInstants[0]);
1938 return true;
1941 // Step 20.
1942 MOZ_ASSERT(disambiguation == TemporalDisambiguation::Compatible ||
1943 disambiguation == TemporalDisambiguation::Later);
1945 // Steps 21-22.
1946 auto laterTime = ::AddTime(dateTime.time, nanoseconds);
1947 MOZ_ASSERT(std::abs(laterTime.days) <= 1,
1948 "adding nanoseconds is at most one day");
1950 // Step 23.
1951 PlainDate laterDate;
1952 if (!AddISODate(cx, dateTime.date, {0, 0, 0, laterTime.days},
1953 TemporalOverflow::Constrain, &laterDate)) {
1954 return false;
1957 // Step 24.
1958 Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
1959 Rooted<PlainDateTimeWithCalendar> laterDateTime(
1960 cx, PlainDateTimeWithCalendar{{laterDate, laterTime.time}, calendar});
1962 // Step 25.
1963 Rooted<InstantVector> laterInstants(cx, InstantVector(cx));
1964 if (!GetPossibleInstantsFor(cx, timeZone, laterDateTime, &laterInstants)) {
1965 return false;
1968 // Steps 26-27.
1969 if (laterInstants.empty()) {
1970 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1971 JSMSG_TEMPORAL_TIMEZONE_INSTANT_AMBIGUOUS);
1972 return false;
1975 // Step 28.
1976 size_t last = laterInstants.length() - 1;
1977 result.set(laterInstants[last]);
1978 return true;
1982 * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
1984 static bool GetInstantFor(JSContext* cx, Handle<TimeZoneRecord> timeZone,
1985 Handle<Wrapped<PlainDateTimeObject*>> dateTime,
1986 TemporalDisambiguation disambiguation,
1987 MutableHandle<Wrapped<InstantObject*>> result) {
1988 // Step 1.
1989 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1990 timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
1992 // Step 2.
1993 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1994 timeZone, TimeZoneMethod::GetPossibleInstantsFor));
1996 auto* unwrappedDateTime = dateTime.unwrap(cx);
1997 if (!unwrappedDateTime) {
1998 return false;
2000 auto plainDateTime = ToPlainDateTime(unwrappedDateTime);
2002 // Step 3.
2003 Rooted<InstantVector> possibleInstants(cx, InstantVector(cx));
2004 if (!GetPossibleInstantsFor(cx, timeZone, dateTime, plainDateTime,
2005 &possibleInstants)) {
2006 return false;
2009 // Step 4.
2010 return DisambiguatePossibleInstants(cx, possibleInstants, timeZone,
2011 plainDateTime, disambiguation, result);
2015 * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
2017 static bool GetInstantFor(JSContext* cx, Handle<TimeZoneValue> timeZone,
2018 Handle<Wrapped<PlainDateTimeObject*>> dateTime,
2019 TemporalDisambiguation disambiguation,
2020 MutableHandle<Wrapped<InstantObject*>> result) {
2021 Rooted<TimeZoneRecord> timeZoneRec(cx);
2022 if (!CreateTimeZoneMethodsRecord(cx, timeZone,
2024 TimeZoneMethod::GetOffsetNanosecondsFor,
2025 TimeZoneMethod::GetPossibleInstantsFor,
2027 &timeZoneRec)) {
2028 return false;
2031 return GetInstantFor(cx, timeZoneRec, dateTime, disambiguation, result);
2035 * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
2037 bool js::temporal::GetInstantFor(JSContext* cx, Handle<TimeZoneValue> timeZone,
2038 Handle<PlainDateTimeObject*> dateTime,
2039 TemporalDisambiguation disambiguation,
2040 Instant* result) {
2041 Rooted<TimeZoneRecord> timeZoneRec(cx);
2042 if (!CreateTimeZoneMethodsRecord(cx, timeZone,
2044 TimeZoneMethod::GetOffsetNanosecondsFor,
2045 TimeZoneMethod::GetPossibleInstantsFor,
2047 &timeZoneRec)) {
2048 return false;
2051 Rooted<Wrapped<InstantObject*>> instant(cx);
2052 if (!::GetInstantFor(cx, timeZoneRec, dateTime, disambiguation, &instant)) {
2053 return false;
2056 auto* unwrappedInstant = instant.unwrap(cx);
2057 if (!unwrappedInstant) {
2058 return false;
2061 *result = ToInstant(unwrappedInstant);
2062 return true;
2066 * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
2068 bool js::temporal::GetInstantFor(JSContext* cx, Handle<TimeZoneRecord> timeZone,
2069 Handle<PlainDateTimeWithCalendar> dateTime,
2070 TemporalDisambiguation disambiguation,
2071 Instant* result) {
2072 // Step 1.
2073 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
2074 timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
2076 // Step 2.
2077 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
2078 timeZone, TimeZoneMethod::GetPossibleInstantsFor));
2080 // Step 3.
2081 Rooted<InstantVector> possibleInstants(cx, InstantVector(cx));
2082 if (!GetPossibleInstantsFor(cx, timeZone, dateTime, &possibleInstants)) {
2083 return false;
2086 // Step 4.
2087 Rooted<Wrapped<InstantObject*>> instant(cx);
2088 if (!DisambiguatePossibleInstants(cx, possibleInstants, timeZone,
2089 ToPlainDateTime(dateTime), disambiguation,
2090 &instant)) {
2091 return false;
2094 auto* unwrappedInstant = instant.unwrap(cx);
2095 if (!unwrappedInstant) {
2096 return false;
2099 *result = ToInstant(unwrappedInstant);
2100 return true;
2104 * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
2106 bool js::temporal::GetInstantFor(JSContext* cx, Handle<TimeZoneValue> timeZone,
2107 Handle<PlainDateTimeWithCalendar> dateTime,
2108 TemporalDisambiguation disambiguation,
2109 Instant* result) {
2110 Rooted<TimeZoneRecord> timeZoneRec(cx);
2111 if (!CreateTimeZoneMethodsRecord(cx, timeZone,
2113 TimeZoneMethod::GetOffsetNanosecondsFor,
2114 TimeZoneMethod::GetPossibleInstantsFor,
2116 &timeZoneRec)) {
2117 return false;
2120 return GetInstantFor(cx, timeZoneRec, dateTime, disambiguation, result);
2124 * IsOffsetTimeZoneIdentifier ( offsetString )
2126 * Return true if |offsetString| is the prefix of a time zone offset string.
2127 * Time zone offset strings are be parsed through the |TimeZoneUTCOffsetName|
2128 * production.
2130 * TimeZoneUTCOffsetName :
2131 * UTCOffsetMinutePrecision
2133 * UTCOffsetMinutePrecision :
2134 * Sign Hour[+Padded]
2135 * Sign Hour[+Padded] TimeSeparator[+Extended] MinuteSecond
2136 * Sign Hour[+Padded] TimeSeparator[~Extended] MinuteSecond
2138 * Sign :
2139 * ASCIISign
2140 * U+2212
2142 * ASCIISign : one of + -
2144 * NOTE: IANA time zone identifiers can't start with |Sign|.
2146 static bool IsOffsetTimeZoneIdentifierPrefix(JSLinearString* offsetString) {
2147 // Empty string can't be the prefix of |TimeZoneUTCOffsetName|.
2148 if (offsetString->empty()) {
2149 return false;
2152 // Return true iff |offsetString| starts with |Sign|.
2153 char16_t ch = offsetString->latin1OrTwoByteChar(0);
2154 return ch == '+' || ch == '-' || ch == 0x2212;
2158 * Temporal.TimeZone ( identifier )
2160 static bool TimeZoneConstructor(JSContext* cx, unsigned argc, Value* vp) {
2161 CallArgs args = CallArgsFromVp(argc, vp);
2163 // Step 1.
2164 if (!ThrowIfNotConstructing(cx, args, "Temporal.TimeZone")) {
2165 return false;
2168 // Step 2.
2169 if (!args.requireAtLeast(cx, "Temporal.TimeZone", 1)) {
2170 return false;
2173 if (!args[0].isString()) {
2174 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, args[0],
2175 nullptr, "not a string");
2176 return false;
2179 Rooted<JSLinearString*> identifier(cx, args[0].toString()->ensureLinear(cx));
2180 if (!identifier) {
2181 return false;
2184 Rooted<JSString*> canonical(cx);
2185 Rooted<Value> offsetMinutes(cx);
2186 if (IsOffsetTimeZoneIdentifierPrefix(identifier)) {
2187 // Step 3.
2188 int32_t minutes;
2189 if (!ParseTimeZoneOffsetString(cx, identifier, &minutes)) {
2190 return false;
2192 MOZ_ASSERT(std::abs(minutes) < UnitsPerDay(TemporalUnit::Minute));
2194 canonical = FormatOffsetTimeZoneIdentifier(cx, minutes);
2195 if (!canonical) {
2196 return false;
2199 offsetMinutes.setInt32(minutes);
2200 } else {
2201 // Step 4.
2202 canonical = ValidateAndCanonicalizeTimeZoneName(cx, identifier);
2203 if (!canonical) {
2204 return false;
2207 offsetMinutes.setUndefined();
2210 // Step 5.
2211 auto* timeZone = CreateTemporalTimeZone(cx, args, canonical, offsetMinutes);
2212 if (!timeZone) {
2213 return false;
2216 args.rval().setObject(*timeZone);
2217 return true;
2221 * Temporal.TimeZone.from ( item )
2223 static bool TimeZone_from(JSContext* cx, unsigned argc, Value* vp) {
2224 CallArgs args = CallArgsFromVp(argc, vp);
2226 // Step 1.
2227 Rooted<TimeZoneValue> timeZone(cx);
2228 if (!ToTemporalTimeZone(cx, args.get(0), &timeZone)) {
2229 return false;
2232 // Step 2.
2233 auto* obj = ToTemporalTimeZoneObject(cx, timeZone);
2234 if (!obj) {
2235 return false;
2238 args.rval().setObject(*obj);
2239 return true;
2243 * Temporal.TimeZone.prototype.equals ( timeZoneLike )
2245 static bool TimeZone_equals(JSContext* cx, const CallArgs& args) {
2246 Rooted<TimeZoneValue> timeZone(cx, &args.thisv().toObject());
2248 // Step 3.
2249 Rooted<TimeZoneValue> other(cx);
2250 if (!ToTemporalTimeZone(cx, args.get(0), &other)) {
2251 return false;
2254 // Step 4.
2255 bool equals;
2256 if (!TimeZoneEquals(cx, timeZone, other, &equals)) {
2257 return false;
2260 args.rval().setBoolean(equals);
2261 return true;
2265 * Temporal.TimeZone.prototype.equals ( timeZoneLike )
2267 static bool TimeZone_equals(JSContext* cx, unsigned argc, Value* vp) {
2268 // Steps 1-2.
2269 CallArgs args = CallArgsFromVp(argc, vp);
2270 return CallNonGenericMethod<IsTimeZone, TimeZone_equals>(cx, args);
2274 * Temporal.TimeZone.prototype.getOffsetNanosecondsFor ( instant )
2276 static bool TimeZone_getOffsetNanosecondsFor(JSContext* cx,
2277 const CallArgs& args) {
2278 Rooted<TimeZoneObject*> timeZone(
2279 cx, &args.thisv().toObject().as<TimeZoneObject>());
2281 // Step 3.
2282 Instant instant;
2283 if (!ToTemporalInstant(cx, args.get(0), &instant)) {
2284 return false;
2287 // Steps 4-5.
2288 int64_t offset;
2289 if (!BuiltinGetOffsetNanosecondsFor(cx, timeZone, instant, &offset)) {
2290 return false;
2293 args.rval().setNumber(offset);
2294 return true;
2298 * Temporal.TimeZone.prototype.getOffsetNanosecondsFor ( instant )
2300 static bool TimeZone_getOffsetNanosecondsFor(JSContext* cx, unsigned argc,
2301 Value* vp) {
2302 // Steps 1-2.
2303 CallArgs args = CallArgsFromVp(argc, vp);
2304 return CallNonGenericMethod<IsTimeZone, TimeZone_getOffsetNanosecondsFor>(
2305 cx, args);
2309 * Temporal.TimeZone.prototype.getOffsetStringFor ( instant )
2311 static bool TimeZone_getOffsetStringFor(JSContext* cx, const CallArgs& args) {
2312 Rooted<TimeZoneValue> timeZone(cx, &args.thisv().toObject());
2314 // FIXME: spec issue - CreateTimeZoneMethodsRecord called before
2315 // ToTemporalInstant whereas TimeZone.p.{getPlainDateTimeFor,getInstantFor}
2316 // first convert the input arguments.
2318 // Step 3.
2319 Rooted<TimeZoneRecord> timeZoneRec(cx);
2320 if (!CreateTimeZoneMethodsRecord(cx, timeZone,
2322 TimeZoneMethod::GetOffsetNanosecondsFor,
2324 &timeZoneRec)) {
2325 return false;
2328 // Step 4.
2329 Rooted<Wrapped<InstantObject*>> instant(cx,
2330 ToTemporalInstant(cx, args.get(0)));
2331 if (!instant) {
2332 return false;
2335 // Step 5.
2336 JSString* str = GetOffsetStringFor(cx, timeZoneRec, instant);
2337 if (!str) {
2338 return false;
2341 args.rval().setString(str);
2342 return true;
2346 * Temporal.TimeZone.prototype.getOffsetStringFor ( instant )
2348 static bool TimeZone_getOffsetStringFor(JSContext* cx, unsigned argc,
2349 Value* vp) {
2350 // Steps 1-2.
2351 CallArgs args = CallArgsFromVp(argc, vp);
2352 return CallNonGenericMethod<IsTimeZone, TimeZone_getOffsetStringFor>(cx,
2353 args);
2357 * Temporal.TimeZone.prototype.getPlainDateTimeFor ( instant [, calendarLike ] )
2359 static bool TimeZone_getPlainDateTimeFor(JSContext* cx, const CallArgs& args) {
2360 Rooted<TimeZoneValue> timeZone(cx, &args.thisv().toObject());
2362 // Step 3.
2363 Rooted<Wrapped<InstantObject*>> instant(cx,
2364 ToTemporalInstant(cx, args.get(0)));
2365 if (!instant) {
2366 return false;
2369 // Step 4.
2370 Rooted<CalendarValue> calendar(cx);
2371 if (!ToTemporalCalendarWithISODefault(cx, args.get(1), &calendar)) {
2372 return false;
2375 // Steps 5-6.
2376 auto* result = GetPlainDateTimeFor(cx, timeZone, instant, calendar);
2377 if (!result) {
2378 return false;
2381 args.rval().setObject(*result);
2382 return true;
2386 * Temporal.TimeZone.prototype.getPlainDateTimeFor ( instant [, calendarLike ] )
2388 static bool TimeZone_getPlainDateTimeFor(JSContext* cx, unsigned argc,
2389 Value* vp) {
2390 // Steps 1-2.
2391 CallArgs args = CallArgsFromVp(argc, vp);
2392 return CallNonGenericMethod<IsTimeZone, TimeZone_getPlainDateTimeFor>(cx,
2393 args);
2397 * Temporal.TimeZone.prototype.getInstantFor ( dateTime [ , options ] )
2399 static bool TimeZone_getInstantFor(JSContext* cx, const CallArgs& args) {
2400 Rooted<TimeZoneValue> timeZone(cx, &args.thisv().toObject());
2402 // Step 3.
2403 Rooted<Wrapped<PlainDateTimeObject*>> dateTime(
2404 cx, ToTemporalDateTime(cx, args.get(0)));
2405 if (!dateTime) {
2406 return false;
2409 // Steps 4-5.
2410 auto disambiguation = TemporalDisambiguation::Compatible;
2411 if (args.hasDefined(1)) {
2412 // Step 4.
2413 Rooted<JSObject*> options(
2414 cx, RequireObjectArg(cx, "options", "getInstantFor", args[1]));
2415 if (!options) {
2416 return false;
2419 // Step 5.
2420 if (!ToTemporalDisambiguation(cx, options, &disambiguation)) {
2421 return false;
2425 // Steps 6-7.
2426 Rooted<Wrapped<InstantObject*>> result(cx);
2427 if (!::GetInstantFor(cx, timeZone, dateTime, disambiguation, &result)) {
2428 return false;
2431 args.rval().setObject(*result);
2432 return true;
2436 * Temporal.TimeZone.prototype.getInstantFor ( dateTime [ , options ] )
2438 static bool TimeZone_getInstantFor(JSContext* cx, unsigned argc, Value* vp) {
2439 // Steps 1-2.
2440 CallArgs args = CallArgsFromVp(argc, vp);
2441 return CallNonGenericMethod<IsTimeZone, TimeZone_getInstantFor>(cx, args);
2445 * Temporal.TimeZone.prototype.getPossibleInstantsFor ( dateTime )
2447 static bool TimeZone_getPossibleInstantsFor(JSContext* cx,
2448 const CallArgs& args) {
2449 Rooted<TimeZoneObject*> timeZone(
2450 cx, &args.thisv().toObject().as<TimeZoneObject>());
2452 // Step 3.
2453 PlainDateTime dateTime;
2454 if (!ToTemporalDateTime(cx, args.get(0), &dateTime)) {
2455 return false;
2458 // Steps 4-5.
2459 EpochInstantList possibleInstants;
2460 if (!BuiltinGetPossibleInstantsFor(cx, timeZone, dateTime,
2461 possibleInstants)) {
2462 return false;
2465 // Step 6.
2466 size_t length = possibleInstants.length();
2467 Rooted<ArrayObject*> result(cx, NewDenseFullyAllocatedArray(cx, length));
2468 if (!result) {
2469 return false;
2471 result->ensureDenseInitializedLength(0, length);
2473 // Step 7.
2474 for (size_t i = 0; i < length; i++) {
2475 // Step 7.a. (Already performed in step 4 in our implementation.)
2476 MOZ_ASSERT(IsValidEpochInstant(possibleInstants[i]));
2478 // Step 7.b.
2479 auto* instant = CreateTemporalInstant(cx, possibleInstants[i]);
2480 if (!instant) {
2481 return false;
2484 // Step 7.c.
2485 result->initDenseElement(i, ObjectValue(*instant));
2488 // Step 8.
2489 args.rval().setObject(*result);
2490 return true;
2494 * Temporal.TimeZone.prototype.getPossibleInstantsFor ( dateTime )
2496 static bool TimeZone_getPossibleInstantsFor(JSContext* cx, unsigned argc,
2497 Value* vp) {
2498 // Steps 1-2.
2499 CallArgs args = CallArgsFromVp(argc, vp);
2500 return CallNonGenericMethod<IsTimeZone, TimeZone_getPossibleInstantsFor>(
2501 cx, args);
2505 * Temporal.TimeZone.prototype.getNextTransition ( startingPoint )
2507 static bool TimeZone_getNextTransition(JSContext* cx, const CallArgs& args) {
2508 Rooted<TimeZoneObject*> timeZone(
2509 cx, &args.thisv().toObject().as<TimeZoneObject>());
2511 // Step 3.
2512 Instant startingPoint;
2513 if (!ToTemporalInstant(cx, args.get(0), &startingPoint)) {
2514 return false;
2517 // Step 4.
2518 if (!timeZone->offsetMinutes().isUndefined()) {
2519 args.rval().setNull();
2520 return true;
2523 // Step 5.
2524 mozilla::Maybe<Instant> transition;
2525 if (!GetNamedTimeZoneNextTransition(cx, timeZone, startingPoint,
2526 &transition)) {
2527 return false;
2530 // Step 6.
2531 if (!transition) {
2532 args.rval().setNull();
2533 return true;
2536 // Step 7.
2537 auto* instant = CreateTemporalInstant(cx, *transition);
2538 if (!instant) {
2539 return false;
2542 args.rval().setObject(*instant);
2543 return true;
2547 * Temporal.TimeZone.prototype.getNextTransition ( startingPoint )
2549 static bool TimeZone_getNextTransition(JSContext* cx, unsigned argc,
2550 Value* vp) {
2551 // Steps 1-2.
2552 CallArgs args = CallArgsFromVp(argc, vp);
2553 return CallNonGenericMethod<IsTimeZone, TimeZone_getNextTransition>(cx, args);
2557 * Temporal.TimeZone.prototype.getPreviousTransition ( startingPoint )
2559 static bool TimeZone_getPreviousTransition(JSContext* cx,
2560 const CallArgs& args) {
2561 Rooted<TimeZoneObject*> timeZone(
2562 cx, &args.thisv().toObject().as<TimeZoneObject>());
2564 // Step 3.
2565 Instant startingPoint;
2566 if (!ToTemporalInstant(cx, args.get(0), &startingPoint)) {
2567 return false;
2570 // Step 4.
2571 if (!timeZone->offsetMinutes().isUndefined()) {
2572 args.rval().setNull();
2573 return true;
2576 // Step 5.
2577 mozilla::Maybe<Instant> transition;
2578 if (!GetNamedTimeZonePreviousTransition(cx, timeZone, startingPoint,
2579 &transition)) {
2580 return false;
2583 // Step 6.
2584 if (!transition) {
2585 args.rval().setNull();
2586 return true;
2589 // Step 7.
2590 auto* instant = CreateTemporalInstant(cx, *transition);
2591 if (!instant) {
2592 return false;
2595 args.rval().setObject(*instant);
2596 return true;
2600 * Temporal.TimeZone.prototype.getPreviousTransition ( startingPoint )
2602 static bool TimeZone_getPreviousTransition(JSContext* cx, unsigned argc,
2603 Value* vp) {
2604 // Steps 1-2.
2605 CallArgs args = CallArgsFromVp(argc, vp);
2606 return CallNonGenericMethod<IsTimeZone, TimeZone_getPreviousTransition>(cx,
2607 args);
2611 * Temporal.TimeZone.prototype.toString ( )
2613 static bool TimeZone_toString(JSContext* cx, const CallArgs& args) {
2614 auto* timeZone = &args.thisv().toObject().as<TimeZoneObject>();
2616 // Steps 3-4.
2617 args.rval().setString(timeZone->identifier());
2618 return true;
2622 * Temporal.TimeZone.prototype.toString ( )
2624 static bool TimeZone_toString(JSContext* cx, unsigned argc, Value* vp) {
2625 // Steps 1-2.
2626 CallArgs args = CallArgsFromVp(argc, vp);
2627 return CallNonGenericMethod<IsTimeZone, TimeZone_toString>(cx, args);
2631 * Temporal.TimeZone.prototype.toJSON ( )
2633 static bool TimeZone_toJSON(JSContext* cx, const CallArgs& args) {
2634 auto* timeZone = &args.thisv().toObject().as<TimeZoneObject>();
2636 // Steps 3-4.
2637 args.rval().setString(timeZone->identifier());
2638 return true;
2642 * Temporal.TimeZone.prototype.toJSON ( )
2644 static bool TimeZone_toJSON(JSContext* cx, unsigned argc, Value* vp) {
2645 // Steps 1-2.
2646 CallArgs args = CallArgsFromVp(argc, vp);
2647 return CallNonGenericMethod<IsTimeZone, TimeZone_toJSON>(cx, args);
2651 * get Temporal.TimeZone.prototype.id
2653 static bool TimeZone_id(JSContext* cx, const CallArgs& args) {
2654 auto* timeZone = &args.thisv().toObject().as<TimeZoneObject>();
2656 // Steps 3-4.
2657 args.rval().setString(timeZone->identifier());
2658 return true;
2662 * get Temporal.TimeZone.prototype.id
2664 static bool TimeZone_id(JSContext* cx, unsigned argc, Value* vp) {
2665 // Steps 1-2.
2666 CallArgs args = CallArgsFromVp(argc, vp);
2667 return CallNonGenericMethod<IsTimeZone, TimeZone_id>(cx, args);
2670 void js::temporal::TimeZoneObjectMaybeBuiltin::finalize(JS::GCContext* gcx,
2671 JSObject* obj) {
2672 MOZ_ASSERT(gcx->onMainThread());
2674 if (auto* timeZone = obj->as<TimeZoneObjectMaybeBuiltin>().getTimeZone()) {
2675 intl::RemoveICUCellMemory(gcx, obj, TimeZoneObject::EstimatedMemoryUse);
2676 delete timeZone;
2680 const JSClassOps TimeZoneObject::classOps_ = {
2681 nullptr, // addProperty
2682 nullptr, // delProperty
2683 nullptr, // enumerate
2684 nullptr, // newEnumerate
2685 nullptr, // resolve
2686 nullptr, // mayResolve
2687 TimeZoneObject::finalize, // finalize
2688 nullptr, // call
2689 nullptr, // construct
2690 nullptr, // trace
2693 const JSClass TimeZoneObject::class_ = {
2694 "Temporal.TimeZone",
2695 JSCLASS_HAS_RESERVED_SLOTS(TimeZoneObject::SLOT_COUNT) |
2696 JSCLASS_HAS_CACHED_PROTO(JSProto_TimeZone) |
2697 JSCLASS_FOREGROUND_FINALIZE,
2698 &TimeZoneObject::classOps_,
2699 &TimeZoneObject::classSpec_,
2702 const JSClass& TimeZoneObject::protoClass_ = PlainObject::class_;
2704 static const JSFunctionSpec TimeZone_methods[] = {
2705 JS_FN("from", TimeZone_from, 1, 0),
2706 JS_FS_END,
2709 static const JSFunctionSpec TimeZone_prototype_methods[] = {
2710 JS_FN("equals", TimeZone_equals, 1, 0),
2711 JS_FN("getOffsetNanosecondsFor", TimeZone_getOffsetNanosecondsFor, 1, 0),
2712 JS_FN("getOffsetStringFor", TimeZone_getOffsetStringFor, 1, 0),
2713 JS_FN("getPlainDateTimeFor", TimeZone_getPlainDateTimeFor, 1, 0),
2714 JS_FN("getInstantFor", TimeZone_getInstantFor, 1, 0),
2715 JS_FN("getPossibleInstantsFor", TimeZone_getPossibleInstantsFor, 1, 0),
2716 JS_FN("getNextTransition", TimeZone_getNextTransition, 1, 0),
2717 JS_FN("getPreviousTransition", TimeZone_getPreviousTransition, 1, 0),
2718 JS_FN("toString", TimeZone_toString, 0, 0),
2719 JS_FN("toJSON", TimeZone_toJSON, 0, 0),
2720 JS_FS_END,
2723 static const JSPropertySpec TimeZone_prototype_properties[] = {
2724 JS_PSG("id", TimeZone_id, 0),
2725 JS_STRING_SYM_PS(toStringTag, "Temporal.TimeZone", JSPROP_READONLY),
2726 JS_PS_END,
2729 const ClassSpec TimeZoneObject::classSpec_ = {
2730 GenericCreateConstructor<TimeZoneConstructor, 1, gc::AllocKind::FUNCTION>,
2731 GenericCreatePrototype<TimeZoneObject>,
2732 TimeZone_methods,
2733 nullptr,
2734 TimeZone_prototype_methods,
2735 TimeZone_prototype_properties,
2736 nullptr,
2737 ClassSpec::DontDefineConstructor,
2740 const JSClassOps BuiltinTimeZoneObject::classOps_ = {
2741 nullptr, // addProperty
2742 nullptr, // delProperty
2743 nullptr, // enumerate
2744 nullptr, // newEnumerate
2745 nullptr, // resolve
2746 nullptr, // mayResolve
2747 BuiltinTimeZoneObject::finalize, // finalize
2748 nullptr, // call
2749 nullptr, // construct
2750 nullptr, // trace
2753 const JSClass BuiltinTimeZoneObject::class_ = {
2754 "Temporal.BuiltinTimeZone",
2755 JSCLASS_HAS_RESERVED_SLOTS(BuiltinTimeZoneObject::SLOT_COUNT) |
2756 JSCLASS_FOREGROUND_FINALIZE,
2757 &BuiltinTimeZoneObject::classOps_,