|
|
@@ -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(>ime, <ime);
|
|
|
+ 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(<ime, &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(<ime, &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
|