Backed out changeset 8f976ed899d7 (bug 1847231) for causing bc failures on browser_se...
[gecko.git] / js / src / vm / DateTime.h
blobfd6d7ae078b8f6b3cc46a4a993a1e044a7128c90
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 vm_DateTime_h
8 #define vm_DateTime_h
10 #include "mozilla/UniquePtr.h"
12 #include <stdint.h>
14 #include "js/Utility.h"
15 #include "threading/ExclusiveData.h"
17 #if JS_HAS_INTL_API
18 # include "mozilla/intl/ICU4CGlue.h"
19 # include "mozilla/intl/TimeZone.h"
20 #endif
22 namespace JS {
23 class Realm;
26 namespace js {
28 /* Constants defined by ES5 15.9.1.10. */
29 constexpr double HoursPerDay = 24;
30 constexpr double MinutesPerHour = 60;
31 constexpr double SecondsPerMinute = 60;
32 constexpr double msPerSecond = 1000;
33 constexpr double msPerMinute = msPerSecond * SecondsPerMinute;
34 constexpr double msPerHour = msPerMinute * MinutesPerHour;
36 /* ES5 15.9.1.2. */
37 constexpr double msPerDay = msPerHour * HoursPerDay;
40 * Additional quantities not mentioned in the spec. Be careful using these!
41 * They aren't doubles and aren't defined in terms of all the other constants.
42 * If you need constants that trigger floating point semantics, you'll have to
43 * manually cast to get it.
45 constexpr unsigned SecondsPerHour = 60 * 60;
46 constexpr unsigned SecondsPerDay = SecondsPerHour * 24;
48 constexpr double StartOfTime = -8.64e15;
49 constexpr double EndOfTime = 8.64e15;
51 extern bool InitDateTimeState();
53 extern void FinishDateTimeState();
55 enum class ResetTimeZoneMode : bool {
56 DontResetIfOffsetUnchanged,
57 ResetEvenIfOffsetUnchanged,
60 /**
61 * Engine-internal variant of JS::ResetTimeZone with an additional flag to
62 * control whether to forcibly reset all time zone data (this is the default
63 * behavior when calling JS::ResetTimeZone) or to try to reuse the previous
64 * time zone data.
66 extern void ResetTimeZoneInternal(ResetTimeZoneMode mode);
68 /**
69 * Stores date/time information, particularly concerning the current local
70 * time zone, and implements a small cache for daylight saving time offset
71 * computation.
73 * The basic idea is premised upon this fact: the DST offset never changes more
74 * than once in any thirty-day period. If we know the offset at t_0 is o_0,
75 * the offset at [t_1, t_2] is also o_0, where t_1 + 3_0 days == t_2,
76 * t_1 <= t_0, and t0 <= t2. (In other words, t_0 is always somewhere within a
77 * thirty-day range where the DST offset is constant: DST changes never occur
78 * more than once in any thirty-day period.) Therefore, if we intelligently
79 * retain knowledge of the offset for a range of dates (which may vary over
80 * time), and if requests are usually for dates within that range, we can often
81 * provide a response without repeated offset calculation.
83 * Our caching strategy is as follows: on the first request at date t_0 compute
84 * the requested offset o_0. Save { start: t_0, end: t_0, offset: o_0 } as the
85 * cache's state. Subsequent requests within that range are straightforwardly
86 * handled. If a request for t_i is far outside the range (more than thirty
87 * days), compute o_i = dstOffset(t_i) and save { start: t_i, end: t_i,
88 * offset: t_i }. Otherwise attempt to *overextend* the range to either
89 * [start - 30d, end] or [start, end + 30d] as appropriate to encompass
90 * t_i. If the offset o_i30 is the same as the cached offset, extend the
91 * range. Otherwise the over-guess crossed a DST change -- compute
92 * o_i = dstOffset(t_i) and either extend the original range (if o_i == offset)
93 * or start a new one beneath/above the current one with o_i30 as the offset.
95 * This cache strategy results in 0 to 2 DST offset computations. The naive
96 * always-compute strategy is 1 computation, and since cache maintenance is a
97 * handful of integer arithmetic instructions the speed difference between
98 * always-1 and 1-with-cache is negligible. Caching loses if two computations
99 * happen: when the date is within 30 days of the cached range and when that
100 * 30-day range crosses a DST change. This is relatively uncommon. Further,
101 * instances of such are often dominated by in-range hits, so caching is an
102 * overall slight win.
104 * Why 30 days? For correctness the duration must be smaller than any possible
105 * duration between DST changes. Past that, note that 1) a large duration
106 * increases the likelihood of crossing a DST change while reducing the number
107 * of cache misses, and 2) a small duration decreases the size of the cached
108 * range while producing more misses. Using a month as the interval change is
109 * a balance between these two that tries to optimize for the calendar month at
110 * a time that a site might display. (One could imagine an adaptive duration
111 * that accommodates near-DST-change dates better; we don't believe the
112 * potential win from better caching offsets the loss from extra complexity.)
114 class DateTimeInfo {
115 public:
116 // For realms that force the UTC time zone (for fingerprinting protection) a
117 // separate DateTimeInfo instance is used that is always in the UTC time zone.
118 enum class ForceUTC { No, Yes };
120 private:
121 static ExclusiveData<DateTimeInfo>* instance;
122 static ExclusiveData<DateTimeInfo>* instanceUTC;
124 friend class ExclusiveData<DateTimeInfo>;
126 friend bool InitDateTimeState();
127 friend void FinishDateTimeState();
129 explicit DateTimeInfo(bool forceUTC);
130 ~DateTimeInfo();
132 static auto acquireLockWithValidTimeZone(ForceUTC forceUTC) {
133 auto guard =
134 forceUTC == ForceUTC::Yes ? instanceUTC->lock() : instance->lock();
135 if (guard->timeZoneStatus_ != TimeZoneStatus::Valid) {
136 guard->updateTimeZone();
138 return guard;
141 public:
142 static ForceUTC forceUTC(JS::Realm* realm);
144 // The spec implicitly assumes DST and time zone adjustment information
145 // never change in the course of a function -- sometimes even across
146 // reentrancy. So make critical sections as narrow as possible.
149 * Get the DST offset in milliseconds at a UTC time. This is usually
150 * either 0 or |msPerSecond * SecondsPerHour|, but at least one exotic time
151 * zone (Lord Howe Island, Australia) has a fractional-hour offset, just to
152 * keep things interesting.
154 static int32_t getDSTOffsetMilliseconds(ForceUTC forceUTC,
155 int64_t utcMilliseconds) {
156 auto guard = acquireLockWithValidTimeZone(forceUTC);
157 return guard->internalGetDSTOffsetMilliseconds(utcMilliseconds);
161 * The offset in seconds from the current UTC time to the current local
162 * standard time (i.e. not including any offset due to DST) as computed by the
163 * operating system.
165 static int32_t utcToLocalStandardOffsetSeconds(ForceUTC forceUTC) {
166 auto guard = acquireLockWithValidTimeZone(forceUTC);
167 return guard->utcToLocalStandardOffsetSeconds_;
170 #if JS_HAS_INTL_API
171 enum class TimeZoneOffset { UTC, Local };
174 * Return the time zone offset, including DST, in milliseconds at the
175 * given time. The input time can be either at UTC or at local time.
177 static int32_t getOffsetMilliseconds(ForceUTC forceUTC, int64_t milliseconds,
178 TimeZoneOffset offset) {
179 auto guard = acquireLockWithValidTimeZone(forceUTC);
180 return guard->internalGetOffsetMilliseconds(milliseconds, offset);
184 * Copy the display name for the current time zone at the given time,
185 * localized for the specified locale, into the supplied buffer. If the
186 * buffer is too small, an empty string is stored. The stored display name
187 * is null-terminated in any case.
189 static bool timeZoneDisplayName(ForceUTC forceUTC, char16_t* buf,
190 size_t buflen, int64_t utcMilliseconds,
191 const char* locale) {
192 auto guard = acquireLockWithValidTimeZone(forceUTC);
193 return guard->internalTimeZoneDisplayName(buf, buflen, utcMilliseconds,
194 locale);
198 * Copy the identifier for the current time zone to the provided resizable
199 * buffer.
201 template <typename B>
202 static mozilla::intl::ICUResult timeZoneId(ForceUTC forceUTC, B& buffer) {
203 auto guard = acquireLockWithValidTimeZone(forceUTC);
204 return guard->timeZone()->GetId(buffer);
208 * A number indicating the raw offset from GMT in milliseconds.
210 static mozilla::Result<int32_t, mozilla::intl::ICUError> getRawOffsetMs(
211 ForceUTC forceUTC) {
212 auto guard = acquireLockWithValidTimeZone(forceUTC);
213 return guard->timeZone()->GetRawOffsetMs();
215 #else
217 * Return the local time zone adjustment (ES2019 20.3.1.7) as computed by
218 * the operating system.
220 static int32_t localTZA(ForceUTC forceUTC) {
221 return utcToLocalStandardOffsetSeconds(forceUTC) * msPerSecond;
223 #endif /* JS_HAS_INTL_API */
225 private:
226 // The method below should only be called via js::ResetTimeZoneInternal().
227 friend void js::ResetTimeZoneInternal(ResetTimeZoneMode);
229 static void resetTimeZone(ResetTimeZoneMode mode) {
231 auto guard = instance->lock();
232 guard->internalResetTimeZone(mode);
235 // Only needed to initialize the default state and any later call will
236 // perform an unnecessary reset.
237 auto guard = instanceUTC->lock();
238 guard->internalResetTimeZone(mode);
242 struct RangeCache {
243 // Start and end offsets in seconds describing the current and the
244 // last cached range.
245 int64_t startSeconds, endSeconds;
246 int64_t oldStartSeconds, oldEndSeconds;
248 // The current and the last cached offset in milliseconds.
249 int32_t offsetMilliseconds;
250 int32_t oldOffsetMilliseconds;
252 void reset();
254 void sanityCheck();
257 bool forceUTC_;
259 enum class TimeZoneStatus : uint8_t { Valid, NeedsUpdate, UpdateIfChanged };
261 TimeZoneStatus timeZoneStatus_;
264 * The offset in seconds from the current UTC time to the current local
265 * standard time (i.e. not including any offset due to DST) as computed by the
266 * operating system.
268 * Cached because retrieving this dynamically is Slow, and a certain venerable
269 * benchmark which shall not be named depends on it being fast.
271 * SpiderMonkey occasionally and arbitrarily updates this value from the
272 * system time zone to attempt to keep this reasonably up-to-date. If
273 * temporary inaccuracy can't be tolerated, JSAPI clients may call
274 * JS::ResetTimeZone to forcibly sync this with the system time zone.
276 * In most cases this value is consistent with the raw time zone offset as
277 * returned by the ICU default time zone (`icu::TimeZone::getRawOffset()`),
278 * but it is possible to create cases where the operating system default time
279 * zone differs from the ICU default time zone. For example ICU doesn't
280 * support the full range of TZ environment variable settings, which can
281 * result in <ctime> returning a different time zone than what's returned by
282 * ICU. One example is "TZ=WGT3WGST,M3.5.0/-2,M10.5.0/-1", where <ctime>
283 * returns -3 hours as the local offset, but ICU flat out rejects the TZ value
284 * and instead infers the default time zone via "/etc/localtime" (on Unix).
285 * This offset can also differ from ICU when the operating system and ICU use
286 * different tzdata versions and the time zone rules of the current system
287 * time zone have changed. Or, on Windows, when the Windows default time zone
288 * can't be mapped to a IANA time zone, see for example
289 * <https://unicode-org.atlassian.net/browse/ICU-13845>.
291 * When ICU is exclusively used for time zone computations, that means when
292 * |JS_HAS_INTL_API| is true, this field is only used to detect system default
293 * time zone changes. It must not be used to convert between local and UTC
294 * time, because, as outlined above, this could lead to different results when
295 * compared to ICU.
297 int32_t utcToLocalStandardOffsetSeconds_;
299 RangeCache dstRange_; // UTC-based ranges
301 #if JS_HAS_INTL_API
302 // Use the full date-time range when we can use mozilla::intl::TimeZone.
303 static constexpr int64_t MinTimeT =
304 static_cast<int64_t>(StartOfTime / msPerSecond);
305 static constexpr int64_t MaxTimeT =
306 static_cast<int64_t>(EndOfTime / msPerSecond);
308 RangeCache utcRange_; // localtime-based ranges
309 RangeCache localRange_; // UTC-based ranges
312 * The current time zone. Lazily constructed to avoid potential I/O access
313 * when initializing this class.
315 mozilla::UniquePtr<mozilla::intl::TimeZone> timeZone_;
318 * Cached names of the standard and daylight savings display names of the
319 * current time zone for the default locale.
321 JS::UniqueChars locale_;
322 JS::UniqueTwoByteChars standardName_;
323 JS::UniqueTwoByteChars daylightSavingsName_;
324 #else
325 // Restrict the data-time range to the minimum required time_t range as
326 // specified in POSIX. Most operating systems support 64-bit time_t
327 // values, but we currently still have some configurations which use
328 // 32-bit time_t, e.g. the ARM simulator on 32-bit Linux (bug 1406993).
329 // Bug 1406992 explores to use 64-bit time_t when supported by the
330 // underlying operating system.
331 static constexpr int64_t MinTimeT = 0; /* time_t 01/01/1970 */
332 static constexpr int64_t MaxTimeT = 2145830400; /* time_t 12/31/2037 */
333 #endif /* JS_HAS_INTL_API */
335 static constexpr int64_t RangeExpansionAmount = 30 * SecondsPerDay;
337 void internalResetTimeZone(ResetTimeZoneMode mode);
339 void updateTimeZone();
341 void internalResyncICUDefaultTimeZone();
343 int64_t toClampedSeconds(int64_t milliseconds);
345 using ComputeFn = int32_t (DateTimeInfo::*)(int64_t);
348 * Get or compute an offset value for the requested seconds value.
350 int32_t getOrComputeValue(RangeCache& range, int64_t seconds,
351 ComputeFn compute);
354 * Compute the DST offset at the given UTC time in seconds from the epoch.
355 * (getDSTOffsetMilliseconds attempts to return a cached value from the
356 * dstRange_ member, but in case of a cache miss it calls this method.)
358 int32_t computeDSTOffsetMilliseconds(int64_t utcSeconds);
360 int32_t internalGetDSTOffsetMilliseconds(int64_t utcMilliseconds);
362 #if JS_HAS_INTL_API
364 * Compute the UTC offset in milliseconds for the given local time. Called
365 * by internalGetOffsetMilliseconds on a cache miss.
367 int32_t computeUTCOffsetMilliseconds(int64_t localSeconds);
370 * Compute the local time offset in milliseconds for the given UTC time.
371 * Called by internalGetOffsetMilliseconds on a cache miss.
373 int32_t computeLocalOffsetMilliseconds(int64_t utcSeconds);
375 int32_t internalGetOffsetMilliseconds(int64_t milliseconds,
376 TimeZoneOffset offset);
378 bool internalTimeZoneDisplayName(char16_t* buf, size_t buflen,
379 int64_t utcMilliseconds, const char* locale);
381 mozilla::intl::TimeZone* timeZone();
382 #endif /* JS_HAS_INTL_API */
385 } /* namespace js */
387 #endif /* vm_DateTime_h */