tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

DateTime.h (17222B)


      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/. */
      6 
      7 #ifndef vm_DateTime_h
      8 #define vm_DateTime_h
      9 
     10 #include "mozilla/Atomics.h"
     11 #include "mozilla/RefPtr.h"
     12 #include "mozilla/UniquePtr.h"
     13 
     14 #include <stdint.h>
     15 
     16 #include "js/AllocPolicy.h"
     17 #include "js/RealmOptions.h"
     18 #include "js/Utility.h"
     19 #include "js/Vector.h"
     20 #include "threading/ExclusiveData.h"
     21 
     22 #if JS_HAS_INTL_API
     23 #  include "mozilla/intl/ICUError.h"
     24 #  include "mozilla/intl/TimeZone.h"
     25 #endif
     26 
     27 namespace JS {
     28 class Realm;
     29 }
     30 
     31 namespace js {
     32 
     33 /**
     34 * 21.4.1.2 Time-related Constants
     35 *
     36 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
     37 */
     38 constexpr int32_t HoursPerDay = 24;
     39 constexpr int32_t MinutesPerHour = 60;
     40 constexpr int32_t SecondsPerMinute = 60;
     41 constexpr int32_t msPerSecond = 1000;
     42 constexpr int32_t msPerMinute = msPerSecond * SecondsPerMinute;
     43 constexpr int32_t msPerHour = msPerMinute * MinutesPerHour;
     44 constexpr int32_t msPerDay = msPerHour * HoursPerDay;
     45 
     46 /*
     47 * Additional quantities not mentioned in the spec.
     48 */
     49 constexpr int32_t SecondsPerHour = 60 * 60;
     50 constexpr int32_t SecondsPerDay = SecondsPerHour * 24;
     51 
     52 constexpr double StartOfTime = -8.64e15;
     53 constexpr double EndOfTime = 8.64e15;
     54 
     55 extern bool InitDateTimeState();
     56 
     57 extern void FinishDateTimeState();
     58 
     59 enum class ResetTimeZoneMode : bool {
     60  DontResetIfOffsetUnchanged,
     61  ResetEvenIfOffsetUnchanged,
     62 };
     63 
     64 /**
     65 * Engine-internal variant of JS::ResetTimeZone with an additional flag to
     66 * control whether to forcibly reset all time zone data (this is the default
     67 * behavior when calling JS::ResetTimeZone) or to try to reuse the previous
     68 * time zone data.
     69 */
     70 extern void ResetTimeZoneInternal(ResetTimeZoneMode mode);
     71 
     72 using TimeZoneDisplayNameVector = Vector<char16_t, 100, SystemAllocPolicy>;
     73 
     74 #if JS_HAS_INTL_API
     75 using TimeZoneIdentifierVector =
     76    Vector<char, mozilla::intl::TimeZone::TimeZoneIdentifierLength,
     77           SystemAllocPolicy>;
     78 #endif
     79 
     80 /**
     81 * Stores date/time information, particularly concerning the current local
     82 * time zone, and implements a small cache for daylight saving time offset
     83 * computation.
     84 *
     85 * The basic idea is premised upon this fact: the DST offset never changes more
     86 * than once in any thirty-day period.  If we know the offset at t_0 is o_0,
     87 * the offset at [t_1, t_2] is also o_0, where t_1 + 3_0 days == t_2,
     88 * t_1 <= t_0, and t0 <= t2.  (In other words, t_0 is always somewhere within a
     89 * thirty-day range where the DST offset is constant: DST changes never occur
     90 * more than once in any thirty-day period.)  Therefore, if we intelligently
     91 * retain knowledge of the offset for a range of dates (which may vary over
     92 * time), and if requests are usually for dates within that range, we can often
     93 * provide a response without repeated offset calculation.
     94 *
     95 * Our caching strategy is as follows: on the first request at date t_0 compute
     96 * the requested offset o_0.  Save { start: t_0, end: t_0, offset: o_0 } as the
     97 * cache's state.  Subsequent requests within that range are straightforwardly
     98 * handled.  If a request for t_i is far outside the range (more than thirty
     99 * days), compute o_i = dstOffset(t_i) and save { start: t_i, end: t_i,
    100 * offset: t_i }.  Otherwise attempt to *overextend* the range to either
    101 * [start - 30d, end] or [start, end + 30d] as appropriate to encompass
    102 * t_i.  If the offset o_i30 is the same as the cached offset, extend the
    103 * range.  Otherwise the over-guess crossed a DST change -- compute
    104 * o_i = dstOffset(t_i) and either extend the original range (if o_i == offset)
    105 * or start a new one beneath/above the current one with o_i30 as the offset.
    106 *
    107 * This cache strategy results in 0 to 2 DST offset computations.  The naive
    108 * always-compute strategy is 1 computation, and since cache maintenance is a
    109 * handful of integer arithmetic instructions the speed difference between
    110 * always-1 and 1-with-cache is negligible.  Caching loses if two computations
    111 * happen: when the date is within 30 days of the cached range and when that
    112 * 30-day range crosses a DST change.  This is relatively uncommon.  Further,
    113 * instances of such are often dominated by in-range hits, so caching is an
    114 * overall slight win.
    115 *
    116 * Why 30 days?  For correctness the duration must be smaller than any possible
    117 * duration between DST changes.  Past that, note that 1) a large duration
    118 * increases the likelihood of crossing a DST change while reducing the number
    119 * of cache misses, and 2) a small duration decreases the size of the cached
    120 * range while producing more misses.  Using a month as the interval change is
    121 * a balance between these two that tries to optimize for the calendar month at
    122 * a time that a site might display.  (One could imagine an adaptive duration
    123 * that accommodates near-DST-change dates better; we don't believe the
    124 * potential win from better caching offsets the loss from extra complexity.)
    125 */
    126 class DateTimeInfo {
    127  // DateTimeInfo for the default time zone.
    128  static ExclusiveData<DateTimeInfo>* instance;
    129 
    130  static constexpr int32_t InvalidOffset = INT32_MIN;
    131 
    132  // Additional cache to avoid the mutex overhead. Uses "relaxed" semantics
    133  // because it's acceptable if time zone offset changes aren't propagated right
    134  // away to all other threads.
    135  static inline mozilla::Atomic<int32_t, mozilla::Relaxed>
    136      utcToLocalOffsetSeconds{InvalidOffset};
    137 
    138  friend class ExclusiveData<DateTimeInfo>;
    139 
    140  friend bool InitDateTimeState();
    141  friend void FinishDateTimeState();
    142 
    143  DateTimeInfo();
    144 
    145  static auto acquireLockWithValidTimeZone() {
    146    auto guard = instance->lock();
    147    if (guard->timeZoneStatus_ != TimeZoneStatus::Valid) {
    148      guard->updateTimeZone();
    149    }
    150    return guard;
    151  }
    152 
    153 public:
    154 #if JS_HAS_INTL_API
    155  explicit DateTimeInfo(RefPtr<JS::TimeZoneString> timeZone);
    156 #endif
    157  ~DateTimeInfo();
    158 
    159  // The spec implicitly assumes DST and time zone adjustment information
    160  // never change in the course of a function -- sometimes even across
    161  // reentrancy.  So make critical sections as narrow as possible.
    162 
    163  /**
    164   * Get the DST offset in milliseconds at a UTC time.  This is usually
    165   * either 0 or |msPerSecond * SecondsPerHour|, but at least one exotic time
    166   * zone (Lord Howe Island, Australia) has a fractional-hour offset, just to
    167   * keep things interesting.
    168   */
    169  static int32_t getDSTOffsetMilliseconds(DateTimeInfo* dtInfo,
    170                                          int64_t utcMilliseconds) {
    171    if (MOZ_UNLIKELY(dtInfo)) {
    172      return dtInfo->internalGetDSTOffsetMilliseconds(utcMilliseconds);
    173    }
    174    auto guard = acquireLockWithValidTimeZone();
    175    return guard->internalGetDSTOffsetMilliseconds(utcMilliseconds);
    176  }
    177 
    178  /**
    179   * The offset in seconds from the current UTC time to the current local
    180   * standard time (i.e. not including any offset due to DST) as computed by the
    181   * operating system.
    182   */
    183  static int32_t utcToLocalStandardOffsetSeconds() {
    184    // First try the cached offset to avoid any mutex overhead.
    185    int32_t offset = utcToLocalOffsetSeconds;
    186    if (offset != InvalidOffset) {
    187      return offset;
    188    }
    189 
    190    // If that fails, use the mutex-synchronized code path.
    191    auto guard = acquireLockWithValidTimeZone();
    192    offset = guard->utcToLocalStandardOffsetSeconds_;
    193    utcToLocalOffsetSeconds = offset;
    194    return offset;
    195  }
    196 
    197  /**
    198   * Cache key for this date-time info. Returns a different value when the
    199   * time zone changed.
    200   */
    201  static int32_t timeZoneCacheKey(DateTimeInfo* dtInfo) {
    202    if (MOZ_UNLIKELY(dtInfo)) {
    203      // |utcToLocalStandardOffsetSeconds_| is incremented when the time zone
    204      // override is modified.
    205      return dtInfo->utcToLocalStandardOffsetSeconds_;
    206    }
    207 
    208    // Use the offset as the cache key for the default time zone.
    209    return utcToLocalStandardOffsetSeconds();
    210  }
    211 
    212  enum class TimeZoneOffset { UTC, Local };
    213 
    214 #if JS_HAS_INTL_API
    215  /**
    216   * Return the time zone offset, including DST, in milliseconds at the
    217   * given time. The input time can be either at UTC or at local time.
    218   */
    219  static int32_t getOffsetMilliseconds(DateTimeInfo* dtInfo,
    220                                       int64_t milliseconds,
    221                                       TimeZoneOffset offset) {
    222    if (MOZ_UNLIKELY(dtInfo)) {
    223      return dtInfo->internalGetOffsetMilliseconds(milliseconds, offset);
    224    }
    225    auto guard = acquireLockWithValidTimeZone();
    226    return guard->internalGetOffsetMilliseconds(milliseconds, offset);
    227  }
    228 
    229  /**
    230   * Copy the display name for the current time zone at the given time,
    231   * localized for the specified locale, into the supplied vector.
    232   */
    233  static bool timeZoneDisplayName(DateTimeInfo* dtInfo,
    234                                  TimeZoneDisplayNameVector& result,
    235                                  int64_t utcMilliseconds, const char* locale) {
    236    if (MOZ_UNLIKELY(dtInfo)) {
    237      return dtInfo->internalTimeZoneDisplayName(result, utcMilliseconds,
    238                                                 locale);
    239    }
    240    auto guard = acquireLockWithValidTimeZone();
    241    return guard->internalTimeZoneDisplayName(result, utcMilliseconds, locale);
    242  }
    243 
    244  /**
    245   * Copy the identifier for the current time zone into the supplied vector.
    246   */
    247  static bool timeZoneId(DateTimeInfo* dtInfo,
    248                         TimeZoneIdentifierVector& result) {
    249    if (MOZ_UNLIKELY(dtInfo)) {
    250      return dtInfo->internalTimeZoneId(result);
    251    }
    252    auto guard = acquireLockWithValidTimeZone();
    253    return guard->internalTimeZoneId(result);
    254  }
    255 
    256  /**
    257   * A number indicating the raw offset from GMT in milliseconds.
    258   */
    259  static mozilla::Result<int32_t, mozilla::intl::ICUError> getRawOffsetMs(
    260      DateTimeInfo* dtInfo) {
    261    if (MOZ_UNLIKELY(dtInfo)) {
    262      return dtInfo->timeZone()->GetRawOffsetMs();
    263    }
    264    auto guard = acquireLockWithValidTimeZone();
    265    return guard->timeZone()->GetRawOffsetMs();
    266  }
    267 #else
    268  /**
    269   * Return the local time zone adjustment (ES2019 20.3.1.7) as computed by
    270   * the operating system.
    271   */
    272  static int32_t localTZA() {
    273    return utcToLocalStandardOffsetSeconds() * msPerSecond;
    274  }
    275 #endif /* JS_HAS_INTL_API */
    276 
    277  // JIT access.
    278  static const void* addressOfUTCToLocalOffsetSeconds() {
    279    static_assert(sizeof(decltype(utcToLocalOffsetSeconds)) == sizeof(int32_t));
    280    return &DateTimeInfo::utcToLocalOffsetSeconds;
    281  }
    282 
    283 #if JS_HAS_INTL_API
    284  void updateTimeZoneOverride(RefPtr<JS::TimeZoneString> timeZone);
    285 #endif
    286 
    287 private:
    288  // The method below should only be called via js::ResetTimeZoneInternal().
    289  friend void js::ResetTimeZoneInternal(ResetTimeZoneMode);
    290 
    291  static void resetTimeZone(ResetTimeZoneMode mode) {
    292    auto guard = instance->lock();
    293    guard->internalResetTimeZone(mode);
    294 
    295    // Mark the cached value as invalid.
    296    utcToLocalOffsetSeconds = InvalidOffset;
    297  }
    298 
    299  struct RangeCache {
    300    // Start and end offsets in seconds describing the current and the
    301    // last cached range.
    302    int64_t startSeconds, endSeconds;
    303    int64_t oldStartSeconds, oldEndSeconds;
    304 
    305    // The current and the last cached offset in milliseconds.
    306    int32_t offsetMilliseconds;
    307    int32_t oldOffsetMilliseconds;
    308 
    309    void reset();
    310 
    311    void sanityCheck();
    312  };
    313 
    314  enum class TimeZoneStatus : uint8_t { Valid, NeedsUpdate, UpdateIfChanged };
    315 
    316  TimeZoneStatus timeZoneStatus_;
    317 
    318  /**
    319   * The offset in seconds from the current UTC time to the current local
    320   * standard time (i.e. not including any offset due to DST) as computed by the
    321   * operating system.
    322   *
    323   * Cached because retrieving this dynamically is Slow, and a certain venerable
    324   * benchmark which shall not be named depends on it being fast.
    325   *
    326   * SpiderMonkey occasionally and arbitrarily updates this value from the
    327   * system time zone to attempt to keep this reasonably up-to-date.  If
    328   * temporary inaccuracy can't be tolerated, JSAPI clients may call
    329   * JS::ResetTimeZone to forcibly sync this with the system time zone.
    330   *
    331   * In most cases this value is consistent with the raw time zone offset as
    332   * returned by the ICU default time zone (`icu::TimeZone::getRawOffset()`),
    333   * but it is possible to create cases where the operating system default time
    334   * zone differs from the ICU default time zone. For example ICU doesn't
    335   * support the full range of TZ environment variable settings, which can
    336   * result in <ctime> returning a different time zone than what's returned by
    337   * ICU. One example is "TZ=WGT3WGST,M3.5.0/-2,M10.5.0/-1", where <ctime>
    338   * returns -3 hours as the local offset, but ICU flat out rejects the TZ value
    339   * and instead infers the default time zone via "/etc/localtime" (on Unix).
    340   * This offset can also differ from ICU when the operating system and ICU use
    341   * different tzdata versions and the time zone rules of the current system
    342   * time zone have changed. Or, on Windows, when the Windows default time zone
    343   * can't be mapped to a IANA time zone, see for example
    344   * <https://unicode-org.atlassian.net/browse/ICU-13845>.
    345   *
    346   * When ICU is exclusively used for time zone computations, that means when
    347   * |JS_HAS_INTL_API| is true, this field is only used to detect system default
    348   * time zone changes. It must not be used to convert between local and UTC
    349   * time, because, as outlined above, this could lead to different results when
    350   * compared to ICU.
    351   *
    352   * If |timeZoneOverride_| is non-null, i.e. when not using the default time
    353   * zone, this field is reused as the time zone cache key. See also
    354   * |timeZoneCacheKey()| and |updateTimeZoneOverride()|.
    355   */
    356  int32_t utcToLocalStandardOffsetSeconds_;
    357 
    358  RangeCache dstRange_;  // UTC-based ranges
    359 
    360 #if JS_HAS_INTL_API
    361  // Use the full date-time range when we can use mozilla::intl::TimeZone.
    362  static constexpr int64_t MinTimeT =
    363      static_cast<int64_t>(StartOfTime / msPerSecond);
    364  static constexpr int64_t MaxTimeT =
    365      static_cast<int64_t>(EndOfTime / msPerSecond);
    366 
    367  RangeCache utcRange_;    // localtime-based ranges
    368  RangeCache localRange_;  // UTC-based ranges
    369 
    370  /**
    371   * Time zone override for realms with non-default time zone.
    372   */
    373  RefPtr<JS::TimeZoneString> timeZoneOverride_;
    374 
    375  /**
    376   * The current time zone. Lazily constructed to avoid potential I/O access
    377   * when initializing this class.
    378   */
    379  mozilla::UniquePtr<mozilla::intl::TimeZone> timeZone_;
    380 
    381  /**
    382   * Cached time zone id.
    383   */
    384  JS::UniqueChars timeZoneId_;
    385 
    386  /**
    387   * Cached names of the standard and daylight savings display names of the
    388   * current time zone for the default locale.
    389   */
    390  JS::UniqueChars locale_;
    391  JS::UniqueTwoByteChars standardName_;
    392  JS::UniqueTwoByteChars daylightSavingsName_;
    393 #else
    394  // Restrict the data-time range to the minimum required time_t range as
    395  // specified in POSIX. Most operating systems support 64-bit time_t
    396  // values, but we currently still have some configurations which use
    397  // 32-bit time_t, e.g. the ARM simulator on 32-bit Linux (bug 1406993).
    398  // Bug 1406992 explores to use 64-bit time_t when supported by the
    399  // underlying operating system.
    400  static constexpr int64_t MinTimeT = 0;          /* time_t 01/01/1970 */
    401  static constexpr int64_t MaxTimeT = 2145830400; /* time_t 12/31/2037 */
    402 #endif /* JS_HAS_INTL_API */
    403 
    404  static constexpr int64_t RangeExpansionAmount = 30 * SecondsPerDay;
    405 
    406  void internalResetTimeZone(ResetTimeZoneMode mode);
    407 
    408  void resetState();
    409 
    410  void updateTimeZone();
    411 
    412  void internalResyncICUDefaultTimeZone();
    413 
    414  static int64_t toClampedSeconds(int64_t milliseconds);
    415 
    416  using ComputeFn = int32_t (DateTimeInfo::*)(int64_t);
    417 
    418  /**
    419   * Get or compute an offset value for the requested seconds value.
    420   */
    421  int32_t getOrComputeValue(RangeCache& range, int64_t seconds,
    422                            ComputeFn compute);
    423 
    424  /**
    425   * Compute the DST offset at the given UTC time in seconds from the epoch.
    426   * (getDSTOffsetMilliseconds attempts to return a cached value from the
    427   * dstRange_ member, but in case of a cache miss it calls this method.)
    428   */
    429  int32_t computeDSTOffsetMilliseconds(int64_t utcSeconds);
    430 
    431  int32_t internalGetDSTOffsetMilliseconds(int64_t utcMilliseconds);
    432 
    433 #if JS_HAS_INTL_API
    434  /**
    435   * Compute the UTC offset in milliseconds for the given local time. Called
    436   * by internalGetOffsetMilliseconds on a cache miss.
    437   */
    438  int32_t computeUTCOffsetMilliseconds(int64_t localSeconds);
    439 
    440  /**
    441   * Compute the local time offset in milliseconds for the given UTC time.
    442   * Called by internalGetOffsetMilliseconds on a cache miss.
    443   */
    444  int32_t computeLocalOffsetMilliseconds(int64_t utcSeconds);
    445 
    446  int32_t internalGetOffsetMilliseconds(int64_t milliseconds,
    447                                        TimeZoneOffset offset);
    448 
    449  bool internalTimeZoneDisplayName(TimeZoneDisplayNameVector& result,
    450                                   int64_t utcMilliseconds, const char* locale);
    451 
    452  bool internalTimeZoneId(TimeZoneIdentifierVector& result);
    453 
    454  mozilla::intl::TimeZone* timeZone();
    455 #endif /* JS_HAS_INTL_API */
    456 };
    457 
    458 } /* namespace js */
    459 
    460 #endif /* vm_DateTime_h */