]> git.saurik.com Git - apple/javascriptcore.git/blobdiff - wtf/DateMath.cpp
JavaScriptCore-621.1.tar.gz
[apple/javascriptcore.git] / wtf / DateMath.cpp
index 1d480eeced1a0b112259d5deafbe0a1127e80c33..b9a0207a4c9216a4af333d267dd8f3fe5567a86d 100644 (file)
  * other provisions required by the MPL or the GPL, as the case may be.
  * If you do not delete the provisions above, a recipient may use your
  * version of this file under any of the LGPL, the MPL or the GPL.
+
+ * Copyright 2006-2008 the V8 project authors. All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #include "config.h"
@@ -61,8 +88,9 @@
 #include <errno.h>
 #endif
 
-#if PLATFORM(DARWIN)
-#include <notify.h>
+#if OS(WINCE)
+extern "C" size_t strftime(char * const s, const size_t maxsize, const char * const format, const struct tm * const t);
+extern "C" struct tm * localtime(const time_t *timer);
 #endif
 
 #if HAVE(SYS_TIME_H)
 #include <sys/timeb.h>
 #endif
 
+#if USE(JSC)
+#include "CallFrame.h"
+#endif
+
 #define NaN std::numeric_limits<double>::quiet_NaN()
 
+using namespace WTF;
+
 namespace WTF {
 
 /* Constants */
@@ -86,6 +120,10 @@ static const double secondsPerYear = 24.0 * 60.0 * 60.0 * 365.0;
 static const double usecPerSec = 1000000.0;
 
 static const double maxUnixTime = 2145859200.0; // 12/31/2037
+// ECMAScript asks not to support for a date of which total
+// millisecond value is larger than the following value.
+// See 15.9.1.14 of ECMA-262 5th edition.
+static const double maxECMAScriptTime = 8.64E15;
 
 // Day of year for the first day of each month, where index 0 is January, and day 0 is January 1.
 // First for non-leap years, then for leap years.
@@ -134,7 +172,7 @@ static inline double msToDays(double ms)
     return floor(ms / msPerDay);
 }
 
-static inline int msToYear(double ms)
+int msToYear(double ms)
 {
     int approxYear = static_cast<int>(floor(ms / (msPerDay * 365.2425)) + 1970);
     double msFromApproxYearTo1970 = msPerDay * daysFrom1970ToYear(approxYear);
@@ -145,7 +183,7 @@ static inline int msToYear(double ms)
     return approxYear;
 }
 
-static inline int dayInYear(double ms, int year)
+int dayInYear(double ms, int year)
 {
     return static_cast<int>(msToDays(ms) - daysFrom1970ToYear(year));
 }
@@ -191,7 +229,7 @@ static inline int msToHours(double ms)
     return static_cast<int>(result);
 }
 
-static inline int monthFromDayInYear(int dayInYear, bool leapYear)
+int monthFromDayInYear(int dayInYear, bool leapYear)
 {
     const int d = dayInYear;
     int step;
@@ -229,7 +267,7 @@ static inline bool checkMonth(int dayInYear, int& startDayOfThisMonth, int& star
     return (dayInYear <= startDayOfNextMonth);
 }
 
-static inline int dayInMonthFromDayInYear(int dayInYear, bool leapYear)
+int dayInMonthFromDayInYear(int dayInYear, bool leapYear)
 {
     const int d = dayInYear;
     int step;
@@ -272,7 +310,7 @@ static inline double timeToMS(double hour, double min, double sec, double ms)
     return (((hour * minutesPerHour + min) * secondsPerMinute + sec) * msPerSecond + ms);
 }
 
-static int dateToDayInYear(int year, int month, int day)
+double dateToDaysFrom1970(int year, int month, int day)
 {
     year += month / 12;
 
@@ -282,34 +320,13 @@ static int dateToDayInYear(int year, int month, int day)
         --year;
     }
 
-    int yearday = static_cast<int>(floor(daysFrom1970ToYear(year)));
+    double yearday = floor(daysFrom1970ToYear(year));
+    ASSERT((year >= 1970 && yearday >= 0) || (year < 1970 && yearday < 0));
     int monthday = monthToDayInYear(month, isLeapYear(year));
 
     return yearday + monthday + day - 1;
 }
 
-double getCurrentUTCTime()
-{
-    return floor(getCurrentUTCTimeWithMicroseconds());
-}
-
-// Returns current time in milliseconds since 1 Jan 1970.
-double getCurrentUTCTimeWithMicroseconds()
-{
-    return currentTime() * 1000.0; 
-}
-
-void getLocalTime(const time_t* localTime, struct tm* localTM)
-{
-#if COMPILER(MSVC7) || COMPILER(MINGW) || PLATFORM(WINCE)
-    *localTM = *localtime(localTime);
-#elif COMPILER(MSVC)
-    localtime_s(localTM, localTime);
-#else
-    localtime_r(localTime, localTM);
-#endif
-}
-
 // There is a hard limit at 2038 that we currently do not have a workaround
 // for (rdar://problem/5052975).
 static inline int maximumYearForDST()
@@ -323,7 +340,7 @@ static inline int minimumYearForDST()
     // greater than the max year minus 27 (2010), we want to use the max year
     // minus 27 instead, to ensure there is a range of 28 years that all years
     // can map to.
-    return std::min(msToYear(getCurrentUTCTime()), maximumYearForDST() - 27) ;
+    return std::min(msToYear(jsCurrentTime()), maximumYearForDST() - 27) ;
 }
 
 /*
@@ -362,7 +379,11 @@ int equivalentYearForDST(int year)
 
 static int32_t calculateUTCOffset()
 {
+#if PLATFORM(BREWMP)
+    time_t localTime = static_cast<time_t>(currentTime());
+#else
     time_t localTime = time(0);
+#endif
     tm localt;
     getLocalTime(&localTime, &localt);
 
@@ -376,57 +397,28 @@ static int32_t calculateUTCOffset()
     localt.tm_wday = 0;
     localt.tm_yday = 0;
     localt.tm_isdst = 0;
-#if PLATFORM(WIN_OS) || PLATFORM(SOLARIS) || COMPILER(RVCT)
+#if HAVE(TM_GMTOFF)
+    localt.tm_gmtoff = 0;
+#endif
+#if HAVE(TM_ZONE)
+    localt.tm_zone = 0;
+#endif
+    
+#if HAVE(TIMEGM)
+    time_t utcOffset = timegm(&localt) - mktime(&localt);
+#else
     // Using a canned date of 01/01/2009 on platforms with weaker date-handling foo.
     localt.tm_year = 109;
     time_t utcOffset = 1230768000 - mktime(&localt);
-#else
-    localt.tm_zone = 0;
-    localt.tm_gmtoff = 0;
-    time_t utcOffset = timegm(&localt) - mktime(&localt);
 #endif
 
     return static_cast<int32_t>(utcOffset * 1000);
 }
 
-#if PLATFORM(DARWIN)
-static int32_t s_cachedUTCOffset; // In milliseconds. An assumption here is that access to an int32_t variable is atomic on platforms that take this code path.
-static bool s_haveCachedUTCOffset;
-static int s_notificationToken;
-#endif
-
-/*
- * Get the difference in milliseconds between this time zone and UTC (GMT)
- * NOT including DST.
- */
-double getUTCOffset()
-{
-#if PLATFORM(DARWIN)
-    if (s_haveCachedUTCOffset) {
-        int notified;
-        uint32_t status = notify_check(s_notificationToken, &notified);
-        if (status == NOTIFY_STATUS_OK && !notified)
-            return s_cachedUTCOffset;
-    }
-#endif
-
-    int32_t utcOffset = calculateUTCOffset();
-
-#if PLATFORM(DARWIN)
-    // Theoretically, it is possible that several threads will be executing this code at once, in which case we will have a race condition,
-    // and a newer value may be overwritten. In practice, time zones don't change that often.
-    s_cachedUTCOffset = utcOffset;
-#endif
-
-    return utcOffset;
-}
-
 /*
- * Get the DST offset for the time passed in.  Takes
- * seconds (not milliseconds) and cannot handle dates before 1970
- * on some OS'
+ * Get the DST offset for the time passed in.
  */
-static double getDSTOffsetSimple(double localTimeSeconds, double utcOffset)
+static double calculateDSTOffsetSimple(double localTimeSeconds, double utcOffset)
 {
     if (localTimeSeconds > maxUnixTime)
         localTimeSeconds = maxUnixTime;
@@ -455,9 +447,9 @@ static double getDSTOffsetSimple(double localTimeSeconds, double utcOffset)
 }
 
 // Get the DST offset, given a time in UTC
-static double getDSTOffset(double ms, double utcOffset)
+static double calculateDSTOffset(double ms, double utcOffset)
 {
-    // On Mac OS X, the call to localtime (see getDSTOffsetSimple) will return historically accurate
+    // On Mac OS X, the call to localtime (see calculateDSTOffsetSimple) will return historically accurate
     // DST information (e.g. New Zealand did not have DST from 1946 to 1974) however the JavaScript
     // standard explicitly dictates that historical information should not be considered when
     // determining DST. For this reason we shift away from years that localtime can handle but would
@@ -469,70 +461,22 @@ static double getDSTOffset(double ms, double utcOffset)
         int dayInYearLocal = dayInYear(ms, year);
         int dayInMonth = dayInMonthFromDayInYear(dayInYearLocal, leapYear);
         int month = monthFromDayInYear(dayInYearLocal, leapYear);
-        int day = dateToDayInYear(equivalentYear, month, dayInMonth);
+        double day = dateToDaysFrom1970(equivalentYear, month, dayInMonth);
         ms = (day * msPerDay) + msToMilliseconds(ms);
     }
 
-    return getDSTOffsetSimple(ms / msPerSecond, utcOffset);
-}
-
-double gregorianDateTimeToMS(const GregorianDateTime& t, double milliSeconds, bool inputIsUTC)
-{
-    int day = dateToDayInYear(t.year + 1900, t.month, t.monthDay);
-    double ms = timeToMS(t.hour, t.minute, t.second, milliSeconds);
-    double result = (day * msPerDay) + ms;
-
-    if (!inputIsUTC) { // convert to UTC
-        double utcOffset = getUTCOffset();
-        result -= utcOffset;
-        result -= getDSTOffset(result, utcOffset);
-    }
-
-    return result;
-}
-
-void msToGregorianDateTime(double ms, bool outputIsUTC, GregorianDateTime& tm)
-{
-    // input is UTC
-    double dstOff = 0.0;
-    const double utcOff = getUTCOffset();
-
-    if (!outputIsUTC) {  // convert to local time
-        dstOff = getDSTOffset(ms, utcOff);
-        ms += dstOff + utcOff;
-    }
-
-    const int year = msToYear(ms);
-    tm.second   =  msToSeconds(ms);
-    tm.minute   =  msToMinutes(ms);
-    tm.hour     =  msToHours(ms);
-    tm.weekDay  =  msToWeekDay(ms);
-    tm.yearDay  =  dayInYear(ms, year);
-    tm.monthDay =  dayInMonthFromDayInYear(tm.yearDay, isLeapYear(year));
-    tm.month    =  monthFromDayInYear(tm.yearDay, isLeapYear(year));
-    tm.year     =  year - 1900;
-    tm.isDST    =  dstOff != 0.0;
-
-    tm.utcOffset = outputIsUTC ? 0 : static_cast<long>((dstOff + utcOff) / msPerSecond);
-    tm.timeZone = NULL;
+    return calculateDSTOffsetSimple(ms / msPerSecond, utcOffset);
 }
 
 void initializeDates()
 {
 #ifndef NDEBUG
     static bool alreadyInitialized;
-    ASSERT(!alreadyInitialized++);
+    ASSERT(!alreadyInitialized);
+    alreadyInitialized = true;
 #endif
 
     equivalentYearForDST(2000); // Need to call once to initialize a static used in this function.
-#if PLATFORM(DARWIN)
-    // Register for a notification whenever the time zone changes.
-    uint32_t status = notify_register_check("com.apple.system.timezone", &s_notificationToken);
-    if (status == NOTIFY_STATUS_OK) {
-        s_cachedUTCOffset = calculateUTCOffset();
-        s_haveCachedUTCOffset = true;
-    }
-#endif
 }
 
 static inline double ymdhmsToSeconds(long year, int mon, int day, int hour, int minute, int second)
@@ -548,7 +492,7 @@ static inline double ymdhmsToSeconds(long year, int mon, int day, int hour, int
 // We follow the recommendation of RFC 2822 to consider all
 // obsolete time zones not listed here equivalent to "-0000".
 static const struct KnownZone {
-#if !PLATFORM(WIN_OS)
+#if !OS(WINDOWS)
     const
 #endif
         char tzName[4];
@@ -613,8 +557,12 @@ static bool parseLong(const char* string, char** stopPosition, int base, long* r
     return true;
 }
 
-double parseDateFromNullTerminatedCharacters(const char* dateString)
+// Odd case where 'exec' is allowed to be 0, to accomodate a caller in WebCore.
+static double parseDateFromNullTerminatedCharacters(const char* dateString, bool& haveTZ, int& offset)
 {
+    haveTZ = false;
+    offset = 0;
+
     // This parses a date in the form:
     //     Tuesday, 09-Nov-99 23:12:40 GMT
     // or
@@ -815,9 +763,6 @@ double parseDateFromNullTerminatedCharacters(const char* dateString)
         }
     }
 
-    bool haveTZ = false;
-    int offset = 0;
-
     // Don't fail if the time zone is missing. 
     // Some websites omit the time zone (4275206).
     if (*dateString) {
@@ -836,7 +781,7 @@ double parseDateFromNullTerminatedCharacters(const char* dateString)
                 return NaN;
 
             int sgn = (o < 0) ? -1 : 1;
-            o = abs(o);
+            o = labs(o);
             if (*dateString != ':') {
                 offset = ((o / 100) * 60 + (o % 100)) * sgn;
             } else { // GMT+05:00
@@ -880,33 +825,172 @@ double parseDateFromNullTerminatedCharacters(const char* dateString)
         else
             year += 1900;
     }
+    
+    return ymdhmsToSeconds(year, month + 1, day, hour, minute, second) * msPerSecond;
+}
+
+double parseDateFromNullTerminatedCharacters(const char* dateString)
+{
+    bool haveTZ;
+    int offset;
+    double ms = parseDateFromNullTerminatedCharacters(dateString, haveTZ, offset);
+    if (isnan(ms))
+        return NaN;
 
     // fall back to local timezone
     if (!haveTZ) {
-        GregorianDateTime t;
-        t.monthDay = day;
-        t.month = month;
-        t.year = year - 1900;
-        t.isDST = -1;
-        t.second = second;
-        t.minute = minute;
-        t.hour = hour;
-
-        // Use our gregorianDateTimeToMS() rather than mktime() as the latter can't handle the full year range.
-        return gregorianDateTimeToMS(t, 0, false);
+        double utcOffset = calculateUTCOffset();
+        double dstOffset = calculateDSTOffset(ms, utcOffset);
+        offset = static_cast<int>((utcOffset + dstOffset) / msPerMinute);
     }
-
-    return (ymdhmsToSeconds(year, month + 1, day, hour, minute, second) - (offset * 60.0)) * msPerSecond;
+    return ms - (offset * msPerMinute);
 }
 
 double timeClip(double t)
 {
     if (!isfinite(t))
         return NaN;
-    if (fabs(t) > 8.64E15)
+    if (fabs(t) > maxECMAScriptTime)
         return NaN;
     return trunc(t);
 }
+} // namespace WTF
+
+#if USE(JSC)
+namespace JSC {
+
+// Get the DST offset for the time passed in.
+//
+// NOTE: The implementation relies on the fact that no time zones have
+// more than one daylight savings offset change per month.
+// If this function is called with NaN it returns NaN.
+static double getDSTOffset(ExecState* exec, double ms, double utcOffset)
+{
+    DSTOffsetCache& cache = exec->globalData().dstOffsetCache;
+    double start = cache.start;
+    double end = cache.end;
+
+    if (start <= ms) {
+        // If the time fits in the cached interval, return the cached offset.
+        if (ms <= end) return cache.offset;
+
+        // Compute a possible new interval end.
+        double newEnd = end + cache.increment;
+
+        if (ms <= newEnd) {
+            double endOffset = calculateDSTOffset(newEnd, utcOffset);
+            if (cache.offset == endOffset) {
+                // If the offset at the end of the new interval still matches
+                // the offset in the cache, we grow the cached time interval
+                // and return the offset.
+                cache.end = newEnd;
+                cache.increment = msPerMonth;
+                return endOffset;
+            } else {
+                double offset = calculateDSTOffset(ms, utcOffset);
+                if (offset == endOffset) {
+                    // The offset at the given time is equal to the offset at the
+                    // new end of the interval, so that means that we've just skipped
+                    // the point in time where the DST offset change occurred. Updated
+                    // the interval to reflect this and reset the increment.
+                    cache.start = ms;
+                    cache.end = newEnd;
+                    cache.increment = msPerMonth;
+                } else {
+                    // The interval contains a DST offset change and the given time is
+                    // before it. Adjust the increment to avoid a linear search for
+                    // the offset change point and change the end of the interval.
+                    cache.increment /= 3;
+                    cache.end = ms;
+                }
+                // Update the offset in the cache and return it.
+                cache.offset = offset;
+                return offset;
+            }
+        }
+    }
+
+    // Compute the DST offset for the time and shrink the cache interval
+    // to only contain the time. This allows fast repeated DST offset
+    // computations for the same time.
+    double offset = calculateDSTOffset(ms, utcOffset);
+    cache.offset = offset;
+    cache.start = ms;
+    cache.end = ms;
+    cache.increment = msPerMonth;
+    return offset;
+}
 
+/*
+ * Get the difference in milliseconds between this time zone and UTC (GMT)
+ * NOT including DST.
+ */
+double getUTCOffset(ExecState* exec)
+{
+    double utcOffset = exec->globalData().cachedUTCOffset;
+    if (!isnan(utcOffset))
+        return utcOffset;
+    exec->globalData().cachedUTCOffset = calculateUTCOffset();
+    return exec->globalData().cachedUTCOffset;
+}
 
-} // namespace WTF
+double gregorianDateTimeToMS(ExecState* exec, const GregorianDateTime& t, double milliSeconds, bool inputIsUTC)
+{
+    double day = dateToDaysFrom1970(t.year + 1900, t.month, t.monthDay);
+    double ms = timeToMS(t.hour, t.minute, t.second, milliSeconds);
+    double result = (day * WTF::msPerDay) + ms;
+
+    if (!inputIsUTC) { // convert to UTC
+        double utcOffset = getUTCOffset(exec);
+        result -= utcOffset;
+        result -= getDSTOffset(exec, result, utcOffset);
+    }
+
+    return result;
+}
+
+// input is UTC
+void msToGregorianDateTime(ExecState* exec, double ms, bool outputIsUTC, GregorianDateTime& tm)
+{
+    double dstOff = 0.0;
+    double utcOff = 0.0;
+    if (!outputIsUTC) {
+        utcOff = getUTCOffset(exec);
+        dstOff = getDSTOffset(exec, ms, utcOff);
+        ms += dstOff + utcOff;
+    }
+
+    const int year = msToYear(ms);
+    tm.second   =  msToSeconds(ms);
+    tm.minute   =  msToMinutes(ms);
+    tm.hour     =  msToHours(ms);
+    tm.weekDay  =  msToWeekDay(ms);
+    tm.yearDay  =  dayInYear(ms, year);
+    tm.monthDay =  dayInMonthFromDayInYear(tm.yearDay, isLeapYear(year));
+    tm.month    =  monthFromDayInYear(tm.yearDay, isLeapYear(year));
+    tm.year     =  year - 1900;
+    tm.isDST    =  dstOff != 0.0;
+    tm.utcOffset = static_cast<long>((dstOff + utcOff) / WTF::msPerSecond);
+    tm.timeZone = NULL;
+}
+
+double parseDateFromNullTerminatedCharacters(ExecState* exec, const char* dateString)
+{
+    ASSERT(exec);
+    bool haveTZ;
+    int offset;
+    double ms = WTF::parseDateFromNullTerminatedCharacters(dateString, haveTZ, offset);
+    if (isnan(ms))
+        return NaN;
+
+    // fall back to local timezone
+    if (!haveTZ) {
+        double utcOffset = getUTCOffset(exec);
+        double dstOffset = getDSTOffset(exec, ms, utcOffset);
+        offset = static_cast<int>((utcOffset + dstOffset) / WTF::msPerMinute);
+    }
+    return ms - (offset * WTF::msPerMinute);
+}
+
+} // namespace JSC
+#endif // USE(JSC)