LCOV - code coverage report
Current view: top level - js/src/vm - DateTime.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 83 131 63.4 %
Date: 2017-07-14 16:53:18 Functions: 10 12 83.3 %
Legend: Lines: hit not hit

          Line data    Source code
       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/. */
       6             : 
       7             : #include "vm/DateTime.h"
       8             : 
       9             : #include <time.h>
      10             : 
      11             : #include "jsutil.h"
      12             : 
      13             : #include "js/Date.h"
      14             : #include "threading/ExclusiveData.h"
      15             : #if ENABLE_INTL_API
      16             : #include "unicode/timezone.h"
      17             : #endif
      18             : #include "vm/MutexIDs.h"
      19             : 
      20             : using mozilla::UnspecifiedNaN;
      21             : 
      22             : static bool
      23         327 : ComputeLocalTime(time_t local, struct tm* ptm)
      24             : {
      25             : #if defined(_WIN32)
      26             :     return localtime_s(ptm, &local) == 0;
      27             : #elif defined(HAVE_LOCALTIME_R)
      28         327 :     return localtime_r(&local, ptm);
      29             : #else
      30             :     struct tm* otm = localtime(&local);
      31             :     if (!otm)
      32             :         return false;
      33             :     *ptm = *otm;
      34             :     return true;
      35             : #endif
      36             : }
      37             : 
      38             : static bool
      39         325 : ComputeUTCTime(time_t t, struct tm* ptm)
      40             : {
      41             : #if defined(_WIN32)
      42             :     return gmtime_s(ptm, &t) == 0;
      43             : #elif defined(HAVE_GMTIME_R)
      44         325 :     return gmtime_r(&t, ptm);
      45             : #else
      46             :     struct tm* otm = gmtime(&t);
      47             :     if (!otm)
      48             :         return false;
      49             :     *ptm = *otm;
      50             :     return true;
      51             : #endif
      52             : }
      53             : 
      54             : /*
      55             :  * Compute the offset in seconds from the current UTC time to the current local
      56             :  * standard time (i.e. not including any offset due to DST).
      57             :  *
      58             :  * Examples:
      59             :  *
      60             :  * Suppose we are in California, USA on January 1, 2013 at 04:00 PST (UTC-8, no
      61             :  * DST in effect), corresponding to 12:00 UTC.  This function would then return
      62             :  * -8 * SecondsPerHour, or -28800.
      63             :  *
      64             :  * Or suppose we are in Berlin, Germany on July 1, 2013 at 17:00 CEST (UTC+2,
      65             :  * DST in effect), corresponding to 15:00 UTC.  This function would then return
      66             :  * +1 * SecondsPerHour, or +3600.
      67             :  */
      68             : static int32_t
      69         325 : UTCToLocalStandardOffsetSeconds()
      70             : {
      71             :     using js::SecondsPerDay;
      72             :     using js::SecondsPerHour;
      73             :     using js::SecondsPerMinute;
      74             : 
      75             :     // Get the current time.
      76         325 :     time_t currentMaybeWithDST = time(nullptr);
      77         325 :     if (currentMaybeWithDST == time_t(-1))
      78           0 :         return 0;
      79             : 
      80             :     // Break down the current time into its (locally-valued, maybe with DST)
      81             :     // components.
      82             :     struct tm local;
      83         325 :     if (!ComputeLocalTime(currentMaybeWithDST, &local))
      84           0 :         return 0;
      85             : 
      86             :     // Compute a |time_t| corresponding to |local| interpreted without DST.
      87             :     time_t currentNoDST;
      88         325 :     if (local.tm_isdst == 0) {
      89             :         // If |local| wasn't DST, we can use the same time.
      90           0 :         currentNoDST = currentMaybeWithDST;
      91             :     } else {
      92             :         // If |local| respected DST, we need a time broken down into components
      93             :         // ignoring DST.  Turn off DST in the broken-down time.
      94         325 :         local.tm_isdst = 0;
      95             : 
      96             :         // Compute a |time_t t| corresponding to the broken-down time with DST
      97             :         // off.  This has boundary-condition issues (for about the duration of
      98             :         // a DST offset) near the time a location moves to a different time
      99             :         // zone.  But 1) errors will be transient; 2) locations rarely change
     100             :         // time zone; and 3) in the absence of an API that provides the time
     101             :         // zone offset directly, this may be the best we can do.
     102         325 :         currentNoDST = mktime(&local);
     103         325 :         if (currentNoDST == time_t(-1))
     104           0 :             return 0;
     105             :     }
     106             : 
     107             :     // Break down the time corresponding to the no-DST |local| into UTC-based
     108             :     // components.
     109             :     struct tm utc;
     110         325 :     if (!ComputeUTCTime(currentNoDST, &utc))
     111           0 :         return 0;
     112             : 
     113             :     // Finally, compare the seconds-based components of the local non-DST
     114             :     // representation and the UTC representation to determine the actual
     115             :     // difference.
     116         325 :     int utc_secs = utc.tm_hour * SecondsPerHour + utc.tm_min * SecondsPerMinute;
     117         325 :     int local_secs = local.tm_hour * SecondsPerHour + local.tm_min * SecondsPerMinute;
     118             : 
     119             :     // Same-day?  Just subtract the seconds counts.
     120         325 :     if (utc.tm_mday == local.tm_mday)
     121         325 :         return local_secs - utc_secs;
     122             : 
     123             :     // If we have more UTC seconds, move local seconds into the UTC seconds'
     124             :     // frame of reference and then subtract.
     125           0 :     if (utc_secs > local_secs)
     126           0 :         return (SecondsPerDay + local_secs) - utc_secs;
     127             : 
     128             :     // Otherwise we have more local seconds, so move the UTC seconds into the
     129             :     // local seconds' frame of reference and then subtract.
     130           0 :     return local_secs - (utc_secs + SecondsPerDay);
     131             : }
     132             : 
     133             : void
     134         325 : js::DateTimeInfo::internalUpdateTimeZoneAdjustment()
     135             : {
     136             :     /*
     137             :      * The difference between local standard time and UTC will never change for
     138             :      * a given time zone.
     139             :      */
     140         325 :     utcToLocalStandardOffsetSeconds = UTCToLocalStandardOffsetSeconds();
     141             : 
     142         325 :     double newTZA = utcToLocalStandardOffsetSeconds * msPerSecond;
     143         325 :     if (newTZA == localTZA_)
     144         322 :         return;
     145             : 
     146           3 :     localTZA_ = newTZA;
     147             : 
     148             :     /*
     149             :      * The initial range values are carefully chosen to result in a cache miss
     150             :      * on first use given the range of possible values.  Be careful to keep
     151             :      * these values and the caching algorithm in sync!
     152             :      */
     153           3 :     offsetMilliseconds = 0;
     154           3 :     rangeStartSeconds = rangeEndSeconds = INT64_MIN;
     155           3 :     oldOffsetMilliseconds = 0;
     156           3 :     oldRangeStartSeconds = oldRangeEndSeconds = INT64_MIN;
     157             : 
     158           3 :     sanityCheck();
     159             : }
     160             : 
     161           3 : js::DateTimeInfo::DateTimeInfo()
     162             : {
     163             :     // Set to an impossible TZA so that the comparison in
     164             :     // |internalUpdateTimeZoneAdjustment()| initially fails, causing the
     165             :     // remaining fields to be properly initialized at first adjustment.
     166           3 :     localTZA_ = UnspecifiedNaN<double>();
     167           3 :     internalUpdateTimeZoneAdjustment();
     168           3 : }
     169             : 
     170             : int64_t
     171           2 : js::DateTimeInfo::computeDSTOffsetMilliseconds(int64_t utcSeconds)
     172             : {
     173           2 :     MOZ_ASSERT(utcSeconds >= 0);
     174           2 :     MOZ_ASSERT(utcSeconds <= MaxUnixTimeT);
     175             : 
     176             :     struct tm tm;
     177           2 :     if (!ComputeLocalTime(static_cast<time_t>(utcSeconds), &tm))
     178           0 :         return 0;
     179             : 
     180           2 :     int32_t dayoff = int32_t((utcSeconds + utcToLocalStandardOffsetSeconds) % SecondsPerDay);
     181           2 :     int32_t tmoff = tm.tm_sec + (tm.tm_min * SecondsPerMinute) + (tm.tm_hour * SecondsPerHour);
     182             : 
     183           2 :     int32_t diff = tmoff - dayoff;
     184             : 
     185           2 :     if (diff < 0)
     186           0 :         diff += SecondsPerDay;
     187             : 
     188           2 :     return diff * msPerSecond;
     189             : }
     190             : 
     191             : int64_t
     192           3 : js::DateTimeInfo::internalGetDSTOffsetMilliseconds(int64_t utcMilliseconds)
     193             : {
     194           3 :     sanityCheck();
     195             : 
     196           3 :     int64_t utcSeconds = utcMilliseconds / msPerSecond;
     197             : 
     198           3 :     if (utcSeconds > MaxUnixTimeT) {
     199           0 :         utcSeconds = MaxUnixTimeT;
     200           3 :     } else if (utcSeconds < 0) {
     201             :         /* Go ahead a day to make localtime work (does not work with 0). */
     202           0 :         utcSeconds = SecondsPerDay;
     203             :     }
     204             : 
     205             :     /*
     206             :      * NB: Be aware of the initial range values when making changes to this
     207             :      *     code: the first call to this method, with those initial range
     208             :      *     values, must result in a cache miss.
     209             :      */
     210             : 
     211           3 :     if (rangeStartSeconds <= utcSeconds && utcSeconds <= rangeEndSeconds)
     212           1 :         return offsetMilliseconds;
     213             : 
     214           2 :     if (oldRangeStartSeconds <= utcSeconds && utcSeconds <= oldRangeEndSeconds)
     215           0 :         return oldOffsetMilliseconds;
     216             : 
     217           2 :     oldOffsetMilliseconds = offsetMilliseconds;
     218           2 :     oldRangeStartSeconds = rangeStartSeconds;
     219           2 :     oldRangeEndSeconds = rangeEndSeconds;
     220             : 
     221           2 :     if (rangeStartSeconds <= utcSeconds) {
     222           1 :         int64_t newEndSeconds = Min(rangeEndSeconds + RangeExpansionAmount, MaxUnixTimeT);
     223           1 :         if (newEndSeconds >= utcSeconds) {
     224           0 :             int64_t endOffsetMilliseconds = computeDSTOffsetMilliseconds(newEndSeconds);
     225           0 :             if (endOffsetMilliseconds == offsetMilliseconds) {
     226           0 :                 rangeEndSeconds = newEndSeconds;
     227           0 :                 return offsetMilliseconds;
     228             :             }
     229             : 
     230           0 :             offsetMilliseconds = computeDSTOffsetMilliseconds(utcSeconds);
     231           0 :             if (offsetMilliseconds == endOffsetMilliseconds) {
     232           0 :                 rangeStartSeconds = utcSeconds;
     233           0 :                 rangeEndSeconds = newEndSeconds;
     234             :             } else {
     235           0 :                 rangeEndSeconds = utcSeconds;
     236             :             }
     237           0 :             return offsetMilliseconds;
     238             :         }
     239             : 
     240           1 :         offsetMilliseconds = computeDSTOffsetMilliseconds(utcSeconds);
     241           1 :         rangeStartSeconds = rangeEndSeconds = utcSeconds;
     242           1 :         return offsetMilliseconds;
     243             :     }
     244             : 
     245           1 :     int64_t newStartSeconds = Max<int64_t>(rangeStartSeconds - RangeExpansionAmount, 0);
     246           1 :     if (newStartSeconds <= utcSeconds) {
     247           1 :         int64_t startOffsetMilliseconds = computeDSTOffsetMilliseconds(newStartSeconds);
     248           1 :         if (startOffsetMilliseconds == offsetMilliseconds) {
     249           1 :             rangeStartSeconds = newStartSeconds;
     250           1 :             return offsetMilliseconds;
     251             :         }
     252             : 
     253           0 :         offsetMilliseconds = computeDSTOffsetMilliseconds(utcSeconds);
     254           0 :         if (offsetMilliseconds == startOffsetMilliseconds) {
     255           0 :             rangeStartSeconds = newStartSeconds;
     256           0 :             rangeEndSeconds = utcSeconds;
     257             :         } else {
     258           0 :             rangeStartSeconds = utcSeconds;
     259             :         }
     260           0 :         return offsetMilliseconds;
     261             :     }
     262             : 
     263           0 :     rangeStartSeconds = rangeEndSeconds = utcSeconds;
     264           0 :     offsetMilliseconds = computeDSTOffsetMilliseconds(utcSeconds);
     265           0 :     return offsetMilliseconds;
     266             : }
     267             : 
     268             : void
     269           6 : js::DateTimeInfo::sanityCheck()
     270             : {
     271           6 :     MOZ_ASSERT(rangeStartSeconds <= rangeEndSeconds);
     272           6 :     MOZ_ASSERT_IF(rangeStartSeconds == INT64_MIN, rangeEndSeconds == INT64_MIN);
     273           6 :     MOZ_ASSERT_IF(rangeEndSeconds == INT64_MIN, rangeStartSeconds == INT64_MIN);
     274           6 :     MOZ_ASSERT_IF(rangeStartSeconds != INT64_MIN,
     275             :                   rangeStartSeconds >= 0 && rangeEndSeconds >= 0);
     276           6 :     MOZ_ASSERT_IF(rangeStartSeconds != INT64_MIN,
     277             :                   rangeStartSeconds <= MaxUnixTimeT && rangeEndSeconds <= MaxUnixTimeT);
     278           6 : }
     279             : 
     280             : /* static */ js::ExclusiveData<js::DateTimeInfo>*
     281             : js::DateTimeInfo::instance;
     282             : 
     283             : /* static */ js::ExclusiveData<js::IcuTimeZoneStatus>*
     284             : js::IcuTimeZoneState;
     285             : 
     286             : bool
     287           3 : js::InitDateTimeState()
     288             : {
     289             : 
     290           3 :     MOZ_ASSERT(!DateTimeInfo::instance,
     291             :                "we should be initializing only once");
     292             : 
     293           3 :     DateTimeInfo::instance = js_new<ExclusiveData<DateTimeInfo>>(mutexid::DateTimeInfoMutex);
     294           3 :     if (!DateTimeInfo::instance)
     295           0 :         return false;
     296             : 
     297           3 :     MOZ_ASSERT(!IcuTimeZoneState,
     298             :                "we should be initializing only once");
     299             : 
     300           3 :     IcuTimeZoneState = js_new<ExclusiveData<IcuTimeZoneStatus>>(mutexid::IcuTimeZoneStateMutex);
     301           3 :     if (!IcuTimeZoneState) {
     302           0 :         js_delete(DateTimeInfo::instance);
     303           0 :         DateTimeInfo::instance = nullptr;
     304           0 :         return false;
     305             :     }
     306             : 
     307           3 :     return true;
     308             : }
     309             : 
     310             : /* static */ void
     311           0 : js::FinishDateTimeState()
     312             : {
     313           0 :     js_delete(IcuTimeZoneState);
     314           0 :     IcuTimeZoneState = nullptr;
     315             : 
     316           0 :     js_delete(DateTimeInfo::instance);
     317           0 :     DateTimeInfo::instance = nullptr;
     318           0 : }
     319             : 
     320             : JS_PUBLIC_API(void)
     321         322 : JS::ResetTimeZone()
     322             : {
     323         322 :     js::DateTimeInfo::updateTimeZoneAdjustment();
     324             : 
     325             : #if ENABLE_INTL_API && defined(ICU_TZ_HAS_RECREATE_DEFAULT)
     326         322 :     js::IcuTimeZoneState->lock().get() = js::IcuTimeZoneStatus::NeedsUpdate;
     327             : #endif
     328         322 : }
     329             : 
     330             : void
     331           0 : js::ResyncICUDefaultTimeZone()
     332             : {
     333             : #if ENABLE_INTL_API && defined(ICU_TZ_HAS_RECREATE_DEFAULT)
     334           0 :     auto guard = IcuTimeZoneState->lock();
     335           0 :     if (guard.get() == IcuTimeZoneStatus::NeedsUpdate) {
     336           0 :         icu::TimeZone::recreateDefault();
     337           0 :         guard.get() = IcuTimeZoneStatus::Valid;
     338             :     }
     339             : #endif
     340           0 : }

Generated by: LCOV version 1.13