rktio: fix Windows file timestamp dst correction

The old correction was broken in at least a couple of ways; use
the more tested DST calculation in the implementation of
`seconds->date`.
This commit is contained in:
Matthew Flatt 2018-03-29 20:30:19 -06:00
parent e95c3fe6d5
commit d3aa7e90e7
3 changed files with 32 additions and 59 deletions

View File

@ -129,12 +129,12 @@ static time_t convert_date(const FILETIME *ft)
SYSTEMTIME st;
TIME_ZONE_INFORMATION tz;
/* FindFirstFile incorrectly shifts for daylight saving. It
/* GetFileAttributes incorrectly shifts for daylight saving. It
subtracts an hour to get to UTC when daylight saving is in effect
now, even when daylight saving was not in effect when the file
was saved. Counteract the difference. There's a race condition
here, because we might cross the daylight-saving boundary between
the time that FindFirstFile runs and GetTimeZoneInformation
the time that GetFileAttributes runs and GetTimeZoneInformation
runs. Cross your fingers... */
FileTimeToLocalFileTime(ft, &ft2);
FileTimeToSystemTime(&ft2, &st);
@ -143,47 +143,8 @@ static time_t convert_date(const FILETIME *ft)
if (GetTimeZoneInformation(&tz) == TIME_ZONE_ID_DAYLIGHT) {
/* Daylight saving is in effect now, so there may be a bad
shift. Check the file's date. */
int start_day_of_month, end_day_of_month, first_day_of_week, diff, end_shift;
/* Valid only when the months match: */
first_day_of_week = (st.wDayOfWeek - (st.wDay - 1 - (((st.wDay - 1) / 7) * 7)));
if (first_day_of_week < 0)
first_day_of_week += 7;
diff = (tz.DaylightDate.wDayOfWeek - first_day_of_week);
if (diff < 0)
diff += 7;
start_day_of_month = 1 + (((tz.DaylightDate.wDay - 1) * 7)
+ diff);
diff = (tz.StandardDate.wDayOfWeek - first_day_of_week);
if (diff < 0)
diff += 7;
end_day_of_month = 1 + (((tz.StandardDate.wDay - 1) * 7)
+ diff);
/* Count ambigious range (when the clock goes back) as
in standard time. We assume that subtracting the
ambiguous range does not go back into the previous day,
and that the shift is a multiple of an hour. */
end_shift = ((tz.StandardBias - tz.DaylightBias) / 60);
if ((st.wMonth < tz.DaylightDate.wMonth)
|| ((st.wMonth == tz.DaylightDate.wMonth)
&& ((st.wDay < start_day_of_month)
|| ((st.wDay == start_day_of_month)
&& (st.wHour < tz.DaylightDate.wHour))))) {
/* Daylight saving had not yet started. */
if (!rktio_system_time_is_dst(&st, NULL))
delta = ((tz.StandardBias - tz.DaylightBias) * 60);
} else if ((st.wMonth > tz.StandardDate.wMonth)
|| ((st.wMonth == tz.StandardDate.wMonth)
&& ((st.wDay > end_day_of_month)
|| ((st.wDay == end_day_of_month)
&& (st.wHour >= (tz.StandardDate.wHour
- end_shift)))))) {
/* Daylight saving was already over. */
delta = ((tz.StandardBias - tz.DaylightBias) * 60);
}
}
l = ((((LONGLONG)ft->dwHighDateTime << 32) | ft->dwLowDateTime)

View File

@ -360,3 +360,8 @@ char *rktio_strndup(char *s, intptr_t len);
void rktio_set_signal_handler(int sig_id, void (*proc)(int));
#endif
void rktio_forget_os_signal_handler(rktio_t *rktio);
#ifdef RKTIO_SYSTEM_WINDOWS
int rktio_system_time_is_dst(SYSTEMTIME *st, TIME_ZONE_INFORMATION *_tz);
#endif

View File

@ -258,6 +258,28 @@ static int is_day_before(SYSTEMTIME *a, SYSTEMTIME *b)
return 0;
}
# undef dtxCOMP
int rktio_system_time_is_dst(SYSTEMTIME *st, TIME_ZONE_INFORMATION *_tz)
{
TIME_ZONE_INFORMATION tz;
if (GetTimeZoneInformationForYearProc)
GetTimeZoneInformationForYearProc(st->wYear, NULL, &tz);
else
(void)GetTimeZoneInformation(&tz);
if (_tz) *_tz = tz;
if (tz.StandardDate.wMonth) {
if (is_start_day_before(&tz.DaylightDate, &tz.StandardDate)) {
/* northern hemisphere */
return (!is_day_before(st, &tz.DaylightDate)
&& is_day_before(st, &tz.StandardDate));
} else {
/* southern hemisphere */
return (is_day_before(st, &tz.StandardDate)
|| !is_day_before(st, &tz.DaylightDate));
}
}
return 0;
}
#endif
#if defined(OS_X) && defined(__x86_64__)
@ -379,22 +401,7 @@ rktio_date_t *rktio_seconds_to_date(rktio_t *rktio, rktio_timestamp_t seconds, i
tzn = MSC_IZE(strdup)("UTC");
} else {
TIME_ZONE_INFORMATION tz;
if (GetTimeZoneInformationForYearProc)
GetTimeZoneInformationForYearProc(localTime.wYear, NULL, &tz);
else
(void)GetTimeZoneInformation(&tz);
if (tz.StandardDate.wMonth) {
if (is_start_day_before(&tz.DaylightDate, &tz.StandardDate)) {
/* northern hemisphere */
dst = (!is_day_before(&localTime, &tz.DaylightDate)
&& is_day_before(&localTime, &tz.StandardDate));
} else {
/* southern hemisphere */
dst = (is_day_before(&localTime, &tz.StandardDate)
|| !is_day_before(&localTime, &tz.DaylightDate));
}
}
if (dst) {
if (rktio_system_time_is_dst(&localTime, &tz)) {
tzoffset = (tz.Bias + tz.DaylightBias) * -60;
tzn = NARROW_PATH_copy(tz.DaylightName);
} else {