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