Bug 1874684 - Part 10: Replace BigInt with Int128 in RoundNumberToIncrement. r=mgaudet
[gecko.git] / js / src / builtin / temporal / TimeZone.cpp
blob1a88b2c87d848b57539f24e7d73052935ab70508
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++] = '0' + (hour / 10);
1135 result[n++] = '0' + (hour % 10);
1136 result[n++] = ':';
1137 result[n++] = '0' + (minute / 10);
1138 result[n++] = '0' + (minute % 10);
1140 if (second != 0 || subSecondNanoseconds != 0) {
1141 result[n++] = ':';
1142 result[n++] = '0' + (second / 10);
1143 result[n++] = '0' + (second % 10);
1145 if (uint32_t fractional = subSecondNanoseconds) {
1146 result[n++] = '.';
1148 uint32_t k = 100'000'000;
1149 do {
1150 result[n++] = '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 int64_t epochMilliseconds = instant.floorToMilliseconds();
1343 // Step 4.
1344 int32_t year = JS::YearFromTime(epochMilliseconds);
1346 // Step 5.
1347 int32_t month = JS::MonthFromTime(epochMilliseconds) + 1;
1349 // Step 6.
1350 int32_t day = JS::DayFromTime(epochMilliseconds);
1352 // Step 7.
1353 int32_t hour = HourFromTime(epochMilliseconds);
1355 // Step 8.
1356 int32_t minute = MinFromTime(epochMilliseconds);
1358 // Step 9.
1359 int32_t second = SecFromTime(epochMilliseconds);
1361 // Step 10.
1362 int32_t millisecond = 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 auto& [date, time] = dateTime;
1394 // Step 1.
1395 auto balancedTime = BalanceTime(time, nanoseconds);
1396 MOZ_ASSERT(-1 <= balancedTime.days && 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 // 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)) {
1637 return false;
1640 // Step 2. (Not applicable)
1642 // Step 3.
1643 JS::ForOfIterator iterator(cx);
1644 if (!iterator.init(rval)) {
1645 return false;
1648 // Step 4. (Not applicable in our implementation.)
1650 // Steps 5-6.
1651 Rooted<Value> nextValue(cx);
1652 while (true) {
1653 // Steps 6.a and 6.b.i.
1654 bool done;
1655 if (!iterator.next(&nextValue, &done)) {
1656 return false;
1658 if (done) {
1659 break;
1662 // Steps 6.b.ii.
1663 if (nextValue.isObject()) {
1664 JSObject* obj = &nextValue.toObject();
1665 if (obj->canUnwrapAs<InstantObject>()) {
1666 // Step 6.b.iii.
1667 if (!list.append(obj)) {
1668 return false;
1670 continue;
1674 // Step 6.b.ii.1.
1675 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, nextValue,
1676 nullptr, "not an instant");
1678 // Step 6.b.ii.2.
1679 iterator.closeThrow();
1680 return false;
1683 // Step 7.
1684 return true;
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;
1704 } else {
1705 // "Object" time zones need to ensure array iteration is still sane.
1706 if (!IsArrayIterationSane(cx, &arrayIterationSane)) {
1707 return false;
1711 if (arrayIterationSane) {
1712 auto builtin = timeZone.receiver().toTimeZoneObjectMaybeBuiltin();
1713 return BuiltinGetPossibleInstantsFor(cx, builtin, dateTime, list);
1717 // Steps 1 and 3-7.
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;
1735 } else {
1736 // "Object" time zones need to ensure array iteration is still sane.
1737 if (!IsArrayIterationSane(cx, &arrayIterationSane)) {
1738 return false;
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()));
1752 if (!dateTimeObj) {
1753 return false;
1756 // Steps 1 and 3-7.
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));
1768 // Steps 1-3.
1769 return BalanceTime(time, nanoseconds);
1773 * DisambiguatePossibleInstants ( possibleInstants, timeZoneRec, dateTime,
1774 * disambiguation )
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) {
1781 // Step 1.
1782 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1783 timeZone, TimeZoneMethod::GetPossibleInstantsFor));
1785 // Step 2.
1786 MOZ_ASSERT_IF(possibleInstants.empty() &&
1787 disambiguation != TemporalDisambiguation::Reject,
1788 TimeZoneMethodsRecordHasLookedUp(
1789 timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
1791 // Steps 3-4.
1792 if (possibleInstants.length() == 1) {
1793 result.set(possibleInstants[0]);
1794 return true;
1797 // Steps 5-6.
1798 if (!possibleInstants.empty()) {
1799 // Step 5.a.
1800 if (disambiguation == TemporalDisambiguation::Earlier ||
1801 disambiguation == TemporalDisambiguation::Compatible) {
1802 result.set(possibleInstants[0]);
1803 return true;
1806 // Step 5.b.
1807 if (disambiguation == TemporalDisambiguation::Later) {
1808 size_t last = possibleInstants.length() - 1;
1809 result.set(possibleInstants[last]);
1810 return true;
1813 // Step 5.c.
1814 MOZ_ASSERT(disambiguation == TemporalDisambiguation::Reject);
1816 // Step 5.d.
1817 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1818 JSMSG_TEMPORAL_TIMEZONE_INSTANT_AMBIGUOUS);
1819 return false;
1822 // Step 7.
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);
1827 return false;
1830 constexpr auto oneDay =
1831 InstantSpan::fromNanoseconds(ToNanoseconds(TemporalUnit::Day));
1833 // Step 8.
1834 auto epochNanoseconds = GetUTCEpochNanoseconds(dateTime);
1836 // Steps 9 and 11.
1837 auto dayBefore = epochNanoseconds - oneDay;
1839 // Step 10.
1840 if (!IsValidEpochInstant(dayBefore)) {
1841 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1842 JSMSG_TEMPORAL_INSTANT_INVALID);
1843 return false;
1846 // Step 12 and 14.
1847 auto dayAfter = epochNanoseconds + oneDay;
1849 // Step 13.
1850 if (!IsValidEpochInstant(dayAfter)) {
1851 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1852 JSMSG_TEMPORAL_INSTANT_INVALID);
1853 return false;
1856 // Step 15.
1857 int64_t offsetBefore;
1858 if (!GetOffsetNanosecondsFor(cx, timeZone, dayBefore, &offsetBefore)) {
1859 return false;
1861 MOZ_ASSERT(std::abs(offsetBefore) < ToNanoseconds(TemporalUnit::Day));
1863 // Step 16.
1864 int64_t offsetAfter;
1865 if (!GetOffsetNanosecondsFor(cx, timeZone, dayAfter, &offsetAfter)) {
1866 return false;
1868 MOZ_ASSERT(std::abs(offsetAfter) < ToNanoseconds(TemporalUnit::Day));
1870 // Step 17.
1871 int64_t nanoseconds = offsetAfter - offsetBefore;
1873 // Step 18.
1874 if (disambiguation == TemporalDisambiguation::Earlier) {
1875 // Steps 18.a-b.
1876 auto earlierTime = ::AddTime(dateTime.time, -nanoseconds);
1877 MOZ_ASSERT(std::abs(earlierTime.days) <= 2,
1878 "subtracting nanoseconds is at most two days");
1880 // Step 18.c.
1881 PlainDate earlierDate;
1882 if (!AddISODate(cx, dateTime.date, {0, 0, 0, earlierTime.days},
1883 TemporalOverflow::Constrain, &earlierDate)) {
1884 return false;
1887 // Step 18.d.
1888 Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
1889 Rooted<PlainDateTimeWithCalendar> earlierDateTime(
1891 PlainDateTimeWithCalendar{{earlierDate, earlierTime.time}, calendar});
1893 // Step 18.e.
1894 Rooted<InstantVector> earlierInstants(cx, InstantVector(cx));
1895 if (!GetPossibleInstantsFor(cx, timeZone, earlierDateTime,
1896 &earlierInstants)) {
1897 return false;
1900 // Step 18.f.
1901 if (earlierInstants.empty()) {
1902 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1903 JSMSG_TEMPORAL_TIMEZONE_INSTANT_AMBIGUOUS);
1904 return false;
1907 // Step 18.g.
1908 result.set(earlierInstants[0]);
1909 return true;
1912 // Step 19.
1913 MOZ_ASSERT(disambiguation == TemporalDisambiguation::Compatible ||
1914 disambiguation == TemporalDisambiguation::Later);
1916 // Steps 20-21.
1917 auto laterTime = ::AddTime(dateTime.time, nanoseconds);
1918 MOZ_ASSERT(std::abs(laterTime.days) <= 2,
1919 "adding nanoseconds is at most two days");
1921 // Step 22.
1922 PlainDate laterDate;
1923 if (!AddISODate(cx, dateTime.date, {0, 0, 0, laterTime.days},
1924 TemporalOverflow::Constrain, &laterDate)) {
1925 return false;
1928 // Step 23.
1929 Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
1930 Rooted<PlainDateTimeWithCalendar> laterDateTime(
1931 cx, PlainDateTimeWithCalendar{{laterDate, laterTime.time}, calendar});
1933 // Step 24.
1934 Rooted<InstantVector> laterInstants(cx, InstantVector(cx));
1935 if (!GetPossibleInstantsFor(cx, timeZone, laterDateTime, &laterInstants)) {
1936 return false;
1939 // Steps 25-26.
1940 if (laterInstants.empty()) {
1941 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1942 JSMSG_TEMPORAL_TIMEZONE_INSTANT_AMBIGUOUS);
1943 return false;
1946 // Step 27.
1947 size_t last = laterInstants.length() - 1;
1948 result.set(laterInstants[last]);
1949 return true;
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) {
1959 // Step 1.
1960 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1961 timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
1963 // Step 2.
1964 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1965 timeZone, TimeZoneMethod::GetPossibleInstantsFor));
1967 auto* unwrappedDateTime = dateTime.unwrap(cx);
1968 if (!unwrappedDateTime) {
1969 return false;
1971 auto plainDateTime = ToPlainDateTime(unwrappedDateTime);
1973 // Step 3.
1974 Rooted<InstantVector> possibleInstants(cx, InstantVector(cx));
1975 if (!GetPossibleInstantsFor(cx, timeZone, dateTime, plainDateTime,
1976 &possibleInstants)) {
1977 return false;
1980 // Step 4.
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,
1998 &timeZoneRec)) {
1999 return false;
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,
2011 Instant* result) {
2012 Rooted<TimeZoneRecord> timeZoneRec(cx);
2013 if (!CreateTimeZoneMethodsRecord(cx, timeZone,
2015 TimeZoneMethod::GetOffsetNanosecondsFor,
2016 TimeZoneMethod::GetPossibleInstantsFor,
2018 &timeZoneRec)) {
2019 return false;
2022 Rooted<Wrapped<InstantObject*>> instant(cx);
2023 if (!::GetInstantFor(cx, timeZoneRec, dateTime, disambiguation, &instant)) {
2024 return false;
2027 auto* unwrappedInstant = instant.unwrap(cx);
2028 if (!unwrappedInstant) {
2029 return false;
2032 *result = ToInstant(unwrappedInstant);
2033 return true;
2037 * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
2039 bool js::temporal::GetInstantFor(JSContext* cx, Handle<TimeZoneRecord> timeZone,
2040 Handle<PlainDateTimeWithCalendar> dateTime,
2041 TemporalDisambiguation disambiguation,
2042 Instant* result) {
2043 // Step 1.
2044 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
2045 timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
2047 // Step 2.
2048 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
2049 timeZone, TimeZoneMethod::GetPossibleInstantsFor));
2051 // Step 3.
2052 Rooted<InstantVector> possibleInstants(cx, InstantVector(cx));
2053 if (!GetPossibleInstantsFor(cx, timeZone, dateTime, &possibleInstants)) {
2054 return false;
2057 // Step 4.
2058 Rooted<Wrapped<InstantObject*>> instant(cx);
2059 if (!DisambiguatePossibleInstants(cx, possibleInstants, timeZone,
2060 ToPlainDateTime(dateTime), disambiguation,
2061 &instant)) {
2062 return false;
2065 auto* unwrappedInstant = instant.unwrap(cx);
2066 if (!unwrappedInstant) {
2067 return false;
2070 *result = ToInstant(unwrappedInstant);
2071 return true;
2075 * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
2077 bool js::temporal::GetInstantFor(JSContext* cx, Handle<TimeZoneValue> timeZone,
2078 Handle<PlainDateTimeWithCalendar> dateTime,
2079 TemporalDisambiguation disambiguation,
2080 Instant* result) {
2081 Rooted<TimeZoneRecord> timeZoneRec(cx);
2082 if (!CreateTimeZoneMethodsRecord(cx, timeZone,
2084 TimeZoneMethod::GetOffsetNanosecondsFor,
2085 TimeZoneMethod::GetPossibleInstantsFor,
2087 &timeZoneRec)) {
2088 return false;
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|
2099 * production.
2101 * TimeZoneUTCOffsetName :
2102 * UTCOffsetMinutePrecision
2104 * UTCOffsetMinutePrecision :
2105 * Sign Hour[+Padded]
2106 * Sign Hour[+Padded] TimeSeparator[+Extended] MinuteSecond
2107 * Sign Hour[+Padded] TimeSeparator[~Extended] MinuteSecond
2109 * Sign :
2110 * ASCIISign
2111 * U+2212
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()) {
2120 return false;
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);
2134 // Step 1.
2135 if (!ThrowIfNotConstructing(cx, args, "Temporal.TimeZone")) {
2136 return false;
2139 // Step 2.
2140 if (!args.requireAtLeast(cx, "Temporal.TimeZone", 1)) {
2141 return false;
2144 if (!args[0].isString()) {
2145 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, args[0],
2146 nullptr, "not a string");
2147 return false;
2150 Rooted<JSLinearString*> identifier(cx, args[0].toString()->ensureLinear(cx));
2151 if (!identifier) {
2152 return false;
2155 Rooted<JSString*> canonical(cx);
2156 Rooted<Value> offsetMinutes(cx);
2157 if (IsOffsetTimeZoneIdentifierPrefix(identifier)) {
2158 // Step 3.
2159 int32_t minutes;
2160 if (!ParseTimeZoneOffsetString(cx, identifier, &minutes)) {
2161 return false;
2163 MOZ_ASSERT(std::abs(minutes) < UnitsPerDay(TemporalUnit::Minute));
2165 canonical = FormatOffsetTimeZoneIdentifier(cx, minutes);
2166 if (!canonical) {
2167 return false;
2170 offsetMinutes.setInt32(minutes);
2171 } else {
2172 // Step 4.
2173 canonical = ValidateAndCanonicalizeTimeZoneName(cx, identifier);
2174 if (!canonical) {
2175 return false;
2178 offsetMinutes.setUndefined();
2181 // Step 5.
2182 auto* timeZone = CreateTemporalTimeZone(cx, args, canonical, offsetMinutes);
2183 if (!timeZone) {
2184 return false;
2187 args.rval().setObject(*timeZone);
2188 return true;
2192 * Temporal.TimeZone.from ( item )
2194 static bool TimeZone_from(JSContext* cx, unsigned argc, Value* vp) {
2195 CallArgs args = CallArgsFromVp(argc, vp);
2197 // Step 1.
2198 Rooted<TimeZoneValue> timeZone(cx);
2199 if (!ToTemporalTimeZone(cx, args.get(0), &timeZone)) {
2200 return false;
2203 // Step 2.
2204 auto* obj = ToTemporalTimeZoneObject(cx, timeZone);
2205 if (!obj) {
2206 return false;
2209 args.rval().setObject(*obj);
2210 return true;
2214 * Temporal.TimeZone.prototype.equals ( timeZoneLike )
2216 static bool TimeZone_equals(JSContext* cx, const CallArgs& args) {
2217 Rooted<TimeZoneValue> timeZone(cx, &args.thisv().toObject());
2219 // Step 3.
2220 Rooted<TimeZoneValue> other(cx);
2221 if (!ToTemporalTimeZone(cx, args.get(0), &other)) {
2222 return false;
2225 // Step 4.
2226 bool equals;
2227 if (!TimeZoneEquals(cx, timeZone, other, &equals)) {
2228 return false;
2231 args.rval().setBoolean(equals);
2232 return true;
2236 * Temporal.TimeZone.prototype.equals ( timeZoneLike )
2238 static bool TimeZone_equals(JSContext* cx, unsigned argc, Value* vp) {
2239 // Steps 1-2.
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>());
2252 // Step 3.
2253 Instant instant;
2254 if (!ToTemporalInstant(cx, args.get(0), &instant)) {
2255 return false;
2258 // Steps 4-5.
2259 int64_t offset;
2260 if (!BuiltinGetOffsetNanosecondsFor(cx, timeZone, instant, &offset)) {
2261 return false;
2264 args.rval().setNumber(offset);
2265 return true;
2269 * Temporal.TimeZone.prototype.getOffsetNanosecondsFor ( instant )
2271 static bool TimeZone_getOffsetNanosecondsFor(JSContext* cx, unsigned argc,
2272 Value* vp) {
2273 // Steps 1-2.
2274 CallArgs args = CallArgsFromVp(argc, vp);
2275 return CallNonGenericMethod<IsTimeZone, TimeZone_getOffsetNanosecondsFor>(
2276 cx, args);
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.
2289 // Step 3.
2290 Rooted<TimeZoneRecord> timeZoneRec(cx);
2291 if (!CreateTimeZoneMethodsRecord(cx, timeZone,
2293 TimeZoneMethod::GetOffsetNanosecondsFor,
2295 &timeZoneRec)) {
2296 return false;
2299 // Step 4.
2300 Rooted<Wrapped<InstantObject*>> instant(cx,
2301 ToTemporalInstant(cx, args.get(0)));
2302 if (!instant) {
2303 return false;
2306 // Step 5.
2307 JSString* str = GetOffsetStringFor(cx, timeZoneRec, instant);
2308 if (!str) {
2309 return false;
2312 args.rval().setString(str);
2313 return true;
2317 * Temporal.TimeZone.prototype.getOffsetStringFor ( instant )
2319 static bool TimeZone_getOffsetStringFor(JSContext* cx, unsigned argc,
2320 Value* vp) {
2321 // Steps 1-2.
2322 CallArgs args = CallArgsFromVp(argc, vp);
2323 return CallNonGenericMethod<IsTimeZone, TimeZone_getOffsetStringFor>(cx,
2324 args);
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());
2333 // Step 3.
2334 Rooted<Wrapped<InstantObject*>> instant(cx,
2335 ToTemporalInstant(cx, args.get(0)));
2336 if (!instant) {
2337 return false;
2340 // Step 4.
2341 Rooted<CalendarValue> calendar(cx);
2342 if (!ToTemporalCalendarWithISODefault(cx, args.get(1), &calendar)) {
2343 return false;
2346 // Steps 5-6.
2347 auto* result = GetPlainDateTimeFor(cx, timeZone, instant, calendar);
2348 if (!result) {
2349 return false;
2352 args.rval().setObject(*result);
2353 return true;
2357 * Temporal.TimeZone.prototype.getPlainDateTimeFor ( instant [, calendarLike ] )
2359 static bool TimeZone_getPlainDateTimeFor(JSContext* cx, unsigned argc,
2360 Value* vp) {
2361 // Steps 1-2.
2362 CallArgs args = CallArgsFromVp(argc, vp);
2363 return CallNonGenericMethod<IsTimeZone, TimeZone_getPlainDateTimeFor>(cx,
2364 args);
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());
2373 // Step 3.
2374 Rooted<Wrapped<PlainDateTimeObject*>> dateTime(
2375 cx, ToTemporalDateTime(cx, args.get(0)));
2376 if (!dateTime) {
2377 return false;
2380 // Steps 4-5.
2381 auto disambiguation = TemporalDisambiguation::Compatible;
2382 if (args.hasDefined(1)) {
2383 // Step 4.
2384 Rooted<JSObject*> options(
2385 cx, RequireObjectArg(cx, "options", "getInstantFor", args[1]));
2386 if (!options) {
2387 return false;
2390 // Step 5.
2391 if (!ToTemporalDisambiguation(cx, options, &disambiguation)) {
2392 return false;
2396 // Steps 6-7.
2397 Rooted<Wrapped<InstantObject*>> result(cx);
2398 if (!::GetInstantFor(cx, timeZone, dateTime, disambiguation, &result)) {
2399 return false;
2402 args.rval().setObject(*result);
2403 return true;
2407 * Temporal.TimeZone.prototype.getInstantFor ( dateTime [ , options ] )
2409 static bool TimeZone_getInstantFor(JSContext* cx, unsigned argc, Value* vp) {
2410 // Steps 1-2.
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>());
2423 // Step 3.
2424 PlainDateTime dateTime;
2425 if (!ToTemporalDateTime(cx, args.get(0), &dateTime)) {
2426 return false;
2429 // Steps 4-5.
2430 EpochInstantList possibleInstants;
2431 if (!BuiltinGetPossibleInstantsFor(cx, timeZone, dateTime,
2432 possibleInstants)) {
2433 return false;
2436 // Step 6.
2437 size_t length = possibleInstants.length();
2438 Rooted<ArrayObject*> result(cx, NewDenseFullyAllocatedArray(cx, length));
2439 if (!result) {
2440 return false;
2442 result->ensureDenseInitializedLength(0, length);
2444 // Step 7.
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]));
2449 // Step 7.b.
2450 auto* instant = CreateTemporalInstant(cx, possibleInstants[i]);
2451 if (!instant) {
2452 return false;
2455 // Step 7.c.
2456 result->initDenseElement(i, ObjectValue(*instant));
2459 // Step 8.
2460 args.rval().setObject(*result);
2461 return true;
2465 * Temporal.TimeZone.prototype.getPossibleInstantsFor ( dateTime )
2467 static bool TimeZone_getPossibleInstantsFor(JSContext* cx, unsigned argc,
2468 Value* vp) {
2469 // Steps 1-2.
2470 CallArgs args = CallArgsFromVp(argc, vp);
2471 return CallNonGenericMethod<IsTimeZone, TimeZone_getPossibleInstantsFor>(
2472 cx, args);
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>());
2482 // Step 3.
2483 Instant startingPoint;
2484 if (!ToTemporalInstant(cx, args.get(0), &startingPoint)) {
2485 return false;
2488 // Step 4.
2489 if (!timeZone->offsetMinutes().isUndefined()) {
2490 args.rval().setNull();
2491 return true;
2494 // Step 5.
2495 mozilla::Maybe<Instant> transition;
2496 if (!GetNamedTimeZoneNextTransition(cx, timeZone, startingPoint,
2497 &transition)) {
2498 return false;
2501 // Step 6.
2502 if (!transition) {
2503 args.rval().setNull();
2504 return true;
2507 // Step 7.
2508 auto* instant = CreateTemporalInstant(cx, *transition);
2509 if (!instant) {
2510 return false;
2513 args.rval().setObject(*instant);
2514 return true;
2518 * Temporal.TimeZone.prototype.getNextTransition ( startingPoint )
2520 static bool TimeZone_getNextTransition(JSContext* cx, unsigned argc,
2521 Value* vp) {
2522 // Steps 1-2.
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>());
2535 // Step 3.
2536 Instant startingPoint;
2537 if (!ToTemporalInstant(cx, args.get(0), &startingPoint)) {
2538 return false;
2541 // Step 4.
2542 if (!timeZone->offsetMinutes().isUndefined()) {
2543 args.rval().setNull();
2544 return true;
2547 // Step 5.
2548 mozilla::Maybe<Instant> transition;
2549 if (!GetNamedTimeZonePreviousTransition(cx, timeZone, startingPoint,
2550 &transition)) {
2551 return false;
2554 // Step 6.
2555 if (!transition) {
2556 args.rval().setNull();
2557 return true;
2560 // Step 7.
2561 auto* instant = CreateTemporalInstant(cx, *transition);
2562 if (!instant) {
2563 return false;
2566 args.rval().setObject(*instant);
2567 return true;
2571 * Temporal.TimeZone.prototype.getPreviousTransition ( startingPoint )
2573 static bool TimeZone_getPreviousTransition(JSContext* cx, unsigned argc,
2574 Value* vp) {
2575 // Steps 1-2.
2576 CallArgs args = CallArgsFromVp(argc, vp);
2577 return CallNonGenericMethod<IsTimeZone, TimeZone_getPreviousTransition>(cx,
2578 args);
2582 * Temporal.TimeZone.prototype.toString ( )
2584 static bool TimeZone_toString(JSContext* cx, const CallArgs& args) {
2585 auto* timeZone = &args.thisv().toObject().as<TimeZoneObject>();
2587 // Steps 3-4.
2588 args.rval().setString(timeZone->identifier());
2589 return true;
2593 * Temporal.TimeZone.prototype.toString ( )
2595 static bool TimeZone_toString(JSContext* cx, unsigned argc, Value* vp) {
2596 // Steps 1-2.
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>();
2607 // Steps 3-4.
2608 args.rval().setString(timeZone->identifier());
2609 return true;
2613 * Temporal.TimeZone.prototype.toJSON ( )
2615 static bool TimeZone_toJSON(JSContext* cx, unsigned argc, Value* vp) {
2616 // Steps 1-2.
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>();
2627 // Steps 3-4.
2628 args.rval().setString(timeZone->identifier());
2629 return true;
2633 * get Temporal.TimeZone.prototype.id
2635 static bool TimeZone_id(JSContext* cx, unsigned argc, Value* vp) {
2636 // Steps 1-2.
2637 CallArgs args = CallArgsFromVp(argc, vp);
2638 return CallNonGenericMethod<IsTimeZone, TimeZone_id>(cx, args);
2641 void js::temporal::TimeZoneObjectMaybeBuiltin::finalize(JS::GCContext* gcx,
2642 JSObject* obj) {
2643 MOZ_ASSERT(gcx->onMainThread());
2645 if (auto* timeZone = obj->as<TimeZoneObjectMaybeBuiltin>().getTimeZone()) {
2646 intl::RemoveICUCellMemory(gcx, obj, TimeZoneObject::EstimatedMemoryUse);
2647 delete timeZone;
2651 const JSClassOps TimeZoneObject::classOps_ = {
2652 nullptr, // addProperty
2653 nullptr, // delProperty
2654 nullptr, // enumerate
2655 nullptr, // newEnumerate
2656 nullptr, // resolve
2657 nullptr, // mayResolve
2658 TimeZoneObject::finalize, // finalize
2659 nullptr, // call
2660 nullptr, // construct
2661 nullptr, // trace
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),
2677 JS_FS_END,
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),
2691 JS_FS_END,
2694 static const JSPropertySpec TimeZone_prototype_properties[] = {
2695 JS_PSG("id", TimeZone_id, 0),
2696 JS_STRING_SYM_PS(toStringTag, "Temporal.TimeZone", JSPROP_READONLY),
2697 JS_PS_END,
2700 const ClassSpec TimeZoneObject::classSpec_ = {
2701 GenericCreateConstructor<TimeZoneConstructor, 1, gc::AllocKind::FUNCTION>,
2702 GenericCreatePrototype<TimeZoneObject>,
2703 TimeZone_methods,
2704 nullptr,
2705 TimeZone_prototype_methods,
2706 TimeZone_prototype_properties,
2707 nullptr,
2708 ClassSpec::DontDefineConstructor,
2711 const JSClassOps BuiltinTimeZoneObject::classOps_ = {
2712 nullptr, // addProperty
2713 nullptr, // delProperty
2714 nullptr, // enumerate
2715 nullptr, // newEnumerate
2716 nullptr, // resolve
2717 nullptr, // mayResolve
2718 BuiltinTimeZoneObject::finalize, // finalize
2719 nullptr, // call
2720 nullptr, // construct
2721 nullptr, // trace
2724 const JSClass BuiltinTimeZoneObject::class_ = {
2725 "Temporal.BuiltinTimeZone",
2726 JSCLASS_HAS_RESERVED_SLOTS(BuiltinTimeZoneObject::SLOT_COUNT) |
2727 JSCLASS_FOREGROUND_FINALIZE,
2728 &BuiltinTimeZoneObject::classOps_,