/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef builtin_temporal_TimeZone_h #define builtin_temporal_TimeZone_h #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" #include "mozilla/Maybe.h" #include #include #include #include "builtin/temporal/TemporalTypes.h" #include "js/RootingAPI.h" #include "js/TypeDecls.h" #include "js/Value.h" #include "vm/JSObject.h" #include "vm/NativeObject.h" #include "vm/StringType.h" class JS_PUBLIC_API JSTracer; struct JSClassOps; namespace mozilla::intl { class TimeZone; } namespace js::temporal { class TimeZoneObject : public NativeObject { public: static const JSClass class_; static constexpr uint32_t IDENTIFIER_SLOT = 0; static constexpr uint32_t PRIMARY_IDENTIFIER_SLOT = 1; static constexpr uint32_t OFFSET_MINUTES_SLOT = 2; static constexpr uint32_t INTL_TIMEZONE_SLOT = 3; static constexpr uint32_t SLOT_COUNT = 4; // Estimated memory use for intl::TimeZone (see IcuMemoryUsage). static constexpr size_t EstimatedMemoryUse = 6840; bool isOffset() const { return getFixedSlot(OFFSET_MINUTES_SLOT).isInt32(); } JSLinearString* identifier() const { return &getFixedSlot(IDENTIFIER_SLOT).toString()->asLinear(); } JSLinearString* primaryIdentifier() const { MOZ_ASSERT(!isOffset()); return &getFixedSlot(PRIMARY_IDENTIFIER_SLOT).toString()->asLinear(); } int32_t offsetMinutes() const { MOZ_ASSERT(isOffset()); return getFixedSlot(OFFSET_MINUTES_SLOT).toInt32(); } mozilla::intl::TimeZone* getTimeZone() const { const auto& slot = getFixedSlot(INTL_TIMEZONE_SLOT); if (slot.isUndefined()) { return nullptr; } return static_cast(slot.toPrivate()); } void setTimeZone(mozilla::intl::TimeZone* timeZone) { setFixedSlot(INTL_TIMEZONE_SLOT, JS::PrivateValue(timeZone)); } private: static const JSClassOps classOps_; static void finalize(JS::GCContext* gcx, JSObject* obj); }; } /* namespace js::temporal */ namespace js::temporal { /** * Temporal time zones are either available named time zones or offset time * zones. * * The identifier of an available named time zones is an available named * time zone identifier, which is either a primary time zone identifier or a * non-primary time zone identifier. * * The identifier of an offset time zone is an offset time zone identifier. * * Temporal methods always return the normalized format of a time zone * identifier. Available named time zone identifier are always in normalized * format. * * Examples of valid available time zone identifiers in normalized format: * - "UTC" (primary identifier) * - "Etc/UTC" (non-primary identifier) * - "America/New_York" (primary identifier) * - "+00:00" * * Examples of valid available time zone identifiers in non-normalized format: * - "+00" * - "-00:00" * * Examples of invalid available time zone identifiers: * - "utc" (wrong case) * - "+00:00:00" (sub-minute precision) * - "+00:00:01" (sub-minute precision) * * The following two implementation approaches are possible: * * 1. Represent time zones as JSStrings. Additionally keep a mapping from * JSString to `mozilla::intl::TimeZone` to avoid repeatedly creating new * `mozilla::intl::TimeZone` for time zone operations. Offset string time * zones have to be special cased, because they don't use * `mozilla::intl::TimeZone`. Either detect offset strings by checking the * time zone identifier or store offset strings as the offset in minutes * value to avoid reparsing the offset string again and again. * 2. Represent time zones as objects which hold `mozilla::intl::TimeZone` in * an internal slot. * * Option 2 is a bit easier to implement, so we use this approach for now. */ class MOZ_STACK_CLASS TimeZoneValue final { TimeZoneObject* object_ = nullptr; public: /** * Default initialize this TimeZoneValue. */ TimeZoneValue() = default; /** * Initialize this TimeZoneValue with a time zone object. */ explicit TimeZoneValue(TimeZoneObject* timeZone) : object_(timeZone) { MOZ_ASSERT(object_); } /** * Initialize this TimeZoneValue from a slot Value. */ explicit TimeZoneValue(const JS::Value& value) : object_(&value.toObject().as()) {} /** * Return true if this TimeZoneValue is not null. */ explicit operator bool() const { return !!object_; } /** * Return true if this TimeZoneValue is an offset time zone. */ bool isOffset() const { MOZ_ASSERT(object_); return object_->isOffset(); } /** * Return the offset of an offset time zone. */ auto offsetMinutes() const { MOZ_ASSERT(object_); return object_->offsetMinutes(); } /** * Return the time zone identifier. */ auto* identifier() const { MOZ_ASSERT(object_); return object_->identifier(); } /** * Return the primary time zone identifier of a named time zone. */ auto* primaryIdentifier() const { MOZ_ASSERT(object_); return object_->primaryIdentifier(); } /** * Return the time zone implementation. */ auto* getTimeZone() const { MOZ_ASSERT(object_); return object_->getTimeZone(); } /** * Return the underlying TimeZoneObject. */ auto* toTimeZoneObject() const { MOZ_ASSERT(object_); return object_; } /** * Return the slot Value representation of this TimeZoneValue. */ JS::Value toSlotValue() const { MOZ_ASSERT(object_); return JS::ObjectValue(*object_); } // Helper methods for (Mutable)WrappedPtrOperations. auto address() { return &object_; } auto address() const { return &object_; } // Trace implementation. void trace(JSTracer* trc); }; class PossibleEpochNanoseconds final { // GetPossibleEpochNanoseconds can return up-to two elements. static constexpr size_t MaxLength = 2; std::array array_ = {}; size_t length_ = 0; void append(const EpochNanoseconds& epochNs) { array_[length_++] = epochNs; } public: PossibleEpochNanoseconds() = default; explicit PossibleEpochNanoseconds(const EpochNanoseconds& epochNs) { append(epochNs); } explicit PossibleEpochNanoseconds(const EpochNanoseconds& earlier, const EpochNanoseconds& later) { MOZ_ASSERT(earlier <= later); append(earlier); append(later); } size_t length() const { return length_; } bool empty() const { return length_ == 0; } const auto& operator[](size_t i) const { return array_[i]; } auto begin() const { return array_.begin(); } auto end() const { return array_.begin() + length_; } const auto& front() const { MOZ_ASSERT(length_ > 0); return array_[0]; } const auto& back() const { MOZ_ASSERT(length_ > 0); return array_[length_ - 1]; } }; struct ParsedTimeZone; enum class TemporalDisambiguation; /** * Create a new |TimeZoneObject| whose identifier is |identifier| and whose * primary identifier is |primaryIdentifier|. */ TimeZoneObject* CreateTimeZoneObject( JSContext* cx, JS::Handle identifier, JS::Handle primaryIdentifier); /** * SystemTimeZoneIdentifier ( ) */ JSLinearString* ComputeSystemTimeZoneIdentifier(JSContext* cx); /** * SystemTimeZoneIdentifier ( ) */ JSLinearString* SystemTimeZoneIdentifier(JSContext* cx); /** * SystemTimeZoneIdentifier ( ) */ bool SystemTimeZone(JSContext* cx, JS::MutableHandle result); /** * ToTemporalTimeZoneIdentifier ( temporalTimeZoneLike ) */ bool ToTemporalTimeZone(JSContext* cx, JS::Handle temporalTimeZoneLike, JS::MutableHandle result); /** * ToTemporalTimeZoneIdentifier ( temporalTimeZoneLike ) */ bool ToTemporalTimeZone(JSContext* cx, JS::Handle string, JS::MutableHandle result); /** * Verifies that the given string is a valid time zone name. If it is a valid * time zone name, returns the canonicalized time zone name. Canonicalization * resolves link names to their target time zones. */ JSLinearString* ToValidCanonicalTimeZoneIdentifier( JSContext* cx, JS::Handle timeZone); /** * TimeZoneEquals ( one, two ) */ bool TimeZoneEquals(const TimeZoneValue& one, const TimeZoneValue& two); /** * GetISODateTimeFor ( timeZone, epochNs ) */ ISODateTime GetISODateTimeFor(const EpochNanoseconds& epochNs, int64_t offsetNanoseconds); /** * GetISODateTimeFor ( timeZone, epochNs ) */ bool GetISODateTimeFor(JSContext* cx, JS::Handle timeZone, const EpochNanoseconds& epochNs, ISODateTime* result); /** * GetEpochNanosecondsFor ( timeZone, isoDateTime, disambiguation ) */ bool GetEpochNanosecondsFor(JSContext* cx, JS::Handle timeZone, const ISODateTime& isoDateTime, TemporalDisambiguation disambiguation, EpochNanoseconds* result); /** * GetOffsetNanosecondsFor ( timeZone, epochNs ) */ bool GetOffsetNanosecondsFor(JSContext* cx, JS::Handle timeZone, const EpochNanoseconds& epochNs, int64_t* offsetNanoseconds); /** * GetPossibleEpochNanoseconds ( timeZone, isoDateTime ) */ bool GetPossibleEpochNanoseconds(JSContext* cx, JS::Handle timeZone, const ISODateTime& isoDateTime, PossibleEpochNanoseconds* result); /** * DisambiguatePossibleEpochNanoseconds ( possibleEpochNs, timeZone, * isoDateTime, disambiguation ) */ bool DisambiguatePossibleEpochNanoseconds( JSContext* cx, const PossibleEpochNanoseconds& possibleEpochNs, JS::Handle timeZone, const ISODateTime& isoDateTime, TemporalDisambiguation disambiguation, EpochNanoseconds* result); /** * GetNamedTimeZoneNextTransition ( timeZoneIdentifier, epochNanoseconds ) */ bool GetNamedTimeZoneNextTransition(JSContext* cx, JS::Handle timeZone, const EpochNanoseconds& epochNanoseconds, mozilla::Maybe* result); /** * GetNamedTimeZonePreviousTransition ( timeZoneIdentifier, epochNanoseconds ) */ bool GetNamedTimeZonePreviousTransition( JSContext* cx, JS::Handle timeZone, const EpochNanoseconds& epochNanoseconds, mozilla::Maybe* result); /** * GetStartOfDay ( timeZone, isoDate ) */ bool GetStartOfDay(JSContext* cx, JS::Handle timeZone, const ISODate& isoDate, EpochNanoseconds* result); // Helper for MutableWrappedPtrOperations. bool WrapTimeZoneValueObject(JSContext* cx, JS::MutableHandle timeZone); } /* namespace js::temporal */ namespace js { template class WrappedPtrOperations { const auto& container() const { return static_cast(this)->get(); } public: explicit operator bool() const { return !!container(); } bool isOffset() const { return container().isOffset(); } auto offsetMinutes() const { return container().offsetMinutes(); } auto* identifier() const { return container().identifier(); } auto* primaryIdentifier() const { return container().primaryIdentifier(); } auto* getTimeZone() const { return container().getTimeZone(); } JS::Value toSlotValue() const { return container().toSlotValue(); } }; template class MutableWrappedPtrOperations : public WrappedPtrOperations { auto& container() { return static_cast(this)->get(); } public: /** * Wrap the time zone value into the current compartment. */ bool wrap(JSContext* cx) { MOZ_ASSERT(container()); auto mh = JS::MutableHandle::fromMarkedLocation( container().address()); return temporal::WrapTimeZoneValueObject(cx, mh); } }; } /* namespace js */ #endif /* builtin_temporal_TimeZone_h */