@@ -13,155 +13,207 @@ namespace PlatformAgnostic
1313namespace 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 (>ime, <ime);
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 (<ime, &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 (<ime, &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
0 commit comments