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/. */
10 #include "mozilla/UniquePtr.h"
14 #include "js/Utility.h"
15 #include "threading/ExclusiveData.h"
18 # include "mozilla/intl/ICU4CGlue.h"
19 # include "mozilla/intl/TimeZone.h"
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
;
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
,
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
66 extern void ResetTimeZoneInternal(ResetTimeZoneMode mode
);
69 * Stores date/time information, particularly concerning the current local
70 * time zone, and implements a small cache for daylight saving time offset
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.)
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
};
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
);
132 static auto acquireLockWithValidTimeZone(ForceUTC forceUTC
) {
134 forceUTC
== ForceUTC::Yes
? instanceUTC
->lock() : instance
->lock();
135 if (guard
->timeZoneStatus_
!= TimeZoneStatus::Valid
) {
136 guard
->updateTimeZone();
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
165 static int32_t utcToLocalStandardOffsetSeconds(ForceUTC forceUTC
) {
166 auto guard
= acquireLockWithValidTimeZone(forceUTC
);
167 return guard
->utcToLocalStandardOffsetSeconds_
;
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
,
198 * Copy the identifier for the current time zone to the provided resizable
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(
212 auto guard
= acquireLockWithValidTimeZone(forceUTC
);
213 return guard
->timeZone()->GetRawOffsetMs();
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 */
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
);
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
;
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
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
297 int32_t utcToLocalStandardOffsetSeconds_
;
299 RangeCache dstRange_
; // UTC-based ranges
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_
;
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
,
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
);
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 */
387 #endif /* vm_DateTime_h */