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 #ifndef builtin_temporal_TimeZone_h
8 #define builtin_temporal_TimeZone_h
10 #include "mozilla/Assertions.h"
11 #include "mozilla/EnumSet.h"
16 #include "builtin/temporal/Wrapped.h"
17 #include "js/GCVector.h"
18 #include "js/RootingAPI.h"
19 #include "js/TypeDecls.h"
21 #include "vm/JSObject.h"
22 #include "vm/NativeObject.h"
25 class JS_PUBLIC_API JSTracer
;
32 namespace mozilla::intl
{
36 namespace js::temporal
{
38 class TimeZoneObjectMaybeBuiltin
: public NativeObject
{
40 static constexpr uint32_t IDENTIFIER_SLOT
= 0;
41 static constexpr uint32_t OFFSET_MINUTES_SLOT
= 1;
42 static constexpr uint32_t INTL_TIMEZONE_SLOT
= 2;
43 static constexpr uint32_t SLOT_COUNT
= 3;
45 // Estimated memory use for intl::TimeZone (see IcuMemoryUsage).
46 static constexpr size_t EstimatedMemoryUse
= 6840;
48 JSString
* identifier() const {
49 return getFixedSlot(IDENTIFIER_SLOT
).toString();
52 const auto& offsetMinutes() const {
53 return getFixedSlot(OFFSET_MINUTES_SLOT
);
56 mozilla::intl::TimeZone
* getTimeZone() const {
57 const auto& slot
= getFixedSlot(INTL_TIMEZONE_SLOT
);
58 if (slot
.isUndefined()) {
61 return static_cast<mozilla::intl::TimeZone
*>(slot
.toPrivate());
64 void setTimeZone(mozilla::intl::TimeZone
* timeZone
) {
65 setFixedSlot(INTL_TIMEZONE_SLOT
, JS::PrivateValue(timeZone
));
69 static void finalize(JS::GCContext
* gcx
, JSObject
* obj
);
72 class TimeZoneObject
: public TimeZoneObjectMaybeBuiltin
{
74 static const JSClass class_
;
75 static const JSClass
& protoClass_
;
78 static const JSClassOps classOps_
;
79 static const ClassSpec classSpec_
;
82 class BuiltinTimeZoneObject
: public TimeZoneObjectMaybeBuiltin
{
84 static const JSClass class_
;
87 static const JSClassOps classOps_
;
90 } /* namespace js::temporal */
93 inline bool JSObject::is
<js::temporal::TimeZoneObjectMaybeBuiltin
>() const {
94 return is
<js::temporal::TimeZoneObject
>() ||
95 is
<js::temporal::BuiltinTimeZoneObject
>();
98 namespace js::temporal
{
101 * Temporal time zones can be either objects or strings. Objects are either
102 * instances of `Temporal.TimeZone` or user-defined time zones. Strings are
103 * either canonical time zone identifiers or time zone offset strings.
105 * Examples of valid Temporal time zones:
108 * - "America/New_York"
111 * Examples of invalid Temporal time zones:
113 * - "utc" (wrong case)
114 * - "Etc/UTC" (canonical name is "UTC")
115 * - "+00" (missing minutes part)
116 * - "+00:00:00" (sub-minute precision)
117 * - "+00:00:01" (sub-minute precision)
118 * - "-00:00" (wrong sign for zero offset)
120 * String-valued Temporal time zones are an optimization to avoid allocating
121 * `Temporal.TimeZone` objects when creating `Temporal.ZonedDateTime` objects.
122 * For example `Temporal.ZonedDateTime.from("1970-01-01[UTC]")` doesn't require
123 * to allocate a fresh `Temporal.TimeZone` object for the "UTC" time zone.
125 * The specification creates new `Temporal.TimeZone` objects whenever any
126 * operation is performed on a string-valued Temporal time zone. This newly
127 * created object can't be accessed by the user and implementations are expected
128 * to optimize away the allocation.
130 * The following two implementation approaches are possible:
132 * 1. Represent string-valued time zones as JSStrings. Additionally keep a
133 * mapping from JSString to `mozilla::intl::TimeZone` to avoid repeatedly
134 * creating new `mozilla::intl::TimeZone` for time zone operations. Offset
135 * string time zones have to be special cased, because they don't use
136 * `mozilla::intl::TimeZone`. Either detect offset strings by checking the
137 * time zone identifier or store offset strings as the offset in minutes
138 * value to avoid reparsing the offset string again and again.
139 * 2. Represent string-valued time zones as `Temporal.TimeZone`-like objects.
140 * These internal `Temporal.TimeZone`-like objects must not be exposed to
143 * Option 2 is a bit easier to implement, so we use this approach for now.
145 class TimeZoneValue final
{
146 JSObject
* object_
= nullptr;
150 * Default initialize this TimeZoneValue.
152 TimeZoneValue() = default;
155 * Initialize this TimeZoneValue with a "string" time zone object.
157 explicit TimeZoneValue(BuiltinTimeZoneObject
* timeZone
) : object_(timeZone
) {
158 MOZ_ASSERT(isString());
162 * Initialize this TimeZoneValue with an "object" time zone object.
164 explicit TimeZoneValue(JSObject
* timeZone
) : object_(timeZone
) {
165 MOZ_ASSERT(isObject());
169 * Initialize this TimeZoneValue from a slot Value, which must be either a
170 * "string" or "object" time zone object.
172 explicit TimeZoneValue(const JS::Value
& value
) : object_(&value
.toObject()) {}
175 * Return true if this TimeZoneValue is not null.
177 explicit operator bool() const { return !!object_
; }
180 * Return true if this TimeZoneValue is a "string" time zone.
182 bool isString() const {
183 return object_
&& object_
->is
<BuiltinTimeZoneObject
>();
187 * Return true if this TimeZoneValue is an "object" time zone.
189 bool isObject() const { return object_
&& !isString(); }
192 * Return true if this TimeZoneValue holds a TimeZoneObjectMaybeBuiltin.
194 bool isTimeZoneObjectMaybeBuiltin() const {
195 return object_
&& object_
->is
<TimeZoneObjectMaybeBuiltin
>();
199 * Return this "string" time zone.
201 auto* toString() const {
202 MOZ_ASSERT(isString());
203 return &object_
->as
<BuiltinTimeZoneObject
>();
207 * Return this "object" time zone.
209 JSObject
* toObject() const {
210 MOZ_ASSERT(isObject());
215 * Return the underlying object as a TimeZoneObjectMaybeBuiltin.
217 auto* toTimeZoneObjectMaybeBuiltin() const {
218 MOZ_ASSERT(isTimeZoneObjectMaybeBuiltin());
219 return &object_
->as
<TimeZoneObjectMaybeBuiltin
>();
223 * Return the Value representation of this TimeZoneValue.
225 JS::Value
toValue() const {
227 return JS::StringValue(toString()->identifier());
231 return JS::ObjectValue(*object_
);
235 * Return the slot Value representation of this TimeZoneValue.
237 JS::Value
toSlotValue() const {
239 return JS::ObjectValue(*object_
);
242 // Helper methods for (Mutable)WrappedPtrOperations.
243 auto address() { return &object_
; }
244 auto address() const { return &object_
; }
246 // Trace implementation.
247 void trace(JSTracer
* trc
);
250 enum class TimeZoneMethod
{
251 GetOffsetNanosecondsFor
,
252 GetPossibleInstantsFor
,
255 class TimeZoneRecord
{
256 TimeZoneValue receiver_
;
258 // Null unless non-builtin time zone methods are used.
259 JSObject
* getOffsetNanosecondsFor_
= nullptr;
260 JSObject
* getPossibleInstantsFor_
= nullptr;
263 mozilla::EnumSet
<TimeZoneMethod
> lookedUp_
{};
268 * Default initialize this TimeZoneRecord.
270 TimeZoneRecord() = default;
272 explicit TimeZoneRecord(const TimeZoneValue
& receiver
)
273 : receiver_(receiver
) {}
275 const auto& receiver() const { return receiver_
; }
276 auto* getOffsetNanosecondsFor() const { return getOffsetNanosecondsFor_
; }
277 auto* getPossibleInstantsFor() const { return getPossibleInstantsFor_
; }
280 auto& lookedUp() const { return lookedUp_
; }
281 auto& lookedUp() { return lookedUp_
; }
284 // Helper methods for (Mutable)WrappedPtrOperations.
285 auto* receiverDoNotUse() const { return &receiver_
; }
286 auto* getOffsetNanosecondsForDoNotUse() const {
287 return &getOffsetNanosecondsFor_
;
289 auto* getOffsetNanosecondsForDoNotUse() { return &getOffsetNanosecondsFor_
; }
290 auto* getPossibleInstantsForDoNotUse() const {
291 return &getPossibleInstantsFor_
;
293 auto* getPossibleInstantsForDoNotUse() { return &getPossibleInstantsFor_
; }
295 // Trace implementation.
296 void trace(JSTracer
* trc
);
300 struct ParsedTimeZone
;
301 struct PlainDateTime
;
304 class PlainDateTimeObject
;
305 class PlainDateTimeWithCalendar
;
306 enum class TemporalDisambiguation
;
309 * IsValidTimeZoneName ( timeZone )
310 * IsAvailableTimeZoneName ( timeZone )
312 bool IsValidTimeZoneName(JSContext
* cx
, JS::Handle
<JSString
*> timeZone
,
313 JS::MutableHandle
<JSAtom
*> validatedTimeZone
);
316 * CanonicalizeTimeZoneName ( timeZone )
318 JSString
* CanonicalizeTimeZoneName(JSContext
* cx
,
319 JS::Handle
<JSLinearString
*> timeZone
);
322 * IsValidTimeZoneName ( timeZone )
323 * IsAvailableTimeZoneName ( timeZone )
324 * CanonicalizeTimeZoneName ( timeZone )
326 JSString
* ValidateAndCanonicalizeTimeZoneName(JSContext
* cx
,
327 JS::Handle
<JSString
*> timeZone
);
330 * CreateTemporalTimeZone ( identifier [ , newTarget ] )
332 BuiltinTimeZoneObject
* CreateTemporalTimeZone(JSContext
* cx
,
333 JS::Handle
<JSString
*> identifier
);
336 * ToTemporalTimeZoneSlotValue ( temporalTimeZoneLike )
338 bool ToTemporalTimeZone(JSContext
* cx
,
339 JS::Handle
<JS::Value
> temporalTimeZoneLike
,
340 JS::MutableHandle
<TimeZoneValue
> result
);
343 * ToTemporalTimeZoneSlotValue ( temporalTimeZoneLike )
345 bool ToTemporalTimeZone(JSContext
* cx
, JS::Handle
<ParsedTimeZone
> string
,
346 JS::MutableHandle
<TimeZoneValue
> result
);
349 * ToTemporalTimeZoneObject ( timeZoneSlotValue )
351 JSObject
* ToTemporalTimeZoneObject(JSContext
* cx
,
352 JS::Handle
<TimeZoneValue
> timeZone
);
355 * ToTemporalTimeZoneIdentifier ( timeZoneSlotValue )
357 JSString
* ToTemporalTimeZoneIdentifier(JSContext
* cx
,
358 JS::Handle
<TimeZoneValue
> timeZone
);
361 * TimeZoneEquals ( one, two )
363 bool TimeZoneEquals(JSContext
* cx
, JS::Handle
<JSString
*> one
,
364 JS::Handle
<JSString
*> two
, bool* equals
);
367 * TimeZoneEquals ( one, two )
369 bool TimeZoneEquals(JSContext
* cx
, JS::Handle
<TimeZoneValue
> one
,
370 JS::Handle
<TimeZoneValue
> two
, bool* equals
);
373 * GetPlainDateTimeFor ( timeZoneRec, instant, calendar [ ,
374 * precalculatedOffsetNanoseconds ] )
376 PlainDateTimeObject
* GetPlainDateTimeFor(JSContext
* cx
,
377 JS::Handle
<TimeZoneValue
> timeZone
,
378 const Instant
& instant
,
379 JS::Handle
<CalendarValue
> calendar
);
382 * GetPlainDateTimeFor ( timeZoneRec, instant, calendar [ ,
383 * precalculatedOffsetNanoseconds ] )
385 PlainDateTimeObject
* GetPlainDateTimeFor(JSContext
* cx
, const Instant
& instant
,
386 JS::Handle
<CalendarValue
> calendar
,
387 int64_t offsetNanoseconds
);
390 * GetPlainDateTimeFor ( timeZoneRec, instant, calendar [ ,
391 * precalculatedOffsetNanoseconds ] )
393 PlainDateTime
GetPlainDateTimeFor(const Instant
& instant
,
394 int64_t offsetNanoseconds
);
397 * GetPlainDateTimeFor ( timeZoneRec, instant, calendar [ ,
398 * precalculatedOffsetNanoseconds ] )
400 bool GetPlainDateTimeFor(JSContext
* cx
, JS::Handle
<TimeZoneRecord
> timeZone
,
401 const Instant
& instant
, PlainDateTime
* result
);
404 * GetPlainDateTimeFor ( timeZoneRec, instant, calendar [ ,
405 * precalculatedOffsetNanoseconds ] )
407 bool GetPlainDateTimeFor(JSContext
* cx
, JS::Handle
<TimeZoneValue
> timeZone
,
408 const Instant
& instant
, PlainDateTime
* result
);
411 * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
413 bool GetInstantFor(JSContext
* cx
, JS::Handle
<TimeZoneValue
> timeZone
,
414 JS::Handle
<PlainDateTimeObject
*> dateTime
,
415 TemporalDisambiguation disambiguation
, Instant
* result
);
418 * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
420 bool GetInstantFor(JSContext
* cx
, JS::Handle
<TimeZoneRecord
> timeZone
,
421 JS::Handle
<PlainDateTimeWithCalendar
> dateTime
,
422 TemporalDisambiguation disambiguation
, Instant
* result
);
425 * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
427 bool GetInstantFor(JSContext
* cx
, JS::Handle
<TimeZoneValue
> timeZone
,
428 JS::Handle
<PlainDateTimeWithCalendar
> dateTime
,
429 TemporalDisambiguation disambiguation
, Instant
* result
);
432 * FormatUTCOffsetNanoseconds ( offsetNanoseconds )
434 JSString
* FormatUTCOffsetNanoseconds(JSContext
* cx
, int64_t offsetNanoseconds
);
437 * GetOffsetStringFor ( timeZoneRec, instant )
439 JSString
* GetOffsetStringFor(JSContext
* cx
, JS::Handle
<TimeZoneValue
> timeZone
,
440 const Instant
& instant
);
443 * GetOffsetStringFor ( timeZoneRec, instant )
445 JSString
* GetOffsetStringFor(JSContext
* cx
, JS::Handle
<TimeZoneRecord
> timeZone
,
446 JS::Handle
<Wrapped
<InstantObject
*>> instant
);
449 * GetOffsetNanosecondsFor ( timeZoneRec, instant )
451 bool GetOffsetNanosecondsFor(JSContext
* cx
, JS::Handle
<TimeZoneRecord
> timeZone
,
452 JS::Handle
<Wrapped
<InstantObject
*>> instant
,
453 int64_t* offsetNanoseconds
);
456 * GetOffsetNanosecondsFor ( timeZoneRec, instant )
458 bool GetOffsetNanosecondsFor(JSContext
* cx
, JS::Handle
<TimeZoneValue
> timeZone
,
459 JS::Handle
<Wrapped
<InstantObject
*>> instant
,
460 int64_t* offsetNanoseconds
);
463 * GetOffsetNanosecondsFor ( timeZoneRec, instant )
465 bool GetOffsetNanosecondsFor(JSContext
* cx
, JS::Handle
<TimeZoneRecord
> timeZone
,
466 const Instant
& instant
,
467 int64_t* offsetNanoseconds
);
470 * GetOffsetNanosecondsFor ( timeZoneRec, instant )
472 bool GetOffsetNanosecondsFor(JSContext
* cx
, JS::Handle
<TimeZoneValue
> timeZone
,
473 const Instant
& instant
,
474 int64_t* offsetNanoseconds
);
476 using InstantVector
= JS::StackGCVector
<Wrapped
<InstantObject
*>>;
479 * GetPossibleInstantsFor ( timeZoneRec, dateTime )
481 bool GetPossibleInstantsFor(JSContext
* cx
, JS::Handle
<TimeZoneRecord
> timeZone
,
482 JS::Handle
<PlainDateTimeWithCalendar
> dateTime
,
483 JS::MutableHandle
<InstantVector
> list
);
486 * DisambiguatePossibleInstants ( possibleInstants, timeZoneRec, dateTime,
489 bool DisambiguatePossibleInstants(
490 JSContext
* cx
, JS::Handle
<InstantVector
> possibleInstants
,
491 JS::Handle
<TimeZoneRecord
> timeZone
, const PlainDateTime
& dateTime
,
492 TemporalDisambiguation disambiguation
,
493 JS::MutableHandle
<Wrapped
<InstantObject
*>> result
);
496 * CreateTimeZoneMethodsRecord ( timeZone, methods )
498 bool CreateTimeZoneMethodsRecord(JSContext
* cx
,
499 JS::Handle
<TimeZoneValue
> timeZone
,
500 mozilla::EnumSet
<TimeZoneMethod
> methods
,
501 JS::MutableHandle
<TimeZoneRecord
> result
);
505 * TimeZoneMethodsRecordHasLookedUp ( timeZoneRec, methodName )
507 inline bool TimeZoneMethodsRecordHasLookedUp(const TimeZoneRecord
& timeZone
,
508 TimeZoneMethod methodName
) {
510 return timeZone
.lookedUp().contains(methodName
);
515 * TimeZoneMethodsRecordIsBuiltin ( timeZoneRec )
517 inline bool TimeZoneMethodsRecordIsBuiltin(const TimeZoneRecord
& timeZone
) {
519 return timeZone
.receiver().isString();
522 // Helper for MutableWrappedPtrOperations.
523 bool WrapTimeZoneValueObject(JSContext
* cx
,
524 JS::MutableHandle
<JSObject
*> timeZone
);
526 } /* namespace js::temporal */
530 template <typename Wrapper
>
531 class WrappedPtrOperations
<temporal::TimeZoneValue
, Wrapper
> {
532 const auto& container() const {
533 return static_cast<const Wrapper
*>(this)->get();
537 explicit operator bool() const { return !!container(); }
539 bool isString() const { return container().isString(); }
541 bool isObject() const { return container().isObject(); }
543 JS::Handle
<temporal::BuiltinTimeZoneObject
*> toString() const {
544 MOZ_ASSERT(container().isString());
545 auto h
= JS::Handle
<JSObject
*>::fromMarkedLocation(container().address());
546 return h
.template as
<temporal::BuiltinTimeZoneObject
>();
549 JS::Handle
<JSObject
*> toObject() const {
550 MOZ_ASSERT(container().isObject());
551 return JS::Handle
<JSObject
*>::fromMarkedLocation(container().address());
554 JS::Handle
<temporal::TimeZoneObjectMaybeBuiltin
*>
555 toTimeZoneObjectMaybeBuiltin() const {
556 MOZ_ASSERT(container().isTimeZoneObjectMaybeBuiltin());
557 auto h
= JS::Handle
<JSObject
*>::fromMarkedLocation(container().address());
558 return h
.template as
<temporal::TimeZoneObjectMaybeBuiltin
>();
561 JS::Value
toValue() const { return container().toValue(); }
563 JS::Value
toSlotValue() const { return container().toSlotValue(); }
566 template <typename Wrapper
>
567 class MutableWrappedPtrOperations
<temporal::TimeZoneValue
, Wrapper
>
568 : public WrappedPtrOperations
<temporal::TimeZoneValue
, Wrapper
> {
569 auto& container() { return static_cast<Wrapper
*>(this)->get(); }
573 * Wrap the time zone value into the current compartment.
575 bool wrap(JSContext
* cx
) {
576 MOZ_ASSERT(container().isString() || container().isObject());
578 JS::MutableHandle
<JSObject
*>::fromMarkedLocation(container().address());
579 return temporal::WrapTimeZoneValueObject(cx
, mh
);
583 template <typename Wrapper
>
584 class WrappedPtrOperations
<temporal::TimeZoneRecord
, Wrapper
> {
585 const auto& container() const {
586 return static_cast<const Wrapper
*>(this)->get();
590 JS::Handle
<temporal::TimeZoneValue
> receiver() const {
591 return JS::Handle
<temporal::TimeZoneValue
>::fromMarkedLocation(
592 container().receiverDoNotUse());
595 JS::Handle
<JSObject
*> getOffsetNanosecondsFor() const {
596 return JS::Handle
<JSObject
*>::fromMarkedLocation(
597 container().getOffsetNanosecondsForDoNotUse());
600 JS::Handle
<JSObject
*> getPossibleInstantsFor() const {
601 return JS::Handle
<JSObject
*>::fromMarkedLocation(
602 container().getPossibleInstantsForDoNotUse());
606 template <typename Wrapper
>
607 class MutableWrappedPtrOperations
<temporal::TimeZoneRecord
, Wrapper
>
608 : public WrappedPtrOperations
<temporal::TimeZoneRecord
, Wrapper
> {
609 auto& container() { return static_cast<Wrapper
*>(this)->get(); }
612 JS::MutableHandle
<JSObject
*> getOffsetNanosecondsFor() {
613 return JS::MutableHandle
<JSObject
*>::fromMarkedLocation(
614 container().getOffsetNanosecondsForDoNotUse());
617 JS::MutableHandle
<JSObject
*> getPossibleInstantsFor() {
618 return JS::MutableHandle
<JSObject
*>::fromMarkedLocation(
619 container().getPossibleInstantsForDoNotUse());
625 #endif /* builtin_temporal_TimeZone_h */