Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
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 #include "vm/DateTime.h"
9 #include <time.h>
11 #include "jsutil.h"
13 using mozilla::UnspecifiedNaN;
15 static bool
16 ComputeLocalTime(time_t local, struct tm *ptm)
17 {
18 #ifdef HAVE_LOCALTIME_R
19 return localtime_r(&local, ptm);
20 #else
21 struct tm *otm = localtime(&local);
22 if (!otm)
23 return false;
24 *ptm = *otm;
25 return true;
26 #endif
27 }
29 static bool
30 ComputeUTCTime(time_t t, struct tm *ptm)
31 {
32 #ifdef HAVE_GMTIME_R
33 return gmtime_r(&t, ptm);
34 #else
35 struct tm *otm = gmtime(&t);
36 if (!otm)
37 return false;
38 *ptm = *otm;
39 return true;
40 #endif
41 }
43 /*
44 * Compute the offset in seconds from the current UTC time to the current local
45 * standard time (i.e. not including any offset due to DST).
46 *
47 * Examples:
48 *
49 * Suppose we are in California, USA on January 1, 2013 at 04:00 PST (UTC-8, no
50 * DST in effect), corresponding to 12:00 UTC. This function would then return
51 * -8 * SecondsPerHour, or -28800.
52 *
53 * Or suppose we are in Berlin, Germany on July 1, 2013 at 17:00 CEST (UTC+2,
54 * DST in effect), corresponding to 15:00 UTC. This function would then return
55 * +1 * SecondsPerHour, or +3600.
56 */
57 static int32_t
58 UTCToLocalStandardOffsetSeconds()
59 {
60 using js::SecondsPerDay;
61 using js::SecondsPerHour;
62 using js::SecondsPerMinute;
64 #if defined(XP_WIN)
65 // Windows doesn't follow POSIX: updates to the TZ environment variable are
66 // not reflected immediately on that platform as they are on other systems
67 // without this call.
68 _tzset();
69 #endif
71 // Get the current time.
72 time_t currentMaybeWithDST = time(nullptr);
73 if (currentMaybeWithDST == time_t(-1))
74 return 0;
76 // Break down the current time into its (locally-valued, maybe with DST)
77 // components.
78 struct tm local;
79 if (!ComputeLocalTime(currentMaybeWithDST, &local))
80 return 0;
82 // Compute a |time_t| corresponding to |local| interpreted without DST.
83 time_t currentNoDST;
84 if (local.tm_isdst == 0) {
85 // If |local| wasn't DST, we can use the same time.
86 currentNoDST = currentMaybeWithDST;
87 } else {
88 // If |local| respected DST, we need a time broken down into components
89 // ignoring DST. Turn off DST in the broken-down time.
90 local.tm_isdst = 0;
92 // Compute a |time_t t| corresponding to the broken-down time with DST
93 // off. This has boundary-condition issues (for about the duration of
94 // a DST offset) near the time a location moves to a different time
95 // zone. But 1) errors will be transient; 2) locations rarely change
96 // time zone; and 3) in the absence of an API that provides the time
97 // zone offset directly, this may be the best we can do.
98 currentNoDST = mktime(&local);
99 if (currentNoDST == time_t(-1))
100 return 0;
101 }
103 // Break down the time corresponding to the no-DST |local| into UTC-based
104 // components.
105 struct tm utc;
106 if (!ComputeUTCTime(currentNoDST, &utc))
107 return 0;
109 // Finally, compare the seconds-based components of the local non-DST
110 // representation and the UTC representation to determine the actual
111 // difference.
112 int utc_secs = utc.tm_hour * SecondsPerHour + utc.tm_min * SecondsPerMinute;
113 int local_secs = local.tm_hour * SecondsPerHour + local.tm_min * SecondsPerMinute;
115 // Same-day? Just subtract the seconds counts.
116 if (utc.tm_mday == local.tm_mday)
117 return local_secs - utc_secs;
119 // If we have more UTC seconds, move local seconds into the UTC seconds'
120 // frame of reference and then subtract.
121 if (utc_secs > local_secs)
122 return (SecondsPerDay + local_secs) - utc_secs;
124 // Otherwise we have more local seconds, so move the UTC seconds into the
125 // local seconds' frame of reference and then subtract.
126 return local_secs - (utc_secs + SecondsPerDay);
127 }
129 void
130 js::DateTimeInfo::updateTimeZoneAdjustment()
131 {
132 /*
133 * The difference between local standard time and UTC will never change for
134 * a given time zone.
135 */
136 utcToLocalStandardOffsetSeconds = UTCToLocalStandardOffsetSeconds();
138 double newTZA = utcToLocalStandardOffsetSeconds * msPerSecond;
139 if (newTZA == localTZA_)
140 return;
142 localTZA_ = newTZA;
144 /*
145 * The initial range values are carefully chosen to result in a cache miss
146 * on first use given the range of possible values. Be careful to keep
147 * these values and the caching algorithm in sync!
148 */
149 offsetMilliseconds = 0;
150 rangeStartSeconds = rangeEndSeconds = INT64_MIN;
151 oldOffsetMilliseconds = 0;
152 oldRangeStartSeconds = oldRangeEndSeconds = INT64_MIN;
154 sanityCheck();
155 }
157 /*
158 * Since getDSTOffsetMilliseconds guarantees that all times seen will be
159 * positive, we can initialize the range at construction time with large
160 * negative numbers to ensure the first computation is always a cache miss and
161 * doesn't return a bogus offset.
162 */
163 js::DateTimeInfo::DateTimeInfo()
164 {
165 // Set to a totally impossible TZA so that the comparison above will fail
166 // and all fields will be properly initialized.
167 localTZA_ = UnspecifiedNaN<double>();
168 updateTimeZoneAdjustment();
169 }
171 int64_t
172 js::DateTimeInfo::computeDSTOffsetMilliseconds(int64_t utcSeconds)
173 {
174 MOZ_ASSERT(utcSeconds >= 0);
175 MOZ_ASSERT(utcSeconds <= MaxUnixTimeT);
177 #if defined(XP_WIN)
178 // Windows does not follow POSIX. Updates to the TZ environment variable
179 // are not reflected immediately on that platform as they are on UNIX
180 // systems without this call.
181 _tzset();
182 #endif
184 struct tm tm;
185 if (!ComputeLocalTime(static_cast<time_t>(utcSeconds), &tm))
186 return 0;
188 int32_t dayoff = int32_t((utcSeconds + utcToLocalStandardOffsetSeconds) % SecondsPerDay);
189 int32_t tmoff = tm.tm_sec + (tm.tm_min * SecondsPerMinute) + (tm.tm_hour * SecondsPerHour);
191 int32_t diff = tmoff - dayoff;
193 if (diff < 0)
194 diff += SecondsPerDay;
196 return diff * msPerSecond;
197 }
199 int64_t
200 js::DateTimeInfo::getDSTOffsetMilliseconds(int64_t utcMilliseconds)
201 {
202 sanityCheck();
204 int64_t utcSeconds = utcMilliseconds / msPerSecond;
206 if (utcSeconds > MaxUnixTimeT) {
207 utcSeconds = MaxUnixTimeT;
208 } else if (utcSeconds < 0) {
209 /* Go ahead a day to make localtime work (does not work with 0). */
210 utcSeconds = SecondsPerDay;
211 }
213 /*
214 * NB: Be aware of the initial range values when making changes to this
215 * code: the first call to this method, with those initial range
216 * values, must result in a cache miss.
217 */
219 if (rangeStartSeconds <= utcSeconds && utcSeconds <= rangeEndSeconds)
220 return offsetMilliseconds;
222 if (oldRangeStartSeconds <= utcSeconds && utcSeconds <= oldRangeEndSeconds)
223 return oldOffsetMilliseconds;
225 oldOffsetMilliseconds = offsetMilliseconds;
226 oldRangeStartSeconds = rangeStartSeconds;
227 oldRangeEndSeconds = rangeEndSeconds;
229 if (rangeStartSeconds <= utcSeconds) {
230 int64_t newEndSeconds = Min(rangeEndSeconds + RangeExpansionAmount, MaxUnixTimeT);
231 if (newEndSeconds >= utcSeconds) {
232 int64_t endOffsetMilliseconds = computeDSTOffsetMilliseconds(newEndSeconds);
233 if (endOffsetMilliseconds == offsetMilliseconds) {
234 rangeEndSeconds = newEndSeconds;
235 return offsetMilliseconds;
236 }
238 offsetMilliseconds = computeDSTOffsetMilliseconds(utcSeconds);
239 if (offsetMilliseconds == endOffsetMilliseconds) {
240 rangeStartSeconds = utcSeconds;
241 rangeEndSeconds = newEndSeconds;
242 } else {
243 rangeEndSeconds = utcSeconds;
244 }
245 return offsetMilliseconds;
246 }
248 offsetMilliseconds = computeDSTOffsetMilliseconds(utcSeconds);
249 rangeStartSeconds = rangeEndSeconds = utcSeconds;
250 return offsetMilliseconds;
251 }
253 int64_t newStartSeconds = Max<int64_t>(rangeStartSeconds - RangeExpansionAmount, 0);
254 if (newStartSeconds <= utcSeconds) {
255 int64_t startOffsetMilliseconds = computeDSTOffsetMilliseconds(newStartSeconds);
256 if (startOffsetMilliseconds == offsetMilliseconds) {
257 rangeStartSeconds = newStartSeconds;
258 return offsetMilliseconds;
259 }
261 offsetMilliseconds = computeDSTOffsetMilliseconds(utcSeconds);
262 if (offsetMilliseconds == startOffsetMilliseconds) {
263 rangeStartSeconds = newStartSeconds;
264 rangeEndSeconds = utcSeconds;
265 } else {
266 rangeStartSeconds = utcSeconds;
267 }
268 return offsetMilliseconds;
269 }
271 rangeStartSeconds = rangeEndSeconds = utcSeconds;
272 offsetMilliseconds = computeDSTOffsetMilliseconds(utcSeconds);
273 return offsetMilliseconds;
274 }
276 void
277 js::DateTimeInfo::sanityCheck()
278 {
279 MOZ_ASSERT(rangeStartSeconds <= rangeEndSeconds);
280 MOZ_ASSERT_IF(rangeStartSeconds == INT64_MIN, rangeEndSeconds == INT64_MIN);
281 MOZ_ASSERT_IF(rangeEndSeconds == INT64_MIN, rangeStartSeconds == INT64_MIN);
282 MOZ_ASSERT_IF(rangeStartSeconds != INT64_MIN,
283 rangeStartSeconds >= 0 && rangeEndSeconds >= 0);
284 MOZ_ASSERT_IF(rangeStartSeconds != INT64_MIN,
285 rangeStartSeconds <= MaxUnixTimeT && rangeEndSeconds <= MaxUnixTimeT);
286 }