[JSC] DateMath should have TimeClipped version
authorysuzuki@apple.com <ysuzuki@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 31 Oct 2019 16:14:03 +0000 (16:14 +0000)
committerysuzuki@apple.com <ysuzuki@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 31 Oct 2019 16:14:03 +0000 (16:14 +0000)
https://bugs.webkit.org/show_bug.cgi?id=203550

Reviewed by Saam Barati.

Source/JavaScriptCore:

Removing `using namespace WTF;` in Date related files in JSC.

* runtime/DateConstructor.cpp:
* runtime/DateConversion.cpp:
(JSC::formatDateTime):
* runtime/DateInstance.cpp:
* runtime/DatePrototype.cpp:
* runtime/JSDateMath.cpp:
(JSC::localTimeOffset):
(JSC::timeToMS):
(JSC::gregorianDateTimeToMS):
(JSC::msToGregorianDateTime):
(JSC::parseDate):
(JSC::msToSeconds): Deleted.
(JSC::msToWeekDay): Deleted.

Source/WTF:

We found that our Date constructor is slow because GregorianDateTime calculation takes so long.
We are doing many `fmod`, floating division, `floor` etc. These operations, in particular `fmod`, takes
very long time. As a result, 30% of JetStream2/date-format-xparb is taken by `fmod` function.

But since we are performance timeClip operation, double value in DateInstance is always Int52. We should
have integer version of GregorianDateTime calculation which avoids many unnecessary fmod etc.

While integer division is truncate-to-zero, many Date calculation requires `floor(value / xxx)`. For now,
we use integer fast path only when the value is Int52 and positive.

We see 10~ % improvement in JetStream2/date-format-xparb-SP (from 201 to 239).

* wtf/DateMath.cpp:
(WTF::isLeapYear): Deleted.
(WTF::daysInYear): Deleted.
(WTF::daysFrom1970ToYear): Deleted.
(WTF::msToDays): Deleted.
(WTF::msToYear): Deleted.
(WTF::dayInYear): Deleted.
(WTF::msToMinutes): Deleted.
(WTF::msToHours): Deleted.
(WTF::monthFromDayInYear): Deleted.
(WTF::checkMonth): Deleted.
(WTF::dayInMonthFromDayInYear): Deleted.
(WTF::dateToDaysFrom1970): Deleted.
(WTF::timeClip): Deleted.
* wtf/DateMath.h:
(WTF::TimeClippedPositiveMilliseconds::TimeClippedPositiveMilliseconds):
(WTF::TimeClippedPositiveMilliseconds::value const):
(WTF::TimeClippedPositiveMilliseconds::asDouble const):
(WTF::timeClip):
(WTF::daysFrom1970ToYear):
(WTF::daysFrom1970ToYearTimeClippedPositive):
(WTF::isLeapYear):
(WTF::daysInYear):
(WTF::msToDays):
(WTF::dayInYear):
(WTF::dateToDaysFrom1970):
(WTF::msToYear):
(WTF::msToMinutes):
(WTF::msToHours):
(WTF::msToSeconds):
(WTF::msToWeekDay):
(WTF::monthFromDayInYear):
(WTF::dayInMonthFromDayInYear):
* wtf/GregorianDateTime.cpp:
(WTF::GregorianDateTime::GregorianDateTime):
* wtf/GregorianDateTime.h:

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251852 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/runtime/DateConstructor.cpp
Source/JavaScriptCore/runtime/DateConversion.cpp
Source/JavaScriptCore/runtime/DateInstance.cpp
Source/JavaScriptCore/runtime/DatePrototype.cpp
Source/JavaScriptCore/runtime/JSDateMath.cpp
Source/WTF/ChangeLog
Source/WTF/wtf/DateMath.cpp
Source/WTF/wtf/DateMath.h
Source/WTF/wtf/GregorianDateTime.cpp
Source/WTF/wtf/GregorianDateTime.h

index d48e963..069ab86 100644 (file)
@@ -1,3 +1,26 @@
+2019-10-31  Yusuke Suzuki  <ysuzuki@apple.com>
+
+        [JSC] DateMath should have TimeClipped version
+        https://bugs.webkit.org/show_bug.cgi?id=203550
+
+        Reviewed by Saam Barati.
+
+        Removing `using namespace WTF;` in Date related files in JSC.
+
+        * runtime/DateConstructor.cpp:
+        * runtime/DateConversion.cpp:
+        (JSC::formatDateTime):
+        * runtime/DateInstance.cpp:
+        * runtime/DatePrototype.cpp:
+        * runtime/JSDateMath.cpp:
+        (JSC::localTimeOffset):
+        (JSC::timeToMS):
+        (JSC::gregorianDateTimeToMS):
+        (JSC::msToGregorianDateTime):
+        (JSC::parseDate):
+        (JSC::msToSeconds): Deleted.
+        (JSC::msToWeekDay): Deleted.
+
 2019-10-30  Peng Liu  <peng.liu6@apple.com>
 
         [Picture-in-Picture Web API] Enable the support for iOS
index 9d7f104..2cf87e8 100644 (file)
@@ -55,8 +55,6 @@ EncodedJSValue JSC_HOST_CALL dateNow(JSGlobalObject*, CallFrame*);
 
 namespace JSC {
 
-using namespace WTF;
-
 const ClassInfo DateConstructor::s_info = { "Function", &InternalFunction::s_info, &dateConstructorTable, nullptr, CREATE_METHOD_TABLE(DateConstructor) };
 
 /* Source for DateConstructor.lut.h
index 5b2aa1a..955756b 100644 (file)
@@ -36,8 +36,6 @@
 
 namespace JSC {
 
-using namespace WTF;
-
 template<int width>
 static inline void appendNumber(StringBuilder& builder, int value)
 {
@@ -68,16 +66,16 @@ String formatDateTime(const GregorianDateTime& t, DateTimeFormat format, bool as
     StringBuilder builder;
 
     if (appendDate) {
-        builder.append(weekdayName[(t.weekDay() + 6) % 7]);
+        builder.append(WTF::weekdayName[(t.weekDay() + 6) % 7]);
 
         if (asUTCVariant) {
             builder.appendLiteral(", ");
             appendNumber<2>(builder, t.monthDay());
             builder.append(' ');
-            builder.append(monthName[t.month()]);
+            builder.append(WTF::monthName[t.month()]);
         } else {
             builder.append(' ');
-            builder.append(monthName[t.month()]);
+            builder.append(WTF::monthName[t.month()]);
             builder.append(' ');
             appendNumber<2>(builder, t.monthDay());
         }
index 8f5c2ed..c8079d5 100644 (file)
@@ -30,8 +30,6 @@
 
 namespace JSC {
 
-using namespace WTF;
-
 const ClassInfo DateInstance::s_info = {"Date", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(DateInstance)};
 
 DateInstance::DateInstance(VM& vm, Structure* structure)
index 2aff0f7..984f611 100644 (file)
@@ -69,8 +69,6 @@
 
 namespace JSC {
 
-using namespace WTF;
-
 EncodedJSValue JSC_HOST_CALL dateProtoFuncGetDate(JSGlobalObject*, CallFrame*);
 EncodedJSValue JSC_HOST_CALL dateProtoFuncGetDay(JSGlobalObject*, CallFrame*);
 EncodedJSValue JSC_HOST_CALL dateProtoFuncGetFullYear(JSGlobalObject*, CallFrame*);
index 6c8b379..64826ab 100644 (file)
 
 namespace JSC {
 
-using namespace WTF;
-
-static inline double timeToMS(double hour, double min, double sec, double ms)
-{
-    return (((hour * minutesPerHour + min) * secondsPerMinute + sec) * msPerSecond + ms);
-}
-
-static inline int msToSeconds(double ms)
-{
-    double result = fmod(floor(ms / msPerSecond), secondsPerMinute);
-    if (result < 0)
-        result += secondsPerMinute;
-    return static_cast<int>(result);
-}
-
-// 0: Sunday, 1: Monday, etc.
-static inline int msToWeekDay(double ms)
-{
-    int wd = (static_cast<int>(msToDays(ms)) + 4) % 7;
-    if (wd < 0)
-        wd += 7;
-    return wd;
-}
-
 // Get the combined UTC + DST offset for the time passed in.
 //
 // NOTE: The implementation relies on the fact that no time zones have
@@ -152,7 +128,7 @@ static LocalTimeOffset localTimeOffset(VM& vm, double ms, WTF::TimeType inputTim
                 // the offset in the cache, we grow the cached time interval
                 // and return the offset.
                 cache.end = newEnd;
-                cache.increment = msPerMonth;
+                cache.increment = WTF::msPerMonth;
                 return endOffset;
             }
             LocalTimeOffset offset = calculateLocalTimeOffset(ms, inputTimeType);
@@ -163,7 +139,7 @@ static LocalTimeOffset localTimeOffset(VM& vm, double ms, WTF::TimeType inputTim
                 // the interval to reflect this and reset the increment.
                 cache.start = ms;
                 cache.end = newEnd;
-                cache.increment = msPerMonth;
+                cache.increment = WTF::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
@@ -184,17 +160,22 @@ static LocalTimeOffset localTimeOffset(VM& vm, double ms, WTF::TimeType inputTim
     cache.offset = offset;
     cache.start = ms;
     cache.end = ms;
-    cache.increment = msPerMonth;
+    cache.increment = WTF::msPerMonth;
     return offset;
 }
 
+static inline double timeToMS(double hour, double min, double sec, double ms)
+{
+    return (((hour * WTF::minutesPerHour + min) * WTF::secondsPerMinute + sec) * WTF::msPerSecond + ms);
+}
+
 double gregorianDateTimeToMS(VM& vm, const GregorianDateTime& t, double milliSeconds, WTF::TimeType inputTimeType)
 {
     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
+    double localToUTCTimeOffset = inputTimeType == WTF::LocalTime
         ? localTimeOffset(vm, localTimeResult, inputTimeType).offset : 0;
 
     return localTimeResult - localToUTCTimeOffset;
@@ -208,18 +189,7 @@ void msToGregorianDateTime(VM& vm, double ms, WTF::TimeType outputTimeType, Greg
         localTime = localTimeOffset(vm, ms);
         ms += localTime.offset;
     }
-
-    const int year = msToYear(ms);
-    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.setUTCOffsetInMinute(localTime.offset / WTF::msPerMinute);
+    tm = GregorianDateTime(ms, localTime);
 }
 
 double parseDateFromNullTerminatedCharacters(VM& vm, const char* dateString)
@@ -254,7 +224,7 @@ double parseDate(JSGlobalObject* globalObject, VM& vm, const String& date)
     }
 
     auto dateUtf8 = expectedString.value();
-    double value = parseES5DateFromNullTerminatedCharacters(dateUtf8.data());
+    double value = WTF::parseES5DateFromNullTerminatedCharacters(dateUtf8.data());
     if (std::isnan(value))
         value = parseDateFromNullTerminatedCharacters(vm, dateUtf8.data());
     vm.cachedDateString = date;
index 5e60b1b..9cb7e0e 100644 (file)
@@ -1,3 +1,59 @@
+2019-10-31  Yusuke Suzuki  <ysuzuki@apple.com>
+
+        [JSC] DateMath should have TimeClipped version
+        https://bugs.webkit.org/show_bug.cgi?id=203550
+
+        Reviewed by Saam Barati.
+
+        We found that our Date constructor is slow because GregorianDateTime calculation takes so long.
+        We are doing many `fmod`, floating division, `floor` etc. These operations, in particular `fmod`, takes
+        very long time. As a result, 30% of JetStream2/date-format-xparb is taken by `fmod` function.
+
+        But since we are performance timeClip operation, double value in DateInstance is always Int52. We should
+        have integer version of GregorianDateTime calculation which avoids many unnecessary fmod etc.
+
+        While integer division is truncate-to-zero, many Date calculation requires `floor(value / xxx)`. For now,
+        we use integer fast path only when the value is Int52 and positive.
+
+        We see 10~ % improvement in JetStream2/date-format-xparb-SP (from 201 to 239).
+
+        * wtf/DateMath.cpp:
+        (WTF::isLeapYear): Deleted.
+        (WTF::daysInYear): Deleted.
+        (WTF::daysFrom1970ToYear): Deleted.
+        (WTF::msToDays): Deleted.
+        (WTF::msToYear): Deleted.
+        (WTF::dayInYear): Deleted.
+        (WTF::msToMinutes): Deleted.
+        (WTF::msToHours): Deleted.
+        (WTF::monthFromDayInYear): Deleted.
+        (WTF::checkMonth): Deleted.
+        (WTF::dayInMonthFromDayInYear): Deleted.
+        (WTF::dateToDaysFrom1970): Deleted.
+        (WTF::timeClip): Deleted.
+        * wtf/DateMath.h:
+        (WTF::TimeClippedPositiveMilliseconds::TimeClippedPositiveMilliseconds):
+        (WTF::TimeClippedPositiveMilliseconds::value const):
+        (WTF::TimeClippedPositiveMilliseconds::asDouble const):
+        (WTF::timeClip):
+        (WTF::daysFrom1970ToYear):
+        (WTF::daysFrom1970ToYearTimeClippedPositive):
+        (WTF::isLeapYear):
+        (WTF::daysInYear):
+        (WTF::msToDays):
+        (WTF::dayInYear):
+        (WTF::dateToDaysFrom1970):
+        (WTF::msToYear):
+        (WTF::msToMinutes):
+        (WTF::msToHours):
+        (WTF::msToSeconds):
+        (WTF::msToWeekDay):
+        (WTF::monthFromDayInYear):
+        (WTF::dayInMonthFromDayInYear):
+        * wtf/GregorianDateTime.cpp:
+        (WTF::GregorianDateTime::GregorianDateTime):
+        * wtf/GregorianDateTime.h:
+
 2019-10-30  Alex Christensen  <achristensen@webkit.org>
 
         Prevent Mac CMake build from bit rotting
index c0ab2e1..0bacd1f 100644 (file)
@@ -109,15 +109,13 @@ template<unsigned length> inline bool startsWithLettersIgnoringASCIICase(const c
 
 /* Constants */
 
-static constexpr 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 constexpr double maxECMAScriptTime = 8.64E15;
+const char* const weekdayName[7] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
+const char* const monthName[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+const char* const monthFullName[12] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };
 
 // 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.
-static const int firstDayOfMonth[2][12] = {
+const int firstDayOfMonth[2][12] = {
     {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
     {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}
 };
@@ -133,46 +131,6 @@ static inline void getLocalTime(const time_t* localTime, struct tm* localTM)
 }
 #endif
 
-bool isLeapYear(int year)
-{
-    if (year % 4 != 0)
-        return false;
-    if (year % 400 == 0)
-        return true;
-    if (year % 100 == 0)
-        return false;
-    return true;
-}
-
-static inline int daysInYear(int year)
-{
-    return 365 + isLeapYear(year);
-}
-
-static inline double daysFrom1970ToYear(int year)
-{
-    // The Gregorian Calendar rules for leap years:
-    // Every fourth year is a leap year.  2004, 2008, and 2012 are leap years.
-    // However, every hundredth year is not a leap year.  1900 and 2100 are not leap years.
-    // Every four hundred years, there's a leap year after all.  2000 and 2400 are leap years.
-
-    static constexpr int leapDaysBefore1971By4Rule = 1970 / 4;
-    static constexpr int excludedLeapDaysBefore1971By100Rule = 1970 / 100;
-    static constexpr int leapDaysBefore1971By400Rule = 1970 / 400;
-
-    const double yearMinusOne = year - 1;
-    const double yearsToAddBy4Rule = floor(yearMinusOne / 4.0) - leapDaysBefore1971By4Rule;
-    const double yearsToExcludeBy100Rule = floor(yearMinusOne / 100.0) - excludedLeapDaysBefore1971By100Rule;
-    const double yearsToAddBy400Rule = floor(yearMinusOne / 400.0) - leapDaysBefore1971By400Rule;
-
-    return 365.0 * (year - 1970.0) + yearsToAddBy4Rule - yearsToExcludeBy100Rule + yearsToAddBy400Rule;
-}
-
-double msToDays(double ms)
-{
-    return floor(ms / msPerDay);
-}
-
 static void appendTwoDigitNumber(StringBuilder& builder, int number)
 {
     ASSERT(number >= 0);
@@ -181,22 +139,6 @@ static void appendTwoDigitNumber(StringBuilder& builder, int number)
     builder.append(static_cast<LChar>('0' + number % 10));
 }
 
-int msToYear(double ms)
-{
-    int approxYear = static_cast<int>(floor(ms / (msPerDay * 365.2425)) + 1970);
-    double msFromApproxYearTo1970 = msPerDay * daysFrom1970ToYear(approxYear);
-    if (msFromApproxYearTo1970 > ms)
-        return approxYear - 1;
-    if (msFromApproxYearTo1970 + msPerDay * daysInYear(approxYear) <= ms)
-        return approxYear + 1;
-    return approxYear;
-}
-
-int dayInYear(double ms, int year)
-{
-    return static_cast<int>(msToDays(ms) - daysFrom1970ToYear(year));
-}
-
 static inline double msToMilliseconds(double ms)
 {
     double result = fmod(ms, msPerDay);
@@ -205,113 +147,6 @@ static inline double msToMilliseconds(double ms)
     return result;
 }
 
-int msToMinutes(double ms)
-{
-    double result = fmod(floor(ms / msPerMinute), minutesPerHour);
-    if (result < 0)
-        result += minutesPerHour;
-    return static_cast<int>(result);
-}
-
-int msToHours(double ms)
-{
-    double result = fmod(floor(ms/msPerHour), hoursPerDay);
-    if (result < 0)
-        result += hoursPerDay;
-    return static_cast<int>(result);
-}
-
-int monthFromDayInYear(int dayInYear, bool leapYear)
-{
-    const int d = dayInYear;
-    int step;
-
-    if (d < (step = 31))
-        return 0;
-    step += (leapYear ? 29 : 28);
-    if (d < step)
-        return 1;
-    if (d < (step += 31))
-        return 2;
-    if (d < (step += 30))
-        return 3;
-    if (d < (step += 31))
-        return 4;
-    if (d < (step += 30))
-        return 5;
-    if (d < (step += 31))
-        return 6;
-    if (d < (step += 31))
-        return 7;
-    if (d < (step += 30))
-        return 8;
-    if (d < (step += 31))
-        return 9;
-    if (d < (step += 30))
-        return 10;
-    return 11;
-}
-
-static inline bool checkMonth(int dayInYear, int& startDayOfThisMonth, int& startDayOfNextMonth, int daysInThisMonth)
-{
-    startDayOfThisMonth = startDayOfNextMonth;
-    startDayOfNextMonth += daysInThisMonth;
-    return (dayInYear <= startDayOfNextMonth);
-}
-
-int dayInMonthFromDayInYear(int dayInYear, bool leapYear)
-{
-    const int d = dayInYear;
-    int step;
-    int next = 30;
-
-    if (d <= next)
-        return d + 1;
-    const int daysInFeb = (leapYear ? 29 : 28);
-    if (checkMonth(d, step, next, daysInFeb))
-        return d - step;
-    if (checkMonth(d, step, next, 31))
-        return d - step;
-    if (checkMonth(d, step, next, 30))
-        return d - step;
-    if (checkMonth(d, step, next, 31))
-        return d - step;
-    if (checkMonth(d, step, next, 30))
-        return d - step;
-    if (checkMonth(d, step, next, 31))
-        return d - step;
-    if (checkMonth(d, step, next, 31))
-        return d - step;
-    if (checkMonth(d, step, next, 30))
-        return d - step;
-    if (checkMonth(d, step, next, 31))
-        return d - step;
-    if (checkMonth(d, step, next, 30))
-        return d - step;
-    step = next;
-    return d - step;
-}
-
-int dayInYear(int year, int month, int day)
-{
-    return firstDayOfMonth[isLeapYear(year)][month] + day - 1;
-}
-
-double dateToDaysFrom1970(int year, int month, int day)
-{
-    year += month / 12;
-
-    month %= 12;
-    if (month < 0) {
-        month += 12;
-        --year;
-    }
-
-    double yearday = floor(daysFrom1970ToYear(year));
-    ASSERT((year >= 1970 && yearday >= 0) || (year < 1970 && yearday < 0));
-    return yearday + dayInYear(year, month, day);
-}
-
 // There is a hard limit at 2038 that we currently do not have a workaround
 // for (rdar://problem/5052975).
 static inline int maximumYearForDST()
@@ -1159,13 +994,6 @@ double parseDateFromNullTerminatedCharacters(const char* dateString)
     return ms - (offset * msPerMinute);
 }
 
-double timeClip(double t)
-{
-    if (std::abs(t) > maxECMAScriptTime)
-        return std::numeric_limits<double>::quiet_NaN();
-    return std::trunc(t) + 0.0;
-}
-
 // See http://tools.ietf.org/html/rfc2822#section-3.3 for more information.
 String makeRFC2822DateString(unsigned dayOfWeek, unsigned day, unsigned month, unsigned year, unsigned hours, unsigned minutes, unsigned seconds, int utcOffset)
 {
index 7e2d1de..5ad8c33 100644 (file)
@@ -92,7 +92,6 @@ int equivalentYearForDST(int year);
 WTF_EXPORT_PRIVATE double parseES5DateFromNullTerminatedCharacters(const char* dateString);
 WTF_EXPORT_PRIVATE double parseDateFromNullTerminatedCharacters(const char* dateString);
 WTF_EXPORT_PRIVATE double parseDateFromNullTerminatedCharacters(const char* dateString, bool& haveTZ, int& offset);
-WTF_EXPORT_PRIVATE double timeClip(double);
 // dayOfWeek: [0, 6] 0 being Monday, day: [1, 31], month: [0, 11], year: ex: 2011, hours: [0, 23], minutes: [0, 59], seconds: [0, 59], utcOffset: [-720,720]. 
 String makeRFC2822DateString(unsigned dayOfWeek, unsigned day, unsigned month, unsigned year, unsigned hours, unsigned minutes, unsigned seconds, int utcOffset);
 
@@ -102,9 +101,10 @@ inline double jsCurrentTime()
     return floor(WallTime::now().secondsSinceEpoch().milliseconds());
 }
 
-const char* const weekdayName[7] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
-const char* const monthName[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
-const char* const monthFullName[12] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };
+extern WTF_EXPORT_PRIVATE const char* const weekdayName[7];
+extern WTF_EXPORT_PRIVATE const char* const monthName[12];
+extern WTF_EXPORT_PRIVATE const char* const monthFullName[12];
+extern WTF_EXPORT_PRIVATE const int firstDayOfMonth[2][12];
 
 static constexpr double hoursPerDay = 24.0;
 static constexpr double minutesPerHour = 60.0;
@@ -117,18 +117,277 @@ static constexpr double msPerMinute = msPerSecond * secondsPerMinute;
 static constexpr double msPerHour = msPerSecond * secondsPerHour;
 static constexpr double msPerDay = msPerSecond * secondsPerDay;
 
-WTF_EXPORT_PRIVATE bool isLeapYear(int year);
+static constexpr 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 constexpr double maxECMAScriptTime = 8.64E15;
+
+class TimeClippedPositiveMilliseconds {
+public:
+    static constexpr int64_t hoursPerDay = 24;
+    static constexpr int64_t minutesPerHour = 60;
+    static constexpr int64_t secondsPerMinute = 60;
+    static constexpr int64_t msPerSecond = 1000;
+    static constexpr int64_t msPerMonth = 2592000000;
+    static constexpr int64_t secondsPerHour = secondsPerMinute * minutesPerHour;
+    static constexpr int64_t secondsPerDay = secondsPerHour * hoursPerDay;
+    static constexpr int64_t msPerMinute = msPerSecond * secondsPerMinute;
+    static constexpr int64_t msPerHour = msPerSecond * secondsPerHour;
+    static constexpr int64_t msPerDay = msPerSecond * secondsPerDay;
+    static constexpr int64_t maxECMAScriptTime = 8.64E15;
+
+    explicit TimeClippedPositiveMilliseconds(int64_t value)
+        : m_value(value)
+    {
+        ASSERT(value >= 0);
+    }
+
+    int64_t value() const { return m_value; }
+    double asDouble() const { return static_cast<double>(m_value); }
+private:
+    int64_t m_value;
+};
+
+inline double timeClip(double t)
+{
+    if (std::abs(t) > maxECMAScriptTime)
+        return std::numeric_limits<double>::quiet_NaN();
+    return std::trunc(t) + 0.0;
+}
+
+inline double daysFrom1970ToYear(int year)
+{
+    // The Gregorian Calendar rules for leap years:
+    // Every fourth year is a leap year. 2004, 2008, and 2012 are leap years.
+    // However, every hundredth year is not a leap year. 1900 and 2100 are not leap years.
+    // Every four hundred years, there's a leap year after all. 2000 and 2400 are leap years.
+
+    static constexpr int leapDaysBefore1971By4Rule = 1970 / 4;
+    static constexpr int excludedLeapDaysBefore1971By100Rule = 1970 / 100;
+    static constexpr int leapDaysBefore1971By400Rule = 1970 / 400;
+
+    const double yearMinusOne = year - 1;
+    const double yearsToAddBy4Rule = floor(yearMinusOne / 4.0) - leapDaysBefore1971By4Rule;
+    const double yearsToExcludeBy100Rule = floor(yearMinusOne / 100.0) - excludedLeapDaysBefore1971By100Rule;
+    const double yearsToAddBy400Rule = floor(yearMinusOne / 400.0) - leapDaysBefore1971By400Rule;
+
+    return 365.0 * (year - 1970.0) + yearsToAddBy4Rule - yearsToExcludeBy100Rule + yearsToAddBy400Rule;
+}
+
+inline int64_t daysFrom1970ToYearTimeClippedPositive(int year)
+{
+    static constexpr int leapDaysBefore1971By4Rule = 1970 / 4;
+    static constexpr int excludedLeapDaysBefore1971By100Rule = 1970 / 100;
+    static constexpr int leapDaysBefore1971By400Rule = 1970 / 400;
+
+    ASSERT(year >= 1970);
+    const int64_t yearMinusOne = year - 1;
+    const int64_t yearsToAddBy4Rule = yearMinusOne / 4.0 - leapDaysBefore1971By4Rule;
+    const int64_t yearsToExcludeBy100Rule = yearMinusOne / 100.0 - excludedLeapDaysBefore1971By100Rule;
+    const int64_t yearsToAddBy400Rule = yearMinusOne / 400.0 - leapDaysBefore1971By400Rule;
+
+    return 365 * (year - 1970) + yearsToAddBy4Rule - yearsToExcludeBy100Rule + yearsToAddBy400Rule;
+}
+
+inline bool isLeapYear(int year)
+{
+    if (year % 4 != 0)
+        return false;
+    if (year % 400 == 0)
+        return true;
+    if (year % 100 == 0)
+        return false;
+    return true;
+}
+
+inline int daysInYear(int year)
+{
+    return 365 + isLeapYear(year);
+}
+
+inline double msToDays(double ms)
+{
+    return floor(ms / msPerDay);
+}
+
+inline int64_t msToDays(TimeClippedPositiveMilliseconds ms)
+{
+    return ms.value() / TimeClippedPositiveMilliseconds::msPerDay;
+}
+
+inline int dayInYear(int year, int month, int day)
+{
+    return firstDayOfMonth[isLeapYear(year)][month] + day - 1;
+}
+
+inline int dayInYear(double ms, int year)
+{
+    return static_cast<int>(msToDays(ms) - daysFrom1970ToYear(year));
+}
+
+inline int dayInYear(TimeClippedPositiveMilliseconds ms, int year)
+{
+    return static_cast<int>(msToDays(ms) - daysFrom1970ToYearTimeClippedPositive(year));
+}
 
 // Returns the number of days from 1970-01-01 to the specified date.
-WTF_EXPORT_PRIVATE double dateToDaysFrom1970(int year, int month, int day);
-WTF_EXPORT_PRIVATE int msToYear(double ms);
-WTF_EXPORT_PRIVATE double msToDays(double ms);
-WTF_EXPORT_PRIVATE int msToMinutes(double ms);
-WTF_EXPORT_PRIVATE int msToHours(double ms);
-WTF_EXPORT_PRIVATE int dayInYear(int year, int month, int day);
-WTF_EXPORT_PRIVATE int dayInYear(double ms, int year);
-WTF_EXPORT_PRIVATE int monthFromDayInYear(int dayInYear, bool leapYear);
-WTF_EXPORT_PRIVATE int dayInMonthFromDayInYear(int dayInYear, bool leapYear);
+inline double dateToDaysFrom1970(int year, int month, int day)
+{
+    year += month / 12;
+
+    month %= 12;
+    if (month < 0) {
+        month += 12;
+        --year;
+    }
+
+    double yearday = floor(daysFrom1970ToYear(year));
+    ASSERT((year >= 1970 && yearday >= 0) || (year < 1970 && yearday < 0));
+    return yearday + dayInYear(year, month, day);
+}
+
+inline int msToYear(double ms)
+{
+    int approxYear = static_cast<int>(floor(ms / (msPerDay * 365.2425)) + 1970);
+    double msFromApproxYearTo1970 = msPerDay * daysFrom1970ToYear(approxYear);
+    if (msFromApproxYearTo1970 > ms)
+        return approxYear - 1;
+    if (msFromApproxYearTo1970 + msPerDay * daysInYear(approxYear) <= ms)
+        return approxYear + 1;
+    return approxYear;
+}
+
+inline int msToMinutes(double ms)
+{
+    double result = fmod(floor(ms / msPerMinute), minutesPerHour);
+    if (result < 0)
+        result += minutesPerHour;
+    return static_cast<int>(result);
+}
+
+inline int msToMinutes(TimeClippedPositiveMilliseconds ms)
+{
+    int64_t result = (ms.value() / TimeClippedPositiveMilliseconds::msPerMinute) % TimeClippedPositiveMilliseconds::minutesPerHour;
+    ASSERT(result >= 0);
+    return static_cast<int>(result);
+}
+
+inline int msToHours(double ms)
+{
+    double result = fmod(floor(ms / msPerHour), hoursPerDay);
+    if (result < 0)
+        result += hoursPerDay;
+    return static_cast<int>(result);
+}
+
+inline int msToHours(TimeClippedPositiveMilliseconds ms)
+{
+    int64_t result = (ms.value() / TimeClippedPositiveMilliseconds::msPerHour) % TimeClippedPositiveMilliseconds::hoursPerDay;
+    ASSERT(result >= 0);
+    return static_cast<int>(result);
+}
+
+inline int msToSeconds(double ms)
+{
+    double result = fmod(floor(ms / msPerSecond), secondsPerMinute);
+    if (result < 0)
+        result += secondsPerMinute;
+    return static_cast<int>(result);
+}
+
+inline int msToSeconds(TimeClippedPositiveMilliseconds ms)
+{
+    int64_t result = ms.value() / TimeClippedPositiveMilliseconds::msPerSecond % TimeClippedPositiveMilliseconds::secondsPerMinute;
+    ASSERT(result >= 0);
+    return static_cast<int>(result);
+}
+
+// 0: Sunday, 1: Monday, etc.
+inline int msToWeekDay(double ms)
+{
+    int wd = (static_cast<int>(msToDays(ms)) + 4) % 7;
+    if (wd < 0)
+        wd += 7;
+    return wd;
+}
+
+inline int msToWeekDay(TimeClippedPositiveMilliseconds ms)
+{
+    int result = (static_cast<int>(msToDays(ms)) + 4) % 7;
+    ASSERT(result >= 0);
+    return result;
+}
+
+inline int monthFromDayInYear(int dayInYear, bool leapYear)
+{
+    const int d = dayInYear;
+    int step;
+
+    if (d < (step = 31))
+        return 0;
+    step += (leapYear ? 29 : 28);
+    if (d < step)
+        return 1;
+    if (d < (step += 31))
+        return 2;
+    if (d < (step += 30))
+        return 3;
+    if (d < (step += 31))
+        return 4;
+    if (d < (step += 30))
+        return 5;
+    if (d < (step += 31))
+        return 6;
+    if (d < (step += 31))
+        return 7;
+    if (d < (step += 30))
+        return 8;
+    if (d < (step += 31))
+        return 9;
+    if (d < (step += 30))
+        return 10;
+    return 11;
+}
+
+inline int dayInMonthFromDayInYear(int dayInYear, bool leapYear)
+{
+    auto checkMonth = [] (int dayInYear, int& startDayOfThisMonth, int& startDayOfNextMonth, int daysInThisMonth) -> bool {
+        startDayOfThisMonth = startDayOfNextMonth;
+        startDayOfNextMonth += daysInThisMonth;
+        return (dayInYear <= startDayOfNextMonth);
+    };
+
+    const int d = dayInYear;
+    int step;
+    int next = 30;
+
+    if (d <= next)
+        return d + 1;
+    const int daysInFeb = (leapYear ? 29 : 28);
+    if (checkMonth(d, step, next, daysInFeb))
+        return d - step;
+    if (checkMonth(d, step, next, 31))
+        return d - step;
+    if (checkMonth(d, step, next, 30))
+        return d - step;
+    if (checkMonth(d, step, next, 31))
+        return d - step;
+    if (checkMonth(d, step, next, 30))
+        return d - step;
+    if (checkMonth(d, step, next, 31))
+        return d - step;
+    if (checkMonth(d, step, next, 31))
+        return d - step;
+    if (checkMonth(d, step, next, 30))
+        return d - step;
+    if (checkMonth(d, step, next, 31))
+        return d - step;
+    if (checkMonth(d, step, next, 30))
+        return d - step;
+    step = next;
+    return d - step;
+}
 
 // Returns combined offset in millisecond (UTC + DST).
 WTF_EXPORT_PRIVATE LocalTimeOffset calculateLocalTimeOffset(double utcInMilliseconds, TimeType = UTCTime);
@@ -155,3 +414,5 @@ using WTF::parseDateFromNullTerminatedCharacters;
 using WTF::makeRFC2822DateString;
 using WTF::LocalTimeOffset;
 using WTF::calculateLocalTimeOffset;
+using WTF::timeClip;
+using WTF::jsCurrentTime;
index 3214887..6c1f75f 100644 (file)
 
 namespace WTF {
 
+GregorianDateTime::GregorianDateTime(double ms, LocalTimeOffset localTime)
+{
+    if (ms >= 0
+#if OS(WINDOWS) && CPU(X86)
+            // The VS Compiler for 32-bit builds generates a floating point error when attempting to cast
+            // from an infinity to a 64-bit integer. We leave this routine with the floating point error
+            // left in a register, causing undefined behavior in later floating point operations.
+            //
+            // To avoid this issue, we check for infinity here, and return false in that case.
+            && !std::isinf(ms)
+#endif
+        ) {
+        int64_t integer = static_cast<int64_t>(ms);
+        if (static_cast<double>(integer) == ms && integer <= maxECMAScriptTime) {
+            // Positive integer fast path.
+            WTF::TimeClippedPositiveMilliseconds timeClipped(integer);
+            const int year = msToYear(ms);
+            setSecond(msToSeconds(timeClipped));
+            setMinute(msToMinutes(timeClipped));
+            setHour(msToHours(timeClipped));
+            setWeekDay(msToWeekDay(timeClipped));
+            int yearDay = dayInYear(timeClipped, year);
+            bool leapYear = isLeapYear(year);
+            setYearDay(yearDay);
+            setMonthDay(dayInMonthFromDayInYear(yearDay, leapYear));
+            setMonth(monthFromDayInYear(yearDay, leapYear));
+            setYear(year);
+            setIsDST(localTime.isDST);
+            setUTCOffsetInMinute(localTime.offset / WTF::msPerMinute);
+            return;
+        }
+    }
+    const int year = msToYear(ms);
+    setSecond(msToSeconds(ms));
+    setMinute(msToMinutes(ms));
+    setHour(msToHours(ms));
+    setWeekDay(msToWeekDay(ms));
+    int yearDay = dayInYear(ms, year);
+    bool leapYear = isLeapYear(year);
+    setYearDay(yearDay);
+    setMonthDay(dayInMonthFromDayInYear(yearDay, leapYear));
+    setMonth(monthFromDayInYear(yearDay, leapYear));
+    setYear(year);
+    setIsDST(localTime.isDST);
+    setUTCOffsetInMinute(localTime.offset / WTF::msPerMinute);
+}
+
 void GregorianDateTime::setToCurrentLocalTime()
 {
 #if OS(WINDOWS)
index 02c2ec7..9c4bd13 100644 (file)
@@ -36,6 +36,7 @@ class GregorianDateTime final {
     WTF_MAKE_FAST_ALLOCATED;
 public:
     GregorianDateTime() = default;
+    WTF_EXPORT_PRIVATE explicit GregorianDateTime(double ms, LocalTimeOffset);
 
     inline int year() const { return m_year; }
     inline int month() const { return m_month; }