summaryrefslogtreecommitdiff
path: root/js/src/vm/DateTime.h
blob: 2a0acac3e3c208587811295c7ea6e623e21bf434 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * 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 vm_DateTime_h
#define vm_DateTime_h

#include "mozilla/Assertions.h"
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/MathAlgorithms.h"

#include <stdint.h>

#include "js/Conversions.h"
#include "js/Date.h"
#include "js/Initialization.h"
#include "js/Value.h"

namespace js {

/* Constants defined by ES5 15.9.1.10. */
const double HoursPerDay = 24;
const double MinutesPerHour = 60;
const double SecondsPerMinute = 60;
const double msPerSecond = 1000;
const double msPerMinute = msPerSecond * SecondsPerMinute;
const double msPerHour = msPerMinute * MinutesPerHour;

/* ES5 15.9.1.2. */
const double msPerDay = msPerHour * HoursPerDay;

/*
 * Additional quantities not mentioned in the spec.  Be careful using these!
 * They aren't doubles (and aren't defined in terms of all the other constants
 * so that they can be used in constexpr scenarios; if you need constants that
 * trigger floating point semantics, you'll have to manually cast to get it.
 */
const unsigned SecondsPerHour = 60 * 60;
const unsigned SecondsPerDay = SecondsPerHour * 24;

const double StartOfTime = -8.64e15;
const double EndOfTime = 8.64e15;

/*
 * Stores date/time information, particularly concerning the current local
 * time zone, and implements a small cache for daylight saving time offset
 * computation.
 *
 * The basic idea is premised upon this fact: the DST offset never changes more
 * than once in any thirty-day period.  If we know the offset at t_0 is o_0,
 * the offset at [t_1, t_2] is also o_0, where t_1 + 3_0 days == t_2,
 * t_1 <= t_0, and t0 <= t2.  (In other words, t_0 is always somewhere within a
 * thirty-day range where the DST offset is constant: DST changes never occur
 * more than once in any thirty-day period.)  Therefore, if we intelligently
 * retain knowledge of the offset for a range of dates (which may vary over
 * time), and if requests are usually for dates within that range, we can often
 * provide a response without repeated offset calculation.
 *
 * Our caching strategy is as follows: on the first request at date t_0 compute
 * the requested offset o_0.  Save { start: t_0, end: t_0, offset: o_0 } as the
 * cache's state.  Subsequent requests within that range are straightforwardly
 * handled.  If a request for t_i is far outside the range (more than thirty
 * days), compute o_i = dstOffset(t_i) and save { start: t_i, end: t_i,
 * offset: t_i }.  Otherwise attempt to *overextend* the range to either
 * [start - 30d, end] or [start, end + 30d] as appropriate to encompass
 * t_i.  If the offset o_i30 is the same as the cached offset, extend the
 * range.  Otherwise the over-guess crossed a DST change -- compute
 * o_i = dstOffset(t_i) and either extend the original range (if o_i == offset)
 * or start a new one beneath/above the current one with o_i30 as the offset.
 *
 * This cache strategy results in 0 to 2 DST offset computations.  The naive
 * always-compute strategy is 1 computation, and since cache maintenance is a
 * handful of integer arithmetic instructions the speed difference between
 * always-1 and 1-with-cache is negligible.  Caching loses if two computations
 * happen: when the date is within 30 days of the cached range and when that
 * 30-day range crosses a DST change.  This is relatively uncommon.  Further,
 * instances of such are often dominated by in-range hits, so caching is an
 * overall slight win.
 *
 * Why 30 days?  For correctness the duration must be smaller than any possible
 * duration between DST changes.  Past that, note that 1) a large duration
 * increases the likelihood of crossing a DST change while reducing the number
 * of cache misses, and 2) a small duration decreases the size of the cached
 * range while producing more misses.  Using a month as the interval change is
 * a balance between these two that tries to optimize for the calendar month at
 * a time that a site might display.  (One could imagine an adaptive duration
 * that accommodates near-DST-change dates better; we don't believe the
 * potential win from better caching offsets the loss from extra complexity.)
 */
class DateTimeInfo
{
    static DateTimeInfo instance;

    // Date/time info is shared across all threads in DateTimeInfo::instance,
    // for consistency with ICU's handling of its default time zone.  Thus we
    // need something to protect concurrent accesses.
    //
    // The spec implicitly assumes DST and time zone adjustment information
    // never change in the course of a function -- sometimes even across
    // reentrancy.  So make critical sections as narrow as possible, and use a
    // bog-standard spinlock with busy-waiting in case of contention for
    // simplicity.
    class MOZ_RAII AcquireLock
    {
        static mozilla::Atomic<bool, mozilla::ReleaseAcquire> spinLock;

      public:
        AcquireLock() {
            while (!spinLock.compareExchange(false, true))
                continue;
        }
        ~AcquireLock() {
            MOZ_ASSERT(spinLock, "spinlock should have been acquired");
            spinLock = false;
        }
    };

    friend const char* JS::detail::InitWithFailureDiagnostic(bool);

    // Initialize global date/time tracking state.  This operation occurs
    // during, and is restricted to, SpiderMonkey initialization.
    static void init();

  public:
    /*
     * Get the DST offset in milliseconds at a UTC time.  This is usually
     * either 0 or |msPerSecond * SecondsPerHour|, but at least one exotic time
     * zone (Lord Howe Island, Australia) has a fractional-hour offset, just to
     * keep things interesting.
     */
    static int64_t getDSTOffsetMilliseconds(int64_t utcMilliseconds) {
        AcquireLock lock;

        return DateTimeInfo::instance.internalGetDSTOffsetMilliseconds(utcMilliseconds);
    }

    /* ES5 15.9.1.7. */
    static double localTZA() {
        AcquireLock lock;

        return DateTimeInfo::instance.localTZA_;
    }

  private:
    // We don't want anyone accidentally calling *only*
    // DateTimeInfo::updateTimeZoneAdjustment() to respond to a system time
    // zone change (missing the necessary poking of ICU as well), so ensure
    // only JS::ResetTimeZone() can call this via access restrictions.
    friend void JS::ResetTimeZone();

    static void updateTimeZoneAdjustment() {
        AcquireLock lock;

        DateTimeInfo::instance.internalUpdateTimeZoneAdjustment();
    }

    /*
     * The current local time zone adjustment, cached because retrieving this
     * dynamically is Slow, and a certain venerable benchmark which shall not
     * be named depends on it being fast.
     *
     * SpiderMonkey occasionally and arbitrarily updates this value from the
     * system time zone to attempt to keep this reasonably up-to-date.  If
     * temporary inaccuracy can't be tolerated, JSAPI clients may call
     * JS::ResetTimeZone to forcibly sync this with the system time zone.
     */
    double localTZA_;

    /*
     * Compute the DST offset at the given UTC time in seconds from the epoch.
     * (getDSTOffsetMilliseconds attempts to return a cached value, but in case
     * of a cache miss it calls this method.  The cache is represented through
     * the offset* and *{Start,End}Seconds fields below.)
     */
    int64_t computeDSTOffsetMilliseconds(int64_t utcSeconds);

    int64_t offsetMilliseconds;
    int64_t rangeStartSeconds, rangeEndSeconds; // UTC-based

    int64_t oldOffsetMilliseconds;
    int64_t oldRangeStartSeconds, oldRangeEndSeconds; // UTC-based

    /*
     * Cached offset in seconds from the current UTC time to the current
     * local standard time (i.e. not including any offset due to DST).
     */
    int32_t utcToLocalStandardOffsetSeconds;

    static const int64_t MaxUnixTimeT = 2145859200; /* time_t 12/31/2037 */

    static const int64_t RangeExpansionAmount = 30 * SecondsPerDay;

    int64_t internalGetDSTOffsetMilliseconds(int64_t utcMilliseconds);
    void internalUpdateTimeZoneAdjustment();

    void sanityCheck();
};

/**
 * ICU's default time zone, used for various date/time formatting operations
 * that include the local time in the representation, is allowed to go stale
 * for unfortunate performance reasons.  Call this function when an up-to-date
 * default time zone is required, to resync ICU's default time zone with
 * reality.
 */
extern void
ResyncICUDefaultTimeZone();

}  /* namespace js */

#endif /* vm_DateTime_h */