Skip to content

Commit 445901f

Browse files
committed
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.
1 parent 89ba407 commit 445901f

10 files changed

Lines changed: 174 additions & 110 deletions

File tree

lib/Common/PlatformAgnostic/DateTime.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,12 @@ namespace DateTime
2929
{
3030
UtilityPlatformData data;
3131
public:
32-
const WCHAR *GetStandardName(size_t *nameLength);
33-
const WCHAR *GetDaylightName(size_t *nameLength);
32+
const WCHAR *GetStandardName(size_t *nameLength,
33+
// xplat implementation needs an actual
34+
// date for the zone abbr.
35+
const DateTime::YMD *ymd = NULL);
36+
const WCHAR *GetDaylightName(size_t *nameLength,
37+
const DateTime::YMD *ymd = NULL);
3438
};
3539

3640
// Decomposed date (Year-Month-Date).

lib/Common/PlatformAgnostic/DateTimeInternal.h

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,12 @@ namespace DateTime
8484

8585
typedef void* DaylightTimeHelperPlatformData;
8686

87-
class UtilityPlatformData
87+
#define __CC_PA_TIMEZONE_ABVR_NAME_LENGTH 32
88+
struct UtilityPlatformData
8889
{
89-
public:
90-
WCHAR standardZoneName[32];
91-
size_t standardZoneNameLength;
92-
uint32 lastTimeZoneUpdateTickCount;
93-
94-
void UpdateTimeZoneInfo();
95-
96-
UtilityPlatformData(): lastTimeZoneUpdateTickCount(0) { }
90+
// cache always the last date's zone
91+
WCHAR standardName[__CC_PA_TIMEZONE_ABVR_NAME_LENGTH];
92+
size_t standardNameLength;
9793
};
9894

9995
class HiresTimerPlatformData

lib/Runtime/Base/ScriptContext.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -745,14 +745,14 @@ namespace Js
745745
DateTime::Utility dateTimeUtility;
746746

747747
public:
748-
inline const WCHAR *const GetStandardName(size_t *nameLength)
748+
inline const WCHAR *const GetStandardName(size_t *nameLength, DateTime::YMD *ymd = NULL)
749749
{
750-
return dateTimeUtility.GetStandardName(nameLength);
750+
return dateTimeUtility.GetStandardName(nameLength, ymd);
751751
}
752752

753-
inline const WCHAR *const GetDaylightName(size_t *nameLength)
753+
inline const WCHAR *const GetDaylightName(size_t *nameLength, DateTime::YMD *ymd = NULL)
754754
{
755-
return dateTimeUtility.GetDaylightName(nameLength);
755+
return dateTimeUtility.GetDaylightName(nameLength, ymd);
756756
}
757757

758758
private:

lib/Runtime/Library/DateImplementation.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,13 +476,13 @@ namespace Js {
476476
if (ptzd->fDst == false)
477477
{
478478
size_t nameLength;
479-
const WCHAR *const standardName = scriptContext->GetStandardName(&nameLength);
479+
const WCHAR *const standardName = scriptContext->GetStandardName(&nameLength, pymd);
480480
bs->AppendChars(standardName, static_cast<CharCount>(nameLength));
481481
}
482482
else
483483
{
484484
size_t nameLength;
485-
const WCHAR *const daylightName = scriptContext->GetDaylightName(&nameLength);
485+
const WCHAR *const daylightName = scriptContext->GetDaylightName(&nameLength, pymd);
486486
bs->AppendChars(daylightName, static_cast<CharCount>(nameLength));
487487
}
488488

lib/Runtime/PlatformAgnostic/Platform/Linux/DateTime.cpp

Lines changed: 137 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -13,155 +13,207 @@ namespace PlatformAgnostic
1313
namespace DateTime
1414
{
1515

16-
#define updatePeriod 1000
17-
18-
const WCHAR *Utility::GetStandardName(size_t *nameLength) {
19-
data.UpdateTimeZoneInfo();
20-
*nameLength = data.standardZoneNameLength;
21-
return data.standardZoneName;
16+
static inline bool IsLeap(const int year)
17+
{
18+
return (0 == (year & 3)) && (0 != (year % 100) || 0 == (year % 400));
2219
}
2320

24-
const WCHAR *Utility::GetDaylightName(size_t *nameLength) {
25-
// We have an abbreviated standard or daylight name
26-
// based on the date and time zone we are in.
27-
return GetStandardName(nameLength);
21+
// Windows DateTime implementation normalizes the year beyond <1900 >2100
22+
// mktime etc. broken-out time bases 1900
23+
static inline int NormalizeYMDYear(const int base_year)
24+
{
25+
if (base_year < -2100)
26+
{
27+
return 2100;
28+
}
29+
else if (base_year < -1900)
30+
{
31+
return base_year * -1;
32+
}
33+
34+
return base_year;
2835
}
2936

30-
void UtilityPlatformData::UpdateTimeZoneInfo()
37+
static inline int UpdateToYMDYear(const int base_year, const struct tm *time)
3138
{
32-
uint32 tickCount = GetTickCount();
33-
if (tickCount - lastTimeZoneUpdateTickCount > updatePeriod)
39+
int year = time->tm_year;
40+
41+
if (base_year < -2100)
3442
{
35-
time_t gtime = time(NULL);
36-
struct tm ltime;
43+
const int diff = year - 2100;
44+
year = abs(base_year) - diff;
45+
}
46+
47+
if (base_year < -1900)
48+
{
49+
year *= -1;
50+
}
51+
52+
return year;
53+
}
54+
55+
static void YMD_TO_TM(const YMD *ymd, struct tm *time, bool *leap_added)
56+
{
57+
time->tm_year = NormalizeYMDYear(ymd->year);
58+
time->tm_mon = ymd->mon;
59+
time->tm_wday = ymd->wday;
60+
time->tm_mday = ymd->mday;
61+
int t = ymd->time;
62+
t /= DateTimeTicks_PerSecond; // discard ms
63+
time->tm_sec = t % 60;
64+
t /= 60;
65+
time->tm_min = t % 60;
66+
t /= 60;
67+
time->tm_hour = t;
68+
69+
// mktime etc. broken-out time accepts 1900 as a start year while epoch is 1970
70+
// temporarily add a calendar day for leap pass
71+
bool leap_year = IsLeap(time->tm_year);
72+
*leap_added = false;
73+
if (ymd->yday == 60 && leap_year) {
74+
time->tm_mday++;
75+
*leap_added = true;
76+
}
77+
}
3778

38-
tzset();
39-
localtime_r(&gtime, &ltime);
79+
static void TM_TO_YMD(const struct tm *time, YMD *ymd, const bool leap_added, const int base_year)
80+
{
81+
ymd->year = UpdateToYMDYear(base_year, time);
82+
ymd->mon = time->tm_mon;
83+
ymd->mday = time->tm_mday;
4084

41-
standardZoneNameLength = strlen(ltime.tm_zone);
85+
ymd->time = (time->tm_hour * DateTimeTicks_PerHour)
86+
+ (time->tm_min * DateTimeTicks_PerMinute)
87+
+ (time->tm_sec * DateTimeTicks_PerSecond);
4288

43-
#if defined(WCHAR_IS_CHAR16_T)
44-
for(int i = 0; i < standardZoneNameLength; i++) {
45-
standardZoneName[i] = (char16_t)ltime.tm_zone[i];
89+
// mktime etc. broken-out time accepts 1900 as a start year while epoch is 1970
90+
// minus the previously added calendar day (see YMD_TO_TM)
91+
if (leap_added)
92+
{
93+
AssertMsg(ymd->mday >= 0, "Day of month can't be a negative number");
94+
if (ymd->mday == 0)
95+
{
96+
ymd->mday = 29;
97+
ymd->mon = 1;
98+
}
99+
else
100+
{
101+
ymd->mday--;
46102
}
47-
#elif defined(WCHAR_IS_WCHAR_T)
48-
mbstowcs( (wchar_t*) standardZoneName, ltime.tm_zone,
49-
sizeof(standardZoneName) / sizeof(WCHAR));
50-
#else
51-
#error "WCHAR should be either wchar_t or char16_t"
52-
#endif
53-
54-
standardZoneName[standardZoneNameLength] = (WCHAR)0;
55-
lastTimeZoneUpdateTickCount = tickCount;
56103
}
57104
}
58105

59-
// DateTimeHelper ******
60-
static inline void TM_TO_SYSTIME(struct tm *time, SYSTEMTIME *sysTime)
106+
static void CopyTimeZoneName(WCHAR *wstr, size_t *length, const char *tm_zone)
61107
{
62-
sysTime->wYear = time->tm_year + 1900;
63-
sysTime->wMonth = time->tm_mon + 1;
64-
sysTime->wDayOfWeek = time->tm_wday;
65-
sysTime->wDay = time->tm_mday;
66-
sysTime->wHour = time->tm_hour;
67-
sysTime->wMinute = time->tm_min;
68-
69-
// C99 tm_sec value is between 0 and 60. (1 leap second.)
70-
// Discard leap second.
71-
// In any case, this is a representation of a wallclock time.
72-
// It can jump forwards and backwards.
73-
74-
sysTime->wSecond = time->tm_sec % 60;
108+
*length = strlen(tm_zone);
109+
110+
for(int i = 0; i < *length; i++) {
111+
wstr[i] = (WCHAR)tm_zone[i];
112+
}
113+
114+
wstr[*length] = (WCHAR)0;
75115
}
76116

77-
#define MIN_ZERO(value) value < 0 ? 0 : value
117+
const WCHAR *Utility::GetStandardName(size_t *nameLength, const DateTime::YMD *ymd) {
118+
AssertMsg(ymd != NULL, "xplat needs DateTime::YMD is defined for this call");
119+
struct tm time_tm;
120+
bool leap_added;
121+
YMD_TO_TM(ymd, &time_tm, &leap_added);
122+
mktime(&time_tm); // get zone name for the given date
123+
CopyTimeZoneName(data.standardName, &data.standardNameLength, time_tm.tm_zone);
124+
*nameLength = data.standardNameLength;
125+
return data.standardName;
126+
}
78127

79-
static inline void SYSTIME_TO_TM(SYSTEMTIME *sysTime, struct tm *time)
80-
{
81-
time->tm_year = MIN_ZERO(sysTime->wYear - 1900);
82-
time->tm_mon = MIN_ZERO(sysTime->wMonth - 1);
83-
time->tm_wday = sysTime->wDayOfWeek;
84-
time->tm_mday = sysTime->wDay;
85-
time->tm_hour = sysTime->wHour;
86-
time->tm_min = sysTime->wMinute;
87-
time->tm_sec = sysTime->wSecond;
128+
const WCHAR *Utility::GetDaylightName(size_t *nameLength, const DateTime::YMD *ymd) {
129+
// xplat only gets the actual zone name for the given date
130+
return GetStandardName(nameLength, ymd);
88131
}
89132

90-
static void SysLocalToUtc(SYSTEMTIME *local, SYSTEMTIME *utc)
133+
static void YMDLocalToUtc(YMD *local, YMD *utc)
91134
{
92135
struct tm local_tm;
93-
SYSTIME_TO_TM(local, (&local_tm));
136+
bool leap_added;
137+
YMD_TO_TM(local, (&local_tm), &leap_added);
94138

95139
// tm doesn't have milliseconds
140+
int milliseconds = local->time % 1000;
96141

97142
tzset();
98143
time_t utime = timegm(&local_tm);
99-
mktime(&local_tm); // we just want the gmt_off, don't care the result
100-
utime -= local_tm.tm_gmtoff; // reverse UTC
144+
145+
// mktime min year is 1900
146+
if (local_tm.tm_year < 1900)
147+
{
148+
local_tm.tm_year = 1900;
149+
}
150+
mktime(&local_tm);
151+
utime -= local_tm.tm_gmtoff;
101152

102153
struct tm utc_tm;
103154
if (gmtime_r(&utime, &utc_tm) == 0)
104155
{
105156
AssertMsg(false, "gmtime() failed");
106157
}
107158

108-
TM_TO_SYSTIME((&utc_tm), utc);
159+
TM_TO_YMD((&utc_tm), utc, leap_added, local->year);
109160
// put milliseconds back
110-
utc->wMilliseconds = local->wMilliseconds;
161+
utc->time += milliseconds;
111162
}
112163

113-
static void SysUtcToLocal(SYSTEMTIME *utc, SYSTEMTIME *local,
164+
static void YMDUtcToLocal(YMD *utc, YMD *local,
114165
int &bias, int &offset, bool &isDaylightSavings)
115166
{
116167
struct tm utc_tm;
117-
SYSTIME_TO_TM(utc, (&utc_tm));
168+
bool leap_added;
169+
YMD_TO_TM(utc, &utc_tm, &leap_added);
118170

119171
// tm doesn't have milliseconds
172+
int milliseconds = utc->time % 1000;
120173

121174
tzset();
122175
time_t ltime = timegm(&utc_tm);
123176
struct tm local_tm;
124177
localtime_r(&ltime, &local_tm);
125-
offset = local_tm.tm_gmtoff / 60;
126-
127-
TM_TO_SYSTIME((&local_tm), local);
128178

179+
TM_TO_YMD((&local_tm), local, leap_added, utc->year);
129180
// put milliseconds back
130-
local->wMilliseconds = utc->wMilliseconds;
131-
132-
isDaylightSavings = local_tm.tm_isdst;
181+
local->time += milliseconds;
182+
183+
// ugly hack but;
184+
// right in between dst pass
185+
// we need mktime trick to get the correct dst
186+
utc_tm.tm_isdst = 1;
187+
ltime = mktime(&utc_tm);
188+
ltime += utc_tm.tm_gmtoff;
189+
localtime_r(&ltime, &utc_tm);
190+
191+
isDaylightSavings = utc_tm.tm_isdst;
192+
offset = utc_tm.tm_gmtoff / 60;
133193
bias = offset;
134194
}
135195

136196
// DaylightTimeHelper ******
137197
double DaylightTimeHelper::UtcToLocal(double utcTime, int &bias,
138198
int &offset, bool &isDaylightSavings)
139199
{
140-
SYSTEMTIME utcSystem, localSystem;
141-
YMD ymd;
200+
YMD ymdUTC, local;
142201

143-
// todo: can we make all these transformation more efficient?
144-
Js::DateUtilities::GetYmdFromTv(utcTime, &ymd);
145-
ymd.ToSystemTime(&utcSystem);
146-
147-
SysUtcToLocal(&utcSystem, &localSystem,
202+
Js::DateUtilities::GetYmdFromTv(utcTime, &ymdUTC);
203+
YMDUtcToLocal(&ymdUTC, &local,
148204
bias, offset, isDaylightSavings);
149205

150-
return Js::DateUtilities::TimeFromSt(&localSystem);
206+
return Js::DateUtilities::TvFromDate(local.year, local.mon, local.mday, local.time);
151207
}
152208

153209
double DaylightTimeHelper::LocalToUtc(double localTime)
154210
{
155-
SYSTEMTIME utcSystem, localSystem;
156-
YMD ymd;
157-
158-
// todo: can we make all these transformation more efficient?
159-
Js::DateUtilities::GetYmdFromTv(localTime, &ymd);
160-
ymd.ToSystemTime(&utcSystem);
211+
YMD ymdLocal, utc;
161212

162-
SysLocalToUtc(&utcSystem, &localSystem);
213+
Js::DateUtilities::GetYmdFromTv(localTime, &ymdLocal);
214+
YMDLocalToUtc(&ymdLocal, &utc);
163215

164-
return Js::DateUtilities::TimeFromSt(&localSystem);
216+
return Js::DateUtilities::TvFromDate(utc.year, utc.mon, utc.mday, utc.time);
165217
}
166218
} // namespace DateTime
167219
} // namespace PlatformAgnostic

lib/Runtime/PlatformAgnostic/Platform/Windows/DateTime.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@ namespace DateTime
5858
}
5959
}
6060

61-
const WCHAR *Utility::GetStandardName(size_t *nameLength)
61+
const WCHAR *Utility::GetStandardName(size_t *nameLength, const DateTime::YMD *_ /*caution! can be NULL. not used for Windows*/)
6262
{
6363
data.UpdateTimeZoneInfo();
6464
*nameLength = wcslen(data.timeZoneInfo.StandardName);
6565
return data.timeZoneInfo.StandardName;
6666
}
6767

68-
const WCHAR *Utility::GetDaylightName(size_t *nameLength)
68+
const WCHAR *Utility::GetDaylightName(size_t *nameLength, const DateTime::YMD *_/*caution! can be NULL. not used for Windows*/)
6969
{
7070
data.UpdateTimeZoneInfo();
7171
*nameLength = wcslen(data.timeZoneInfo.DaylightName);

0 commit comments

Comments
 (0)