Explorar o código

cross platform: add edge date cases support

This PR improves xplat date implementation with edge cases support.
Now test cases for Date passes on Ubuntu / Debian

  - tested against VSOFull and its test cases
  - no perf difference measured on Windows

Some details on what's been done with this update.

  - Linux implementation no longer converts from Windows SYSTEMTIME.
    ChakraCore's internal YMD structure was a good fit between tm and
    SYSTEMTIME. Besides, new implementation spends less time there.

  - Now xplat handles out of range year cases ( <1900 or > 2100) and
    Feb28/29 + 1 (see test file under Date)

  - ChakraCore's common date utilities consider the start year is epoch.
    Besides, system time and clock is based on epoch too. However, the C
    api that calculates the time zone offset, and DST in case of leap year
    depends on broken-out time starting from 1900.
    We handle the leap year differences.

  - On xplat we need exact date to grab right tm_zone information instead
    of caching approach on Windows. Windows API can bring back both DTS and
    STD abbrv with a single call.
Oguz Bastemur %!s(int64=9) %!d(string=hai) anos
pai
achega
445901f9fc

+ 6 - 2
lib/Common/PlatformAgnostic/DateTime.h

@@ -29,8 +29,12 @@ namespace DateTime
     {
         UtilityPlatformData data;
     public:
-        const WCHAR *GetStandardName(size_t *nameLength);
-        const WCHAR *GetDaylightName(size_t *nameLength);
+        const WCHAR *GetStandardName(size_t *nameLength,
+                                     // xplat implementation needs an actual
+                                     // date for the zone abbr.
+                                     const DateTime::YMD *ymd = NULL);
+        const WCHAR *GetDaylightName(size_t *nameLength,
+                                     const DateTime::YMD *ymd = NULL);
     };
 
     // Decomposed date (Year-Month-Date).

+ 5 - 9
lib/Common/PlatformAgnostic/DateTimeInternal.h

@@ -84,16 +84,12 @@ namespace DateTime
 
     typedef void* DaylightTimeHelperPlatformData;
 
-    class UtilityPlatformData
+    #define __CC_PA_TIMEZONE_ABVR_NAME_LENGTH 32
+    struct UtilityPlatformData
     {
-    public:
-        WCHAR standardZoneName[32];
-        size_t standardZoneNameLength;
-        uint32 lastTimeZoneUpdateTickCount;
-
-        void UpdateTimeZoneInfo();
-
-        UtilityPlatformData(): lastTimeZoneUpdateTickCount(0) { }
+        // cache always the last date's zone
+        WCHAR standardName[__CC_PA_TIMEZONE_ABVR_NAME_LENGTH];
+        size_t standardNameLength;
     };
 
     class HiresTimerPlatformData

+ 4 - 4
lib/Runtime/Base/ScriptContext.h

@@ -745,14 +745,14 @@ private:
         DateTime::Utility dateTimeUtility;
 
 public:
-        inline const WCHAR *const GetStandardName(size_t *nameLength)
+        inline const WCHAR *const GetStandardName(size_t *nameLength, DateTime::YMD *ymd = NULL)
         {
-            return dateTimeUtility.GetStandardName(nameLength);
+            return dateTimeUtility.GetStandardName(nameLength, ymd);
         }
 
-        inline const WCHAR *const GetDaylightName(size_t *nameLength)
+        inline const WCHAR *const GetDaylightName(size_t *nameLength, DateTime::YMD *ymd = NULL)
         {
-            return dateTimeUtility.GetDaylightName(nameLength);
+            return dateTimeUtility.GetDaylightName(nameLength, ymd);
         }
 
 private:

+ 2 - 2
lib/Runtime/Library/DateImplementation.h

@@ -476,13 +476,13 @@ namespace Js {
             if (ptzd->fDst == false)
             {
                 size_t nameLength;
-                const WCHAR *const standardName = scriptContext->GetStandardName(&nameLength);
+                const WCHAR *const standardName = scriptContext->GetStandardName(&nameLength, pymd);
                 bs->AppendChars(standardName, static_cast<CharCount>(nameLength));
             }
             else
             {
                 size_t nameLength;
-                const WCHAR *const daylightName = scriptContext->GetDaylightName(&nameLength);
+                const WCHAR *const daylightName = scriptContext->GetDaylightName(&nameLength, pymd);
                 bs->AppendChars(daylightName, static_cast<CharCount>(nameLength));
             }
 

+ 137 - 85
lib/Runtime/PlatformAgnostic/Platform/Linux/DateTime.cpp

@@ -13,91 +13,142 @@ namespace PlatformAgnostic
 namespace DateTime
 {
 
-    #define updatePeriod 1000
-
-    const WCHAR *Utility::GetStandardName(size_t *nameLength) {
-        data.UpdateTimeZoneInfo();
-        *nameLength = data.standardZoneNameLength;
-        return data.standardZoneName;
+    static inline bool IsLeap(const int year)
+    {
+        return (0 == (year & 3)) && (0 != (year % 100) || 0 == (year % 400));
     }
 
-    const WCHAR *Utility::GetDaylightName(size_t *nameLength) {
-        // We have an abbreviated standard or daylight name
-        // based on the date and time zone we are in.
-        return GetStandardName(nameLength);
+    // Windows DateTime implementation normalizes the year beyond <1900 >2100
+    // mktime etc. broken-out time bases 1900
+    static inline int NormalizeYMDYear(const int base_year)
+    {
+        if (base_year < -2100)
+        {
+            return 2100;
+        }
+        else if (base_year < -1900)
+        {
+            return base_year * -1;
+        }
+
+        return base_year;
     }
 
-    void UtilityPlatformData::UpdateTimeZoneInfo()
+    static inline int UpdateToYMDYear(const int base_year, const struct tm *time)
     {
-        uint32 tickCount = GetTickCount();
-        if (tickCount - lastTimeZoneUpdateTickCount > updatePeriod)
+        int year = time->tm_year;
+
+        if (base_year < -2100)
         {
-            time_t gtime = time(NULL);
-            struct tm ltime;
+            const int diff = year - 2100;
+            year = abs(base_year) - diff;
+        }
+
+        if (base_year < -1900)
+        {
+            year *= -1;
+        }
+
+        return year;
+    }
+
+    static void YMD_TO_TM(const YMD *ymd, struct tm *time, bool *leap_added)
+    {
+        time->tm_year = NormalizeYMDYear(ymd->year);
+        time->tm_mon = ymd->mon;
+        time->tm_wday = ymd->wday;
+        time->tm_mday = ymd->mday;
+        int t = ymd->time;
+        t /= DateTimeTicks_PerSecond; // discard ms
+        time->tm_sec = t % 60;
+        t /= 60;
+        time->tm_min = t % 60;
+        t /= 60;
+        time->tm_hour = t;
+
+        // mktime etc. broken-out time accepts 1900 as a start year while epoch is 1970
+        // temporarily add a calendar day for leap pass
+        bool leap_year = IsLeap(time->tm_year);
+        *leap_added = false;
+        if (ymd->yday == 60 && leap_year) {
+            time->tm_mday++;
+            *leap_added = true;
+        }
+    }
 
-            tzset();
-            localtime_r(&gtime, &ltime);
+    static void TM_TO_YMD(const struct tm *time, YMD *ymd, const bool leap_added, const int base_year)
+    {
+        ymd->year = UpdateToYMDYear(base_year, time);
+        ymd->mon = time->tm_mon;
+        ymd->mday = time->tm_mday;
 
-            standardZoneNameLength = strlen(ltime.tm_zone);
+        ymd->time = (time->tm_hour * DateTimeTicks_PerHour)
+                  + (time->tm_min * DateTimeTicks_PerMinute)
+                  + (time->tm_sec * DateTimeTicks_PerSecond);
 
-        #if defined(WCHAR_IS_CHAR16_T)
-            for(int i = 0; i < standardZoneNameLength; i++) {
-                standardZoneName[i] = (char16_t)ltime.tm_zone[i];
+        // mktime etc. broken-out time accepts 1900 as a start year while epoch is 1970
+        // minus the previously added calendar day (see YMD_TO_TM)
+        if (leap_added)
+        {
+            AssertMsg(ymd->mday >= 0, "Day of month can't be a negative number");
+            if (ymd->mday == 0)
+            {
+                ymd->mday = 29;
+                ymd->mon = 1;
+            }
+            else
+            {
+                ymd->mday--;
             }
-        #elif defined(WCHAR_IS_WCHAR_T)
-            mbstowcs( (wchar_t*) standardZoneName, ltime.tm_zone,
-                      sizeof(standardZoneName) / sizeof(WCHAR));
-        #else
-        #error "WCHAR should be either wchar_t or char16_t"
-        #endif
-
-            standardZoneName[standardZoneNameLength] = (WCHAR)0;
-            lastTimeZoneUpdateTickCount = tickCount;
         }
     }
 
-    // DateTimeHelper ******
-    static inline void TM_TO_SYSTIME(struct tm *time, SYSTEMTIME *sysTime)
+    static void CopyTimeZoneName(WCHAR *wstr, size_t *length, const char *tm_zone)
     {
-        sysTime->wYear = time->tm_year + 1900;
-        sysTime->wMonth = time->tm_mon + 1;
-        sysTime->wDayOfWeek = time->tm_wday;
-        sysTime->wDay = time->tm_mday;
-        sysTime->wHour = time->tm_hour;
-        sysTime->wMinute = time->tm_min;
-
-        // C99 tm_sec value is between 0 and 60. (1 leap second.)
-        // Discard leap second.
-        // In any case, this is a representation of a wallclock time.
-        // It can jump forwards and backwards.
-
-        sysTime->wSecond = time->tm_sec % 60;
+        *length = strlen(tm_zone);
+
+        for(int i = 0; i < *length; i++) {
+            wstr[i] = (WCHAR)tm_zone[i];
+        }
+
+        wstr[*length] = (WCHAR)0;
     }
 
-    #define MIN_ZERO(value) value < 0 ? 0 : value
+    const WCHAR *Utility::GetStandardName(size_t *nameLength, const DateTime::YMD *ymd) {
+        AssertMsg(ymd != NULL, "xplat needs DateTime::YMD is defined for this call");
+        struct tm time_tm;
+        bool leap_added;
+        YMD_TO_TM(ymd, &time_tm, &leap_added);
+        mktime(&time_tm); // get zone name for the given date
+        CopyTimeZoneName(data.standardName, &data.standardNameLength, time_tm.tm_zone);
+        *nameLength = data.standardNameLength;
+        return data.standardName;
+    }
 
-    static inline void SYSTIME_TO_TM(SYSTEMTIME *sysTime, struct tm *time)
-    {
-        time->tm_year = MIN_ZERO(sysTime->wYear - 1900);
-        time->tm_mon = MIN_ZERO(sysTime->wMonth - 1);
-        time->tm_wday = sysTime->wDayOfWeek;
-        time->tm_mday = sysTime->wDay;
-        time->tm_hour = sysTime->wHour;
-        time->tm_min = sysTime->wMinute;
-        time->tm_sec = sysTime->wSecond;
+    const WCHAR *Utility::GetDaylightName(size_t *nameLength, const DateTime::YMD *ymd) {
+        // xplat only gets the actual zone name for the given date
+        return GetStandardName(nameLength, ymd);
     }
 
-    static void SysLocalToUtc(SYSTEMTIME *local, SYSTEMTIME *utc)
+    static void YMDLocalToUtc(YMD *local, YMD *utc)
     {
         struct tm local_tm;
-        SYSTIME_TO_TM(local, (&local_tm));
+        bool leap_added;
+        YMD_TO_TM(local, (&local_tm), &leap_added);
 
         // tm doesn't have milliseconds
+        int milliseconds = local->time % 1000;
 
         tzset();
         time_t utime = timegm(&local_tm);
-        mktime(&local_tm); // we just want the gmt_off, don't care the result
-        utime -= local_tm.tm_gmtoff; // reverse UTC
+
+        // mktime min year is 1900
+        if (local_tm.tm_year < 1900)
+        {
+            local_tm.tm_year = 1900;
+        }
+        mktime(&local_tm);
+        utime -= local_tm.tm_gmtoff;
 
         struct tm utc_tm;
         if (gmtime_r(&utime, &utc_tm) == 0)
@@ -105,31 +156,40 @@ namespace DateTime
             AssertMsg(false, "gmtime() failed");
         }
 
-        TM_TO_SYSTIME((&utc_tm), utc);
+        TM_TO_YMD((&utc_tm), utc, leap_added, local->year);
         // put milliseconds back
-        utc->wMilliseconds = local->wMilliseconds;
+        utc->time += milliseconds;
     }
 
-    static void SysUtcToLocal(SYSTEMTIME *utc, SYSTEMTIME *local,
+    static void YMDUtcToLocal(YMD *utc, YMD *local,
                           int &bias, int &offset, bool &isDaylightSavings)
     {
         struct tm utc_tm;
-        SYSTIME_TO_TM(utc, (&utc_tm));
+        bool leap_added;
+        YMD_TO_TM(utc, &utc_tm, &leap_added);
 
         // tm doesn't have milliseconds
+        int milliseconds = utc->time % 1000;
 
         tzset();
         time_t ltime = timegm(&utc_tm);
         struct tm local_tm;
         localtime_r(&ltime, &local_tm);
-        offset = local_tm.tm_gmtoff / 60;
-
-        TM_TO_SYSTIME((&local_tm), local);
 
+        TM_TO_YMD((&local_tm), local, leap_added, utc->year);
         // put milliseconds back
-        local->wMilliseconds = utc->wMilliseconds;
-
-        isDaylightSavings = local_tm.tm_isdst;
+        local->time += milliseconds;
+
+        // ugly hack but;
+        // right in between dst pass
+        // we need mktime trick to get the correct dst
+        utc_tm.tm_isdst = 1;
+        ltime = mktime(&utc_tm);
+        ltime += utc_tm.tm_gmtoff;
+        localtime_r(&ltime, &utc_tm);
+
+        isDaylightSavings = utc_tm.tm_isdst;
+        offset = utc_tm.tm_gmtoff / 60;
         bias = offset;
     }
 
@@ -137,31 +197,23 @@ namespace DateTime
     double DaylightTimeHelper::UtcToLocal(double utcTime, int &bias,
                                           int &offset, bool &isDaylightSavings)
     {
-        SYSTEMTIME utcSystem, localSystem;
-        YMD ymd;
+        YMD ymdUTC, local;
 
-        // todo: can we make all these transformation more efficient?
-        Js::DateUtilities::GetYmdFromTv(utcTime, &ymd);
-        ymd.ToSystemTime(&utcSystem);
-
-        SysUtcToLocal(&utcSystem, &localSystem,
+        Js::DateUtilities::GetYmdFromTv(utcTime, &ymdUTC);
+        YMDUtcToLocal(&ymdUTC, &local,
                       bias, offset, isDaylightSavings);
 
-        return Js::DateUtilities::TimeFromSt(&localSystem);
+        return Js::DateUtilities::TvFromDate(local.year, local.mon, local.mday, local.time);
     }
 
     double DaylightTimeHelper::LocalToUtc(double localTime)
     {
-        SYSTEMTIME utcSystem, localSystem;
-        YMD ymd;
-
-        // todo: can we make all these transformation more efficient?
-        Js::DateUtilities::GetYmdFromTv(localTime, &ymd);
-        ymd.ToSystemTime(&utcSystem);
+        YMD ymdLocal, utc;
 
-        SysLocalToUtc(&utcSystem, &localSystem);
+        Js::DateUtilities::GetYmdFromTv(localTime, &ymdLocal);
+        YMDLocalToUtc(&ymdLocal, &utc);
 
-        return Js::DateUtilities::TimeFromSt(&localSystem);
+        return Js::DateUtilities::TvFromDate(utc.year, utc.mon, utc.mday, utc.time);
     }
 } // namespace DateTime
 } // namespace PlatformAgnostic

+ 2 - 2
lib/Runtime/PlatformAgnostic/Platform/Windows/DateTime.cpp

@@ -58,14 +58,14 @@ namespace DateTime
         }
     }
 
-    const WCHAR *Utility::GetStandardName(size_t *nameLength)
+    const WCHAR *Utility::GetStandardName(size_t *nameLength, const DateTime::YMD *_ /*caution! can be NULL. not used for Windows*/)
     {
         data.UpdateTimeZoneInfo();
         *nameLength = wcslen(data.timeZoneInfo.StandardName);
         return data.timeZoneInfo.StandardName;
     }
 
-    const WCHAR *Utility::GetDaylightName(size_t *nameLength)
+    const WCHAR *Utility::GetDaylightName(size_t *nameLength, const DateTime::YMD *_/*caution! can be NULL. not used for Windows*/)
     {
         data.UpdateTimeZoneInfo();
         *nameLength = wcslen(data.timeZoneInfo.DaylightName);

+ 6 - 1
test/Date/DateParse2.js

@@ -50,6 +50,11 @@ testParseDate("Tue Feb 02 2012 01:02:03 GMT+0430 (prisec@)");
 testParseDate("Tue Feb 2 01:02:03 PST 2013 B.C.");
 testParseDate("Thu Feb 2 01:02:03 PST 2012");
 
+function CUT_NAME(str) {
+    return str.replace("(PST)", "(Pacific Standard Time)")
+              .replace("(PDT)", "(Pacific Daylight Time)");
+}
+
 function testDate(date) {
     testParseDate(date.toString());
 }
@@ -84,7 +89,7 @@ function testParseDateCore(d) {
 
 function myPrint(str) {
     if (WScript.Echo !== undefined) {
-        WScript.Echo(str);
+        WScript.Echo(CUT_NAME(str));
     }
     else {
         throw "no print!";

+ 8 - 3
test/Date/formatting.js

@@ -3,15 +3,20 @@
 // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
 //-------------------------------------------------------------------------------------------------------
 
+function CUT_NAME(str) {
+    return str.replace("(PST)", "(Pacific Standard Time)")
+              .replace("(PDT)", "(Pacific Daylight Time)");
+}
+
 for (var i = 0; i < 4 * 60; i++) {
     var d = new Date(2012, 2, 11, 0, i, 0);
-    WScript.Echo(d.toString());
+    WScript.Echo(CUT_NAME(d.toString()));
 }
 for (var i = 0; i < 4 * 60; i++) {
     var d = new Date(2012, 10, 4, 0, i, 0);
-    WScript.Echo(d.toString());
+    WScript.Echo(CUT_NAME(d.toString()));
 }
 
 // BLUE: 538457
 var bug538457 = new Date(1383672529000000);
-WScript.Echo(bug538457.toString());
+WScript.Echo(CUT_NAME(bug538457.toString()));

+ 2 - 2
test/Date/rlexe.xml

@@ -65,7 +65,7 @@
       <baseline>Conversions.win8.baseline</baseline>
       <compile-flags>-Intl-</compile-flags>
       <!-- test is timezone-sensitive; remove exclude_jenkins after fix -->
-      <tags>exclude_win7,exclude_winBlue,exclude_snap,exclude_jenkins</tags>
+      <tags>exclude_win7,exclude_winBlue,exclude_snap,exclude_jenkins,exclude_xplat</tags>
     </default>
   </test>
   <test>
@@ -74,7 +74,7 @@
       <baseline>Conversions.baseline</baseline>
       <compile-flags>-Intl-</compile-flags>
       <!-- test is timezone-sensitive; remove exclude_jenkins after fix -->
-      <tags>exclude_win7,exclude_win8,exclude_snap,exclude_jenkins</tags>
+      <tags>exclude_win7,exclude_win8,exclude_snap,exclude_jenkins,exclude_xplat</tags>
     </default>
   </test>
   <test>

+ 2 - 0
test/runtests.py

@@ -91,6 +91,7 @@ if not os.path.isfile(binary):
 # global tags/not_tags
 tags = set(args.tag or [])
 not_tags = set(args.not_tag or []).union(['fail', 'exclude_' + arch])
+
 if arch_alias:
     not_tags.add('exclude_' + arch_alias)
 if flavor_alias:
@@ -104,6 +105,7 @@ not_tags.add('exclude_nightly' if args.nightly else 'nightly')
 
 # xplat: temp hard coded to exclude unsupported tests
 if sys.platform != 'win32':
+    not_tags.add('exclude_xplat')
     not_tags.add('exclude_serialized')
     not_tags.add('require_backend')
     not_tags.add('require_debugger')