1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/js/src/vm/DateTime.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,286 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- 1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99: 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "vm/DateTime.h" 1.11 + 1.12 +#include <time.h> 1.13 + 1.14 +#include "jsutil.h" 1.15 + 1.16 +using mozilla::UnspecifiedNaN; 1.17 + 1.18 +static bool 1.19 +ComputeLocalTime(time_t local, struct tm *ptm) 1.20 +{ 1.21 +#ifdef HAVE_LOCALTIME_R 1.22 + return localtime_r(&local, ptm); 1.23 +#else 1.24 + struct tm *otm = localtime(&local); 1.25 + if (!otm) 1.26 + return false; 1.27 + *ptm = *otm; 1.28 + return true; 1.29 +#endif 1.30 +} 1.31 + 1.32 +static bool 1.33 +ComputeUTCTime(time_t t, struct tm *ptm) 1.34 +{ 1.35 +#ifdef HAVE_GMTIME_R 1.36 + return gmtime_r(&t, ptm); 1.37 +#else 1.38 + struct tm *otm = gmtime(&t); 1.39 + if (!otm) 1.40 + return false; 1.41 + *ptm = *otm; 1.42 + return true; 1.43 +#endif 1.44 +} 1.45 + 1.46 +/* 1.47 + * Compute the offset in seconds from the current UTC time to the current local 1.48 + * standard time (i.e. not including any offset due to DST). 1.49 + * 1.50 + * Examples: 1.51 + * 1.52 + * Suppose we are in California, USA on January 1, 2013 at 04:00 PST (UTC-8, no 1.53 + * DST in effect), corresponding to 12:00 UTC. This function would then return 1.54 + * -8 * SecondsPerHour, or -28800. 1.55 + * 1.56 + * Or suppose we are in Berlin, Germany on July 1, 2013 at 17:00 CEST (UTC+2, 1.57 + * DST in effect), corresponding to 15:00 UTC. This function would then return 1.58 + * +1 * SecondsPerHour, or +3600. 1.59 + */ 1.60 +static int32_t 1.61 +UTCToLocalStandardOffsetSeconds() 1.62 +{ 1.63 + using js::SecondsPerDay; 1.64 + using js::SecondsPerHour; 1.65 + using js::SecondsPerMinute; 1.66 + 1.67 +#if defined(XP_WIN) 1.68 + // Windows doesn't follow POSIX: updates to the TZ environment variable are 1.69 + // not reflected immediately on that platform as they are on other systems 1.70 + // without this call. 1.71 + _tzset(); 1.72 +#endif 1.73 + 1.74 + // Get the current time. 1.75 + time_t currentMaybeWithDST = time(nullptr); 1.76 + if (currentMaybeWithDST == time_t(-1)) 1.77 + return 0; 1.78 + 1.79 + // Break down the current time into its (locally-valued, maybe with DST) 1.80 + // components. 1.81 + struct tm local; 1.82 + if (!ComputeLocalTime(currentMaybeWithDST, &local)) 1.83 + return 0; 1.84 + 1.85 + // Compute a |time_t| corresponding to |local| interpreted without DST. 1.86 + time_t currentNoDST; 1.87 + if (local.tm_isdst == 0) { 1.88 + // If |local| wasn't DST, we can use the same time. 1.89 + currentNoDST = currentMaybeWithDST; 1.90 + } else { 1.91 + // If |local| respected DST, we need a time broken down into components 1.92 + // ignoring DST. Turn off DST in the broken-down time. 1.93 + local.tm_isdst = 0; 1.94 + 1.95 + // Compute a |time_t t| corresponding to the broken-down time with DST 1.96 + // off. This has boundary-condition issues (for about the duration of 1.97 + // a DST offset) near the time a location moves to a different time 1.98 + // zone. But 1) errors will be transient; 2) locations rarely change 1.99 + // time zone; and 3) in the absence of an API that provides the time 1.100 + // zone offset directly, this may be the best we can do. 1.101 + currentNoDST = mktime(&local); 1.102 + if (currentNoDST == time_t(-1)) 1.103 + return 0; 1.104 + } 1.105 + 1.106 + // Break down the time corresponding to the no-DST |local| into UTC-based 1.107 + // components. 1.108 + struct tm utc; 1.109 + if (!ComputeUTCTime(currentNoDST, &utc)) 1.110 + return 0; 1.111 + 1.112 + // Finally, compare the seconds-based components of the local non-DST 1.113 + // representation and the UTC representation to determine the actual 1.114 + // difference. 1.115 + int utc_secs = utc.tm_hour * SecondsPerHour + utc.tm_min * SecondsPerMinute; 1.116 + int local_secs = local.tm_hour * SecondsPerHour + local.tm_min * SecondsPerMinute; 1.117 + 1.118 + // Same-day? Just subtract the seconds counts. 1.119 + if (utc.tm_mday == local.tm_mday) 1.120 + return local_secs - utc_secs; 1.121 + 1.122 + // If we have more UTC seconds, move local seconds into the UTC seconds' 1.123 + // frame of reference and then subtract. 1.124 + if (utc_secs > local_secs) 1.125 + return (SecondsPerDay + local_secs) - utc_secs; 1.126 + 1.127 + // Otherwise we have more local seconds, so move the UTC seconds into the 1.128 + // local seconds' frame of reference and then subtract. 1.129 + return local_secs - (utc_secs + SecondsPerDay); 1.130 +} 1.131 + 1.132 +void 1.133 +js::DateTimeInfo::updateTimeZoneAdjustment() 1.134 +{ 1.135 + /* 1.136 + * The difference between local standard time and UTC will never change for 1.137 + * a given time zone. 1.138 + */ 1.139 + utcToLocalStandardOffsetSeconds = UTCToLocalStandardOffsetSeconds(); 1.140 + 1.141 + double newTZA = utcToLocalStandardOffsetSeconds * msPerSecond; 1.142 + if (newTZA == localTZA_) 1.143 + return; 1.144 + 1.145 + localTZA_ = newTZA; 1.146 + 1.147 + /* 1.148 + * The initial range values are carefully chosen to result in a cache miss 1.149 + * on first use given the range of possible values. Be careful to keep 1.150 + * these values and the caching algorithm in sync! 1.151 + */ 1.152 + offsetMilliseconds = 0; 1.153 + rangeStartSeconds = rangeEndSeconds = INT64_MIN; 1.154 + oldOffsetMilliseconds = 0; 1.155 + oldRangeStartSeconds = oldRangeEndSeconds = INT64_MIN; 1.156 + 1.157 + sanityCheck(); 1.158 +} 1.159 + 1.160 +/* 1.161 + * Since getDSTOffsetMilliseconds guarantees that all times seen will be 1.162 + * positive, we can initialize the range at construction time with large 1.163 + * negative numbers to ensure the first computation is always a cache miss and 1.164 + * doesn't return a bogus offset. 1.165 + */ 1.166 +js::DateTimeInfo::DateTimeInfo() 1.167 +{ 1.168 + // Set to a totally impossible TZA so that the comparison above will fail 1.169 + // and all fields will be properly initialized. 1.170 + localTZA_ = UnspecifiedNaN<double>(); 1.171 + updateTimeZoneAdjustment(); 1.172 +} 1.173 + 1.174 +int64_t 1.175 +js::DateTimeInfo::computeDSTOffsetMilliseconds(int64_t utcSeconds) 1.176 +{ 1.177 + MOZ_ASSERT(utcSeconds >= 0); 1.178 + MOZ_ASSERT(utcSeconds <= MaxUnixTimeT); 1.179 + 1.180 +#if defined(XP_WIN) 1.181 + // Windows does not follow POSIX. Updates to the TZ environment variable 1.182 + // are not reflected immediately on that platform as they are on UNIX 1.183 + // systems without this call. 1.184 + _tzset(); 1.185 +#endif 1.186 + 1.187 + struct tm tm; 1.188 + if (!ComputeLocalTime(static_cast<time_t>(utcSeconds), &tm)) 1.189 + return 0; 1.190 + 1.191 + int32_t dayoff = int32_t((utcSeconds + utcToLocalStandardOffsetSeconds) % SecondsPerDay); 1.192 + int32_t tmoff = tm.tm_sec + (tm.tm_min * SecondsPerMinute) + (tm.tm_hour * SecondsPerHour); 1.193 + 1.194 + int32_t diff = tmoff - dayoff; 1.195 + 1.196 + if (diff < 0) 1.197 + diff += SecondsPerDay; 1.198 + 1.199 + return diff * msPerSecond; 1.200 +} 1.201 + 1.202 +int64_t 1.203 +js::DateTimeInfo::getDSTOffsetMilliseconds(int64_t utcMilliseconds) 1.204 +{ 1.205 + sanityCheck(); 1.206 + 1.207 + int64_t utcSeconds = utcMilliseconds / msPerSecond; 1.208 + 1.209 + if (utcSeconds > MaxUnixTimeT) { 1.210 + utcSeconds = MaxUnixTimeT; 1.211 + } else if (utcSeconds < 0) { 1.212 + /* Go ahead a day to make localtime work (does not work with 0). */ 1.213 + utcSeconds = SecondsPerDay; 1.214 + } 1.215 + 1.216 + /* 1.217 + * NB: Be aware of the initial range values when making changes to this 1.218 + * code: the first call to this method, with those initial range 1.219 + * values, must result in a cache miss. 1.220 + */ 1.221 + 1.222 + if (rangeStartSeconds <= utcSeconds && utcSeconds <= rangeEndSeconds) 1.223 + return offsetMilliseconds; 1.224 + 1.225 + if (oldRangeStartSeconds <= utcSeconds && utcSeconds <= oldRangeEndSeconds) 1.226 + return oldOffsetMilliseconds; 1.227 + 1.228 + oldOffsetMilliseconds = offsetMilliseconds; 1.229 + oldRangeStartSeconds = rangeStartSeconds; 1.230 + oldRangeEndSeconds = rangeEndSeconds; 1.231 + 1.232 + if (rangeStartSeconds <= utcSeconds) { 1.233 + int64_t newEndSeconds = Min(rangeEndSeconds + RangeExpansionAmount, MaxUnixTimeT); 1.234 + if (newEndSeconds >= utcSeconds) { 1.235 + int64_t endOffsetMilliseconds = computeDSTOffsetMilliseconds(newEndSeconds); 1.236 + if (endOffsetMilliseconds == offsetMilliseconds) { 1.237 + rangeEndSeconds = newEndSeconds; 1.238 + return offsetMilliseconds; 1.239 + } 1.240 + 1.241 + offsetMilliseconds = computeDSTOffsetMilliseconds(utcSeconds); 1.242 + if (offsetMilliseconds == endOffsetMilliseconds) { 1.243 + rangeStartSeconds = utcSeconds; 1.244 + rangeEndSeconds = newEndSeconds; 1.245 + } else { 1.246 + rangeEndSeconds = utcSeconds; 1.247 + } 1.248 + return offsetMilliseconds; 1.249 + } 1.250 + 1.251 + offsetMilliseconds = computeDSTOffsetMilliseconds(utcSeconds); 1.252 + rangeStartSeconds = rangeEndSeconds = utcSeconds; 1.253 + return offsetMilliseconds; 1.254 + } 1.255 + 1.256 + int64_t newStartSeconds = Max<int64_t>(rangeStartSeconds - RangeExpansionAmount, 0); 1.257 + if (newStartSeconds <= utcSeconds) { 1.258 + int64_t startOffsetMilliseconds = computeDSTOffsetMilliseconds(newStartSeconds); 1.259 + if (startOffsetMilliseconds == offsetMilliseconds) { 1.260 + rangeStartSeconds = newStartSeconds; 1.261 + return offsetMilliseconds; 1.262 + } 1.263 + 1.264 + offsetMilliseconds = computeDSTOffsetMilliseconds(utcSeconds); 1.265 + if (offsetMilliseconds == startOffsetMilliseconds) { 1.266 + rangeStartSeconds = newStartSeconds; 1.267 + rangeEndSeconds = utcSeconds; 1.268 + } else { 1.269 + rangeStartSeconds = utcSeconds; 1.270 + } 1.271 + return offsetMilliseconds; 1.272 + } 1.273 + 1.274 + rangeStartSeconds = rangeEndSeconds = utcSeconds; 1.275 + offsetMilliseconds = computeDSTOffsetMilliseconds(utcSeconds); 1.276 + return offsetMilliseconds; 1.277 +} 1.278 + 1.279 +void 1.280 +js::DateTimeInfo::sanityCheck() 1.281 +{ 1.282 + MOZ_ASSERT(rangeStartSeconds <= rangeEndSeconds); 1.283 + MOZ_ASSERT_IF(rangeStartSeconds == INT64_MIN, rangeEndSeconds == INT64_MIN); 1.284 + MOZ_ASSERT_IF(rangeEndSeconds == INT64_MIN, rangeStartSeconds == INT64_MIN); 1.285 + MOZ_ASSERT_IF(rangeStartSeconds != INT64_MIN, 1.286 + rangeStartSeconds >= 0 && rangeEndSeconds >= 0); 1.287 + MOZ_ASSERT_IF(rangeStartSeconds != INT64_MIN, 1.288 + rangeStartSeconds <= MaxUnixTimeT && rangeEndSeconds <= MaxUnixTimeT); 1.289 +}