js/src/vm/DateTime.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial