michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "vm/DateTime.h" michael@0: michael@0: #include michael@0: michael@0: #include "jsutil.h" michael@0: michael@0: using mozilla::UnspecifiedNaN; michael@0: michael@0: static bool michael@0: ComputeLocalTime(time_t local, struct tm *ptm) michael@0: { michael@0: #ifdef HAVE_LOCALTIME_R michael@0: return localtime_r(&local, ptm); michael@0: #else michael@0: struct tm *otm = localtime(&local); michael@0: if (!otm) michael@0: return false; michael@0: *ptm = *otm; michael@0: return true; michael@0: #endif michael@0: } michael@0: michael@0: static bool michael@0: ComputeUTCTime(time_t t, struct tm *ptm) michael@0: { michael@0: #ifdef HAVE_GMTIME_R michael@0: return gmtime_r(&t, ptm); michael@0: #else michael@0: struct tm *otm = gmtime(&t); michael@0: if (!otm) michael@0: return false; michael@0: *ptm = *otm; michael@0: return true; michael@0: #endif michael@0: } michael@0: michael@0: /* michael@0: * Compute the offset in seconds from the current UTC time to the current local michael@0: * standard time (i.e. not including any offset due to DST). michael@0: * michael@0: * Examples: michael@0: * michael@0: * Suppose we are in California, USA on January 1, 2013 at 04:00 PST (UTC-8, no michael@0: * DST in effect), corresponding to 12:00 UTC. This function would then return michael@0: * -8 * SecondsPerHour, or -28800. michael@0: * michael@0: * Or suppose we are in Berlin, Germany on July 1, 2013 at 17:00 CEST (UTC+2, michael@0: * DST in effect), corresponding to 15:00 UTC. This function would then return michael@0: * +1 * SecondsPerHour, or +3600. michael@0: */ michael@0: static int32_t michael@0: UTCToLocalStandardOffsetSeconds() michael@0: { michael@0: using js::SecondsPerDay; michael@0: using js::SecondsPerHour; michael@0: using js::SecondsPerMinute; michael@0: michael@0: #if defined(XP_WIN) michael@0: // Windows doesn't follow POSIX: updates to the TZ environment variable are michael@0: // not reflected immediately on that platform as they are on other systems michael@0: // without this call. michael@0: _tzset(); michael@0: #endif michael@0: michael@0: // Get the current time. michael@0: time_t currentMaybeWithDST = time(nullptr); michael@0: if (currentMaybeWithDST == time_t(-1)) michael@0: return 0; michael@0: michael@0: // Break down the current time into its (locally-valued, maybe with DST) michael@0: // components. michael@0: struct tm local; michael@0: if (!ComputeLocalTime(currentMaybeWithDST, &local)) michael@0: return 0; michael@0: michael@0: // Compute a |time_t| corresponding to |local| interpreted without DST. michael@0: time_t currentNoDST; michael@0: if (local.tm_isdst == 0) { michael@0: // If |local| wasn't DST, we can use the same time. michael@0: currentNoDST = currentMaybeWithDST; michael@0: } else { michael@0: // If |local| respected DST, we need a time broken down into components michael@0: // ignoring DST. Turn off DST in the broken-down time. michael@0: local.tm_isdst = 0; michael@0: michael@0: // Compute a |time_t t| corresponding to the broken-down time with DST michael@0: // off. This has boundary-condition issues (for about the duration of michael@0: // a DST offset) near the time a location moves to a different time michael@0: // zone. But 1) errors will be transient; 2) locations rarely change michael@0: // time zone; and 3) in the absence of an API that provides the time michael@0: // zone offset directly, this may be the best we can do. michael@0: currentNoDST = mktime(&local); michael@0: if (currentNoDST == time_t(-1)) michael@0: return 0; michael@0: } michael@0: michael@0: // Break down the time corresponding to the no-DST |local| into UTC-based michael@0: // components. michael@0: struct tm utc; michael@0: if (!ComputeUTCTime(currentNoDST, &utc)) michael@0: return 0; michael@0: michael@0: // Finally, compare the seconds-based components of the local non-DST michael@0: // representation and the UTC representation to determine the actual michael@0: // difference. michael@0: int utc_secs = utc.tm_hour * SecondsPerHour + utc.tm_min * SecondsPerMinute; michael@0: int local_secs = local.tm_hour * SecondsPerHour + local.tm_min * SecondsPerMinute; michael@0: michael@0: // Same-day? Just subtract the seconds counts. michael@0: if (utc.tm_mday == local.tm_mday) michael@0: return local_secs - utc_secs; michael@0: michael@0: // If we have more UTC seconds, move local seconds into the UTC seconds' michael@0: // frame of reference and then subtract. michael@0: if (utc_secs > local_secs) michael@0: return (SecondsPerDay + local_secs) - utc_secs; michael@0: michael@0: // Otherwise we have more local seconds, so move the UTC seconds into the michael@0: // local seconds' frame of reference and then subtract. michael@0: return local_secs - (utc_secs + SecondsPerDay); michael@0: } michael@0: michael@0: void michael@0: js::DateTimeInfo::updateTimeZoneAdjustment() michael@0: { michael@0: /* michael@0: * The difference between local standard time and UTC will never change for michael@0: * a given time zone. michael@0: */ michael@0: utcToLocalStandardOffsetSeconds = UTCToLocalStandardOffsetSeconds(); michael@0: michael@0: double newTZA = utcToLocalStandardOffsetSeconds * msPerSecond; michael@0: if (newTZA == localTZA_) michael@0: return; michael@0: michael@0: localTZA_ = newTZA; michael@0: michael@0: /* michael@0: * The initial range values are carefully chosen to result in a cache miss michael@0: * on first use given the range of possible values. Be careful to keep michael@0: * these values and the caching algorithm in sync! michael@0: */ michael@0: offsetMilliseconds = 0; michael@0: rangeStartSeconds = rangeEndSeconds = INT64_MIN; michael@0: oldOffsetMilliseconds = 0; michael@0: oldRangeStartSeconds = oldRangeEndSeconds = INT64_MIN; michael@0: michael@0: sanityCheck(); michael@0: } michael@0: michael@0: /* michael@0: * Since getDSTOffsetMilliseconds guarantees that all times seen will be michael@0: * positive, we can initialize the range at construction time with large michael@0: * negative numbers to ensure the first computation is always a cache miss and michael@0: * doesn't return a bogus offset. michael@0: */ michael@0: js::DateTimeInfo::DateTimeInfo() michael@0: { michael@0: // Set to a totally impossible TZA so that the comparison above will fail michael@0: // and all fields will be properly initialized. michael@0: localTZA_ = UnspecifiedNaN(); michael@0: updateTimeZoneAdjustment(); michael@0: } michael@0: michael@0: int64_t michael@0: js::DateTimeInfo::computeDSTOffsetMilliseconds(int64_t utcSeconds) michael@0: { michael@0: MOZ_ASSERT(utcSeconds >= 0); michael@0: MOZ_ASSERT(utcSeconds <= MaxUnixTimeT); michael@0: michael@0: #if defined(XP_WIN) michael@0: // Windows does not follow POSIX. Updates to the TZ environment variable michael@0: // are not reflected immediately on that platform as they are on UNIX michael@0: // systems without this call. michael@0: _tzset(); michael@0: #endif michael@0: michael@0: struct tm tm; michael@0: if (!ComputeLocalTime(static_cast(utcSeconds), &tm)) michael@0: return 0; michael@0: michael@0: int32_t dayoff = int32_t((utcSeconds + utcToLocalStandardOffsetSeconds) % SecondsPerDay); michael@0: int32_t tmoff = tm.tm_sec + (tm.tm_min * SecondsPerMinute) + (tm.tm_hour * SecondsPerHour); michael@0: michael@0: int32_t diff = tmoff - dayoff; michael@0: michael@0: if (diff < 0) michael@0: diff += SecondsPerDay; michael@0: michael@0: return diff * msPerSecond; michael@0: } michael@0: michael@0: int64_t michael@0: js::DateTimeInfo::getDSTOffsetMilliseconds(int64_t utcMilliseconds) michael@0: { michael@0: sanityCheck(); michael@0: michael@0: int64_t utcSeconds = utcMilliseconds / msPerSecond; michael@0: michael@0: if (utcSeconds > MaxUnixTimeT) { michael@0: utcSeconds = MaxUnixTimeT; michael@0: } else if (utcSeconds < 0) { michael@0: /* Go ahead a day to make localtime work (does not work with 0). */ michael@0: utcSeconds = SecondsPerDay; michael@0: } michael@0: michael@0: /* michael@0: * NB: Be aware of the initial range values when making changes to this michael@0: * code: the first call to this method, with those initial range michael@0: * values, must result in a cache miss. michael@0: */ michael@0: michael@0: if (rangeStartSeconds <= utcSeconds && utcSeconds <= rangeEndSeconds) michael@0: return offsetMilliseconds; michael@0: michael@0: if (oldRangeStartSeconds <= utcSeconds && utcSeconds <= oldRangeEndSeconds) michael@0: return oldOffsetMilliseconds; michael@0: michael@0: oldOffsetMilliseconds = offsetMilliseconds; michael@0: oldRangeStartSeconds = rangeStartSeconds; michael@0: oldRangeEndSeconds = rangeEndSeconds; michael@0: michael@0: if (rangeStartSeconds <= utcSeconds) { michael@0: int64_t newEndSeconds = Min(rangeEndSeconds + RangeExpansionAmount, MaxUnixTimeT); michael@0: if (newEndSeconds >= utcSeconds) { michael@0: int64_t endOffsetMilliseconds = computeDSTOffsetMilliseconds(newEndSeconds); michael@0: if (endOffsetMilliseconds == offsetMilliseconds) { michael@0: rangeEndSeconds = newEndSeconds; michael@0: return offsetMilliseconds; michael@0: } michael@0: michael@0: offsetMilliseconds = computeDSTOffsetMilliseconds(utcSeconds); michael@0: if (offsetMilliseconds == endOffsetMilliseconds) { michael@0: rangeStartSeconds = utcSeconds; michael@0: rangeEndSeconds = newEndSeconds; michael@0: } else { michael@0: rangeEndSeconds = utcSeconds; michael@0: } michael@0: return offsetMilliseconds; michael@0: } michael@0: michael@0: offsetMilliseconds = computeDSTOffsetMilliseconds(utcSeconds); michael@0: rangeStartSeconds = rangeEndSeconds = utcSeconds; michael@0: return offsetMilliseconds; michael@0: } michael@0: michael@0: int64_t newStartSeconds = Max(rangeStartSeconds - RangeExpansionAmount, 0); michael@0: if (newStartSeconds <= utcSeconds) { michael@0: int64_t startOffsetMilliseconds = computeDSTOffsetMilliseconds(newStartSeconds); michael@0: if (startOffsetMilliseconds == offsetMilliseconds) { michael@0: rangeStartSeconds = newStartSeconds; michael@0: return offsetMilliseconds; michael@0: } michael@0: michael@0: offsetMilliseconds = computeDSTOffsetMilliseconds(utcSeconds); michael@0: if (offsetMilliseconds == startOffsetMilliseconds) { michael@0: rangeStartSeconds = newStartSeconds; michael@0: rangeEndSeconds = utcSeconds; michael@0: } else { michael@0: rangeStartSeconds = utcSeconds; michael@0: } michael@0: return offsetMilliseconds; michael@0: } michael@0: michael@0: rangeStartSeconds = rangeEndSeconds = utcSeconds; michael@0: offsetMilliseconds = computeDSTOffsetMilliseconds(utcSeconds); michael@0: return offsetMilliseconds; michael@0: } michael@0: michael@0: void michael@0: js::DateTimeInfo::sanityCheck() michael@0: { michael@0: MOZ_ASSERT(rangeStartSeconds <= rangeEndSeconds); michael@0: MOZ_ASSERT_IF(rangeStartSeconds == INT64_MIN, rangeEndSeconds == INT64_MIN); michael@0: MOZ_ASSERT_IF(rangeEndSeconds == INT64_MIN, rangeStartSeconds == INT64_MIN); michael@0: MOZ_ASSERT_IF(rangeStartSeconds != INT64_MIN, michael@0: rangeStartSeconds >= 0 && rangeEndSeconds >= 0); michael@0: MOZ_ASSERT_IF(rangeStartSeconds != INT64_MIN, michael@0: rangeStartSeconds <= MaxUnixTimeT && rangeEndSeconds <= MaxUnixTimeT); michael@0: }