]> git.saurik.com Git - apple/javascriptcore.git/blobdiff - wtf/DateMath.cpp
JavaScriptCore-903.tar.gz
[apple/javascriptcore.git] / wtf / DateMath.cpp
index b9a0207a4c9216a4af333d267dd8f3fe5567a86d..70c0cf47f5f79cbfc331f7a17fa97e9a7cbc67dc 100644 (file)
@@ -3,6 +3,7 @@
  * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
  * Copyright (C) 2009 Google Inc. All rights reserved.
  * Copyright (C) 2007-2009 Torch Mobile, Inc.
  * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
  * Copyright (C) 2009 Google Inc. All rights reserved.
  * Copyright (C) 2007-2009 Torch Mobile, Inc.
+ * Copyright (C) 2010 &yet, LLC. (nate@andyet.net)
  *
  * The Original Code is Mozilla Communicator client code, released
  * March 31, 1998.
  *
  * The Original Code is Mozilla Communicator client code, released
  * March 31, 1998.
 #include "Assertions.h"
 #include "ASCIICType.h"
 #include "CurrentTime.h"
 #include "Assertions.h"
 #include "ASCIICType.h"
 #include "CurrentTime.h"
+#if USE(JSC)
+#include "JSObject.h"
+#endif
 #include "MathExtras.h"
 #include "MathExtras.h"
+#if USE(JSC)
+#include "ScopeChain.h"
+#endif
+#include "StdLibExtras.h"
 #include "StringExtras.h"
 
 #include <algorithm>
 #include "StringExtras.h"
 
 #include <algorithm>
@@ -377,7 +385,7 @@ int equivalentYearForDST(int year)
     return year;
 }
 
     return year;
 }
 
-static int32_t calculateUTCOffset()
+int32_t calculateUTCOffset()
 {
 #if PLATFORM(BREWMP)
     time_t localTime = static_cast<time_t>(currentTime());
 {
 #if PLATFORM(BREWMP)
     time_t localTime = static_cast<time_t>(currentTime());
@@ -447,7 +455,7 @@ static double calculateDSTOffsetSimple(double localTimeSeconds, double utcOffset
 }
 
 // Get the DST offset, given a time in UTC
 }
 
 // Get the DST offset, given a time in UTC
-static double calculateDSTOffset(double ms, double utcOffset)
+double calculateDSTOffset(double ms, double utcOffset)
 {
     // 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
 {
     // 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
@@ -479,7 +487,7 @@ void initializeDates()
     equivalentYearForDST(2000); // Need to call once to initialize a static used in this function.
 }
 
     equivalentYearForDST(2000); // Need to call once to initialize a static used in this function.
 }
 
-static inline double ymdhmsToSeconds(long year, int mon, int day, int hour, int minute, int second)
+static inline double ymdhmsToSeconds(long year, int mon, int day, int hour, int minute, double second)
 {
     double days = (day - 32075)
         + floor(1461 * (year + 4800.0 + (mon - 14) / 12) / 4)
 {
     double days = (day - 32075)
         + floor(1461 * (year + 4800.0 + (mon - 14) / 12) / 4)
@@ -557,6 +565,162 @@ static bool parseLong(const char* string, char** stopPosition, int base, long* r
     return true;
 }
 
     return true;
 }
 
+double parseES5DateFromNullTerminatedCharacters(const char* dateString)
+{
+    // This parses a date of the form defined in ECMA-262-5, section 15.9.1.15
+    // (similar to RFC 3339 / ISO 8601: YYYY-MM-DDTHH:mm:ss[.sss]Z).
+    // In most cases it is intentionally strict (e.g. correct field widths, no stray whitespace).
+    
+    static const long daysPerMonth[12] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+    
+    const char* currentPosition = dateString;
+    char* postParsePosition;
+    
+    // This is a bit more lenient on the year string than ES5 specifies:
+    // instead of restricting to 4 digits (or 6 digits with mandatory +/-),
+    // it accepts any integer value. Consider this an implementation fallback.
+    long year;
+    if (!parseLong(currentPosition, &postParsePosition, 10, &year))
+        return NaN;
+    if (*postParsePosition != '-')
+        return NaN;
+    currentPosition = postParsePosition + 1;
+    
+    long month;
+    if (!isASCIIDigit(*currentPosition))
+        return NaN;
+    if (!parseLong(currentPosition, &postParsePosition, 10, &month))
+        return NaN;
+    if (*postParsePosition != '-' || (postParsePosition - currentPosition) != 2)
+        return NaN;
+    currentPosition = postParsePosition + 1;
+    
+    long day;
+    if (!isASCIIDigit(*currentPosition))
+        return NaN;
+    if (!parseLong(currentPosition, &postParsePosition, 10, &day))
+        return NaN;
+    if (*postParsePosition != 'T' || (postParsePosition - currentPosition) != 2)
+        return NaN;
+    currentPosition = postParsePosition + 1;
+    
+    long hours;
+    if (!isASCIIDigit(*currentPosition))
+        return NaN;
+    if (!parseLong(currentPosition, &postParsePosition, 10, &hours))
+        return NaN;
+    if (*postParsePosition != ':' || (postParsePosition - currentPosition) != 2)
+        return NaN;
+    currentPosition = postParsePosition + 1;
+    
+    long minutes;
+    if (!isASCIIDigit(*currentPosition))
+        return NaN;
+    if (!parseLong(currentPosition, &postParsePosition, 10, &minutes))
+        return NaN;
+    if (*postParsePosition != ':' || (postParsePosition - currentPosition) != 2)
+        return NaN;
+    currentPosition = postParsePosition + 1;
+    
+    long intSeconds;
+    if (!isASCIIDigit(*currentPosition))
+        return NaN;
+    if (!parseLong(currentPosition, &postParsePosition, 10, &intSeconds))
+        return NaN;
+    if ((postParsePosition - currentPosition) != 2)
+        return NaN;
+    
+    double seconds = intSeconds;
+    if (*postParsePosition == '.') {
+        currentPosition = postParsePosition + 1;
+        
+        // In ECMA-262-5 it's a bit unclear if '.' can be present without milliseconds, but
+        // a reasonable interpretation guided by the given examples and RFC 3339 says "no".
+        // We check the next character to avoid reading +/- timezone hours after an invalid decimal.
+        if (!isASCIIDigit(*currentPosition))
+            return NaN;
+        
+        // We are more lenient than ES5 by accepting more or less than 3 fraction digits.
+        long fracSeconds;
+        if (!parseLong(currentPosition, &postParsePosition, 10, &fracSeconds))
+            return NaN;
+        
+        long numFracDigits = postParsePosition - currentPosition;
+        seconds += fracSeconds * pow(10.0, static_cast<double>(-numFracDigits));
+    }
+    currentPosition = postParsePosition;
+    
+    // A few of these checks could be done inline above, but since many of them are interrelated
+    // we would be sacrificing readability to "optimize" the (presumably less common) failure path.
+    if (month < 1 || month > 12)
+        return NaN;
+    if (day < 1 || day > daysPerMonth[month - 1])
+        return NaN;
+    if (month == 2 && day > 28 && !isLeapYear(year))
+        return NaN;
+    if (hours < 0 || hours > 24)
+        return NaN;
+    if (hours == 24 && (minutes || seconds))
+        return NaN;
+    if (minutes < 0 || minutes > 59)
+        return NaN;
+    if (seconds < 0 || seconds >= 61)
+        return NaN;
+    if (seconds > 60) {
+        // Discard leap seconds by clamping to the end of a minute.
+        seconds = 60;
+    }
+    
+    long timeZoneSeconds = 0;
+    if (*currentPosition != 'Z') {
+        bool tzNegative;
+        if (*currentPosition == '-')
+            tzNegative = true;
+        else if (*currentPosition == '+')
+            tzNegative = false;
+        else
+            return NaN;
+        currentPosition += 1;
+        
+        long tzHours;
+        long tzHoursAbs;
+        long tzMinutes;
+        
+        if (!isASCIIDigit(*currentPosition))
+            return NaN;
+        if (!parseLong(currentPosition, &postParsePosition, 10, &tzHours))
+            return NaN;
+        if (*postParsePosition != ':' || (postParsePosition - currentPosition) != 2)
+            return NaN;
+        tzHoursAbs = abs(tzHours);
+        currentPosition = postParsePosition + 1;
+        
+        if (!isASCIIDigit(*currentPosition))
+            return NaN;
+        if (!parseLong(currentPosition, &postParsePosition, 10, &tzMinutes))
+            return NaN;
+        if ((postParsePosition - currentPosition) != 2)
+            return NaN;
+        currentPosition = postParsePosition;
+        
+        if (tzHoursAbs > 24)
+            return NaN;
+        if (tzMinutes < 0 || tzMinutes > 59)
+            return NaN;
+        
+        timeZoneSeconds = 60 * (tzMinutes + (60 * tzHoursAbs));
+        if (tzNegative)
+            timeZoneSeconds = -timeZoneSeconds;
+    } else {
+        currentPosition += 1;
+    }
+    if (*currentPosition)
+        return NaN;
+    
+    double dateSeconds = ymdhmsToSeconds(year, month, day, hours, minutes, seconds) - timeZoneSeconds;
+    return dateSeconds * msPerSecond;
+}
+
 // 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)
 {
 // 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)
 {
@@ -762,6 +926,16 @@ static double parseDateFromNullTerminatedCharacters(const char* dateString, bool
             }
         }
     }
             }
         }
     }
+    
+    // The year may be after the time but before the time zone, but don't
+    // confuse a time zone specificed as an offset from UTC (e.g. +0100) with a
+    // four-digit year.
+    if (year <= 0 && *dateString != '+' && *dateString != '-') {
+       if (!parseLong(dateString, &newPosStr, 10, &year))
+          year = 0;
+       dateString = newPosStr;
+       skipSpacesAndComments(dateString);
+    }
 
     // Don't fail if the time zone is missing. 
     // Some websites omit the time zone (4275206).
 
     // Don't fail if the time zone is missing. 
     // Some websites omit the time zone (4275206).
@@ -793,7 +967,7 @@ static double parseDateFromNullTerminatedCharacters(const char* dateString, bool
             }
             haveTZ = true;
         } else {
             }
             haveTZ = true;
         } else {
-            for (int i = 0; i < int(sizeof(known_zones) / sizeof(KnownZone)); i++) {
+            for (size_t i = 0; i < WTF_ARRAY_LENGTH(known_zones); ++i) {
                 if (0 == strncasecmp(dateString, known_zones[i].tzName, strlen(known_zones[i].tzName))) {
                     offset = known_zones[i].tzOffset;
                     dateString += strlen(known_zones[i].tzName);
                 if (0 == strncasecmp(dateString, known_zones[i].tzName, strlen(known_zones[i].tzName))) {
                     offset = known_zones[i].tzOffset;
                     dateString += strlen(known_zones[i].tzName);
@@ -971,7 +1145,7 @@ void msToGregorianDateTime(ExecState* exec, double ms, bool outputIsUTC, Gregori
     tm.year     =  year - 1900;
     tm.isDST    =  dstOff != 0.0;
     tm.utcOffset = static_cast<long>((dstOff + utcOff) / WTF::msPerSecond);
     tm.year     =  year - 1900;
     tm.isDST    =  dstOff != 0.0;
     tm.utcOffset = static_cast<long>((dstOff + utcOff) / WTF::msPerSecond);
-    tm.timeZone = NULL;
+    tm.timeZone = nullptr;
 }
 
 double parseDateFromNullTerminatedCharacters(ExecState* exec, const char* dateString)
 }
 
 double parseDateFromNullTerminatedCharacters(ExecState* exec, const char* dateString)