We should support CreateThis in the FTL
[WebKit-https.git] / Source / JavaScriptCore / runtime / JSDateMath.cpp
index 882f86f..c13b3de 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
- * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
+ * Copyright (C) 2006-2018 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)
 #include "config.h"
 #include "JSDateMath.h"
 
-#include "CurrentTime.h"
 #include "JSObject.h"
-#include "MathExtras.h"
-#include "ScopeChain.h"
-#include "StdLibExtras.h"
-#include "StringExtras.h"
+#include "JSScope.h"
+#include "JSCInlines.h"
 
 #include <algorithm>
 #include <limits.h>
 #include <time.h>
 #include <wtf/ASCIICType.h>
 #include <wtf/Assertions.h>
-#include <wtf/text/StringBuilder.h>
+#include <wtf/MathExtras.h>
+#include <wtf/StdLibExtras.h>
 
 #if HAVE(ERRNO_H)
 #include <errno.h>
 #endif
 
-#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/time.h>
 #endif
@@ -131,18 +124,19 @@ static inline int msToWeekDay(double ms)
     return wd;
 }
 
-// Get the DST offset for the time passed in.
+// Get the combined UTC + 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)
+static LocalTimeOffset localTimeOffset(VM& vm, double ms, WTF::TimeType inputTimeType = WTF::UTCTime)
 {
-    DSTOffsetCache& cache = exec->globalData().dstOffsetCache;
+    LocalTimeOffsetCache& cache = vm.localTimeOffsetCache;
     double start = cache.start;
     double end = cache.end;
+    WTF::TimeType cachedTimeType = cache.timeType;
 
-    if (start <= ms) {
+    if (cachedTimeType == inputTimeType && start <= ms) {
         // If the time fits in the cached interval, return the cached offset.
         if (ms <= end) return cache.offset;
 
@@ -150,7 +144,7 @@ static double getDSTOffset(ExecState* exec, double ms, double utcOffset)
         double newEnd = end + cache.increment;
 
         if (ms <= newEnd) {
-            double endOffset = calculateDSTOffset(newEnd, utcOffset);
+            LocalTimeOffset endOffset = calculateLocalTimeOffset(newEnd, inputTimeType);
             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
@@ -158,110 +152,112 @@ static double getDSTOffset(ExecState* exec, double ms, double utcOffset)
                 cache.end = newEnd;
                 cache.increment = msPerMonth;
                 return endOffset;
+            }
+            LocalTimeOffset offset = calculateLocalTimeOffset(ms, inputTimeType);
+            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 {
-                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;
+                // 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);
+    LocalTimeOffset offset = calculateLocalTimeOffset(ms, inputTimeType);
     cache.offset = offset;
     cache.start = ms;
     cache.end = ms;
     cache.increment = msPerMonth;
+    cache.timeType = inputTimeType;
     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;
-}
-
-double gregorianDateTimeToMS(ExecState* exec, const GregorianDateTime& t, double milliSeconds, bool inputIsUTC)
+double gregorianDateTimeToMS(VM& vm, const GregorianDateTime& t, double milliSeconds, WTF::TimeType inputTimeType)
 {
-    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;
+    double day = dateToDaysFrom1970(t.year(), t.month(), t.monthDay());
+    double ms = timeToMS(t.hour(), t.minute(), t.second(), milliSeconds);
+    double localTimeResult = (day * WTF::msPerDay) + ms;
+    double localToUTCTimeOffset = inputTimeType == LocalTime
+        ? localTimeOffset(vm, localTimeResult, inputTimeType).offset : 0;
 
-    if (!inputIsUTC) { // convert to UTC
-        double utcOffset = getUTCOffset(exec);
-        result -= utcOffset;
-        result -= getDSTOffset(exec, result, utcOffset);
-    }
-
-    return result;
+    return localTimeResult - localToUTCTimeOffset;
 }
 
 // input is UTC
-void msToGregorianDateTime(ExecState* exec, double ms, bool outputIsUTC, GregorianDateTime& tm)
+void msToGregorianDateTime(VM& vm, double ms, WTF::TimeType outputTimeType, GregorianDateTime& tm)
 {
-    double dstOff = 0.0;
-    double utcOff = 0.0;
-    if (!outputIsUTC) {
-        utcOff = getUTCOffset(exec);
-        dstOff = getDSTOffset(exec, ms, utcOff);
-        ms += dstOff + utcOff;
+    LocalTimeOffset localTime;
+    if (outputTimeType == WTF::LocalTime) {
+        localTime = localTimeOffset(vm, ms);
+        ms += localTime.offset;
     }
 
     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 = nullptr;
+    tm.setSecond(msToSeconds(ms));
+    tm.setMinute(msToMinutes(ms));
+    tm.setHour(msToHours(ms));
+    tm.setWeekDay(msToWeekDay(ms));
+    tm.setYearDay(dayInYear(ms, year));
+    tm.setMonthDay(dayInMonthFromDayInYear(tm.yearDay(), isLeapYear(year)));
+    tm.setMonth(monthFromDayInYear(tm.yearDay(), isLeapYear(year)));
+    tm.setYear(year);
+    tm.setIsDST(localTime.isDST);
+    tm.setUtcOffset(localTime.offset / WTF::msPerSecond);
 }
 
-double parseDateFromNullTerminatedCharacters(ExecState* exec, const char* dateString)
+double parseDateFromNullTerminatedCharacters(VM& vm, const char* dateString)
 {
-    ASSERT(exec);
     bool haveTZ;
     int offset;
-    double ms = WTF::parseDateFromNullTerminatedCharacters(dateString, haveTZ, offset);
-    if (isnan(ms))
+    double localTimeMS = WTF::parseDateFromNullTerminatedCharacters(dateString, haveTZ, offset);
+    if (std::isnan(localTimeMS))
         return std::numeric_limits<double>::quiet_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);
+    // fall back to local timezone.
+    if (!haveTZ)
+        offset = localTimeOffset(vm, localTimeMS, WTF::LocalTime).offset / WTF::msPerMinute;
+
+    return localTimeMS - (offset * WTF::msPerMinute);
+}
+
+double parseDate(ExecState* exec, VM& vm, const String& date)
+{
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    if (date == vm.cachedDateString)
+        return vm.cachedDateStringValue;
+    auto expectedString = date.tryGetUtf8();
+    if (!expectedString) {
+        if (expectedString.error() == UTF8ConversionError::OutOfMemory)
+            throwOutOfMemoryError(exec, scope);
+        // https://tc39.github.io/ecma262/#sec-date-objects section 20.3.3.2 states that:
+        // "Unrecognizable Strings or dates containing illegal element values in the
+        // format String shall cause Date.parse to return NaN."
+        return std::numeric_limits<double>::quiet_NaN();
     }
-    return ms - (offset * WTF::msPerMinute);
+
+    auto dateUtf8 = expectedString.value();
+    double value = parseES5DateFromNullTerminatedCharacters(dateUtf8.data());
+    if (std::isnan(value))
+        value = parseDateFromNullTerminatedCharacters(vm, dateUtf8.data());
+    vm.cachedDateString = date;
+    vm.cachedDateStringValue = value;
+    return value;
 }
 
 } // namespace JSC