Windows: use native Win32 API for dates

Allows conversion of negative "seconds" to reach dates before
1970, and fixes year-varying DST tracking for versions of
Windows that know about those details.

As far as I can tell, we have to compute ourselves whether a
date is in daylight-saving time based on specifications of
when daylight and standard times start. That part seems tricky
and could use extra review.
This commit is contained in:
Matthew Flatt 2014-07-25 08:24:53 -06:00
parent 816d09bb24
commit 135ccf094e
2 changed files with 174 additions and 83 deletions

View File

@ -579,9 +579,7 @@
# endif # endif
# define TIME_SYNTAX # define TIME_SYNTAX
# define USE_FTIME # define USE_WIN32_TIME
# define USE_TIMEZONE_VAR_W_DLS
# define USE_TZNAME_VAR
# define WINDOWS_GET_PROCESS_TIMES # define WINDOWS_GET_PROCESS_TIMES
# define GETENV_FUNCTION # define GETENV_FUNCTION
# define DIR_FUNCTION # define DIR_FUNCTION
@ -991,6 +989,8 @@
/* USE_MACTIME uses the Mac toolbox to implement time functions. */ /* USE_MACTIME uses the Mac toolbox to implement time functions. */
/* USE_WIN32_TIME uses the Win32 API to implement time functions. */
/* CLOCK_IS_USER_TIME uses the system time for user milliseconds. */ /* CLOCK_IS_USER_TIME uses the system time for user milliseconds. */
/* USER_TIME_IS_CLOCK uses the user time for system milliseconds. */ /* USER_TIME_IS_CLOCK uses the user time for system milliseconds. */

View File

@ -36,9 +36,8 @@
/* The implementations of the time primitives, such as /* The implementations of the time primitives, such as
`current-seconds', vary a lot from platform to platform. */ `current-seconds', vary a lot from platform to platform. */
#ifdef TIME_SYNTAX #ifdef TIME_SYNTAX
# ifdef USE_MACTIME # ifdef USE_WIN32_TIME
# include <OSUtils.h> # include <Windows.h>
# include <Timer.h>
# else # else
# ifndef USE_PALMTIME # ifndef USE_PALMTIME
# if defined(OSKIT) && !defined(OSKIT_TEST) # if defined(OSKIT) && !defined(OSKIT_TEST)
@ -217,6 +216,15 @@ typedef void (*DW_PrePost_Proc)(void *);
static void register_traversers(void); static void register_traversers(void);
#endif #endif
#ifdef USE_WIN32_TIME
typedef BOOL (WINAPI*GetTimeZoneInformationForYearProc_t)(USHORT wYear, void* pdtzi, LPTIME_ZONE_INFORMATION ptzi);
static GetTimeZoneInformationForYearProc_t GetTimeZoneInformationForYearProc;
typedef BOOL (WINAPI*SystemTimeToTzSpecificLocalTimeExProc_t)(void *lpTimeZoneInformation,
const SYSTEMTIME *lpUniversalTime,
LPSYSTEMTIME lpLocalTime);
static SystemTimeToTzSpecificLocalTimeExProc_t SystemTimeToTzSpecificLocalTimeExProc;
#endif
/* See call_cc: */ /* See call_cc: */
typedef struct Scheme_Dynamic_Wind_List { typedef struct Scheme_Dynamic_Wind_List {
@ -665,6 +673,20 @@ scheme_init_fun (Scheme_Env *env)
original_default_prompt = MALLOC_ONE_TAGGED(Scheme_Prompt); original_default_prompt = MALLOC_ONE_TAGGED(Scheme_Prompt);
original_default_prompt->so.type = scheme_prompt_type; original_default_prompt->so.type = scheme_prompt_type;
original_default_prompt->tag = scheme_default_prompt_tag; original_default_prompt->tag = scheme_default_prompt_tag;
#ifdef USE_WIN32_TIME
{
HMODULE hm;
hm = LoadLibrary("kernel32.dll");
GetTimeZoneInformationForYearProc
= (GetTimeZoneInformationForYearProc_t)GetProcAddress(hm, "GetTimeZoneInformationForYear");
SystemTimeToTzSpecificLocalTimeExProc
= (SystemTimeToTzSpecificLocalTimeExProc_t)GetProcAddress(hm, "SystemTimeToTzSpecificLocalTimeEx");
FreeLibrary(hm);
}
#endif
} }
void void
@ -9222,6 +9244,11 @@ static Scheme_Object *jump_to_alt_continuation()
#define CLOCKS_PER_SEC 1000000 #define CLOCKS_PER_SEC 1000000
#endif #endif
#ifdef USE_WIN32_TIME
/* Number of milliseconds from 1601 to 1970: */
# define MSEC_OFFSET 11644473600000
#endif
intptr_t scheme_get_milliseconds(void) intptr_t scheme_get_milliseconds(void)
XFORM_SKIP_PROC XFORM_SKIP_PROC
/* this function can be called from any OS thread */ /* this function can be called from any OS thread */
@ -9234,9 +9261,13 @@ intptr_t scheme_get_milliseconds(void)
MSC_IZE(ftime)(&now); MSC_IZE(ftime)(&now);
return (intptr_t)(now.time * 1000 + now.millitm); return (intptr_t)(now.time * 1000 + now.millitm);
# else # else
# ifdef PALMOS_STUFF # ifdef USE_WIN32_TIME
/* FIXME */ FILETIME ft;
return 0; mzlonglong v;
GetSystemTimeAsFileTime(&ft);
v = ((mzlonglong)ft.dwHighDateTime << 32) | ft.dwLowDateTime;
v = (v / 10000) - MSEC_OFFSET;
return (intptr_t)v;
# else # else
struct timeval now; struct timeval now;
gettimeofday(&now, NULL); gettimeofday(&now, NULL);
@ -9265,9 +9296,13 @@ double scheme_get_inexact_milliseconds(void)
MSC_IZE(ftime)(&now); MSC_IZE(ftime)(&now);
return (double)now.time * 1000.0 + (double)now.millitm; return (double)now.time * 1000.0 + (double)now.millitm;
# else # else
# ifdef PALMOS_STUFF # ifdef USE_WIN32_TIME
/* FIXME */ FILETIME ft;
return 0; mzlonglong v;
GetSystemTimeAsFileTime(&ft);
v = ((mzlonglong)ft.dwHighDateTime << 32) | ft.dwLowDateTime;
v -= ((mzlonglong)MSEC_OFFSET) * 10000;
return (double)(v / 10000) + (((double)(v % 10000)) / 10000.0);
# else # else
struct timeval now; struct timeval now;
gettimeofday(&now, NULL); gettimeofday(&now, NULL);
@ -9312,10 +9347,12 @@ intptr_t scheme_get_process_milliseconds(void)
v = ((((mzlonglong)kr.dwHighDateTime << 32) + kr.dwLowDateTime) v = ((((mzlonglong)kr.dwHighDateTime << 32) + kr.dwLowDateTime)
+ (((mzlonglong)us.dwHighDateTime << 32) + us.dwLowDateTime)); + (((mzlonglong)us.dwHighDateTime << 32) + us.dwLowDateTime));
return (uintptr_t)(v / 10000); return (uintptr_t)(v / 10000);
} } else
return 0; /* anything better to do? */
} }
# endif # else
return clock() * 1000 / CLOCKS_PER_SEC; return clock() * 1000 / CLOCKS_PER_SEC;
# endif
# endif # endif
# endif # endif
@ -9338,11 +9375,8 @@ intptr_t scheme_get_thread_milliseconds(Scheme_Object *thrd)
intptr_t scheme_get_seconds(void) intptr_t scheme_get_seconds(void)
{ {
#ifdef USE_MACTIME #ifdef USE_WIN32_TIME
/* This is wrong, since it's not since January 1, 1970 */ return scheme_get_milliseconds() / 1000;
unsigned long secs;
GetDateTime(&secs);
return secs;
#else #else
# ifdef USE_PALMTIME # ifdef USE_PALMTIME
return TimGetSeconds(); return TimGetSeconds();
@ -9366,10 +9400,66 @@ intptr_t scheme_get_seconds(void)
#endif #endif
} }
#if defined(USE_MACTIME) || defined(USE_PALMTIME) #if defined(USE_WIN32_TIME)
static int month_offsets[12] = { 0, 31, 59, 90, /* Assuming not a leap year (and adjusted elsewhere): */
120, 151, 181, 212, static int month_offsets[13] = { 0, 31, 59, 90,
243, 273, 304, 334 }; 120, 151, 181, 212,
243, 273, 304, 334,
365};
# define dtxCOMP(f) if (a->f < b->f) return 1; if (a->f > b->f) return 0;
static int is_start_day_before(SYSTEMTIME *a, SYSTEMTIME *b)
{
dtxCOMP(wYear);
/* When comparing DST boundaries, we expect to get here,
because wYear will be 0 to mean "every year". */
dtxCOMP(wMonth);
/* When comparing DST boundaries, it's unlikely that we'll get here,
because that would mean that StdT and DST start in the same month. */
dtxCOMP(wDay); /* for DST boundaires, this is a week number */
dtxCOMP(wDayOfWeek);
dtxCOMP(wHour);
dtxCOMP(wMinute);
return 0;
}
static int is_day_before(SYSTEMTIME *a, SYSTEMTIME *b)
/* a is a date, and b is a DST boundary spec */
{
int dos, doc;
if (b->wYear) {
dtxCOMP(wYear);
}
dtxCOMP(wMonth);
/* "Date" of a Sunday this month, 0 to 6: */
dos = ((a->wDay - a->wDayOfWeek) + 7) % 7;
/* Date of first b->wDayOfWeek this month, 1 to 7: */
doc = (dos + b->wDayOfWeek) % 7;
if (doc == 0) doc = 7;
/* Date of change this year: */
doc = doc + ((b->wDay - 1) * 7);
if (doc > (month_offsets[b->wMonth] - month_offsets[b->wMonth-1]))
doc -= 7;
/* Above assumes that a time change doesn't occur on a leap day! */
if (a->wDay < doc)
return 1;
dtxCOMP(wHour);
dtxCOMP(wMinute);
return 0;
}
# undef dtxCOMP
#endif #endif
#if (defined(OS_X) || defined(XONX)) && defined(__x86_64__) #if (defined(OS_X) || defined(XONX)) && defined(__x86_64__)
@ -9430,9 +9520,9 @@ static Scheme_Object *seconds_to_date(int argc, Scheme_Object **argv)
int get_gmt; int get_gmt;
int hour, min, sec, month, day, year, wday, yday, dst; int hour, min, sec, month, day, year, wday, yday, dst;
long tzoffset; long tzoffset;
#ifdef USE_MACTIME #ifdef USE_WIN32_TIME
# define CHECK_TIME_T unsigned long # define CHECK_TIME_T uintptr_t
DateTimeRec localTime; SYSTEMTIME localTime;
#else #else
# ifdef USE_PALMTIME # ifdef USE_PALMTIME
# define CHECK_TIME_T UInt32 # define CHECK_TIME_T UInt32
@ -9479,9 +9569,22 @@ static Scheme_Object *seconds_to_date(int argc, Scheme_Object **argv)
&& VALID_TIME_RANGE(lnow)) { && VALID_TIME_RANGE(lnow)) {
int success; int success;
#ifdef USE_MACTIME #ifdef USE_WIN32_TIME
SecondsToDate(lnow, &localTime); {
success = 1; mzlonglong nsC;
FILETIME ft;
nsC = (((mzlonglong)lnow) * 10000000) + ((mzlonglong)MSEC_OFFSET * 10000);
ft.dwLowDateTime = nsC & (mzlonglong)0xFFFFFFFF;
ft.dwHighDateTime = nsC >> 32;
success = FileTimeToSystemTime(&ft, &localTime);
if (success && !get_gmt) {
SYSTEMTIME t2 = localTime;
if (SystemTimeToTzSpecificLocalTimeExProc)
success = SystemTimeToTzSpecificLocalTimeExProc(NULL, &t2, &localTime);
else
success = SystemTimeToTzSpecificLocalTime(NULL, &t2, &localTime);
}
}
#else #else
# ifdef USE_PALMTIME # ifdef USE_PALMTIME
TimSecondsToDateTime(lnow, &localTime) ; TimSecondsToDateTime(lnow, &localTime) ;
@ -9495,65 +9598,53 @@ static Scheme_Object *seconds_to_date(int argc, Scheme_Object **argv)
#endif #endif
if (success) { if (success) {
#if defined(USE_MACTIME) || defined(USE_PALMTIME) #ifdef USE_WIN32_TIME
# ifdef USE_MACTIME
# define mzDOW(localTime) localTime.dayOfWeek - 1
# else
# define mzDOW(localTime) localTime.weekDay
# endif
hour = localTime.hour; hour = localTime.wHour;
min = localTime.minute; min = localTime.wMinute;
sec = localTime.second; sec = localTime.wSecond;
month = localTime.month; month = localTime.wMonth;
day = localTime.day; day = localTime.wDay;
year = localTime.year; year = localTime.wYear;
wday = mzDOW(localTime); wday = localTime.wDayOfWeek;
yday = month_offsets[localTime.wMonth-1] + day-1;
/* leap-year adjustment: */
if ((month > 2)
&& ((year % 4) == 0)
&& (((year % 100) != 0) || ((year % 400) == 0)))
yday++;
yday = month_offsets[localTime.month - 1] + localTime.day - 1; dst = 0;
/* If month > 2, is it a leap-year? */ if (get_gmt) {
if (localTime.month > 2) { tzoffset = 0;
# ifdef USE_MACTIME tzn = "UTC";
unsigned long ttime; } else {
DateTimeRec tester; TIME_ZONE_INFORMATION tz;
if (GetTimeZoneInformationForYearProc)
tester.year = localTime.year; GetTimeZoneInformationForYearProc(localTime.wYear, NULL, &tz);
tester.hour = tester.minute = 0; else
tester.second = 1; (void)GetTimeZoneInformation(&tz);
tester.month = 1; if (tz.StandardDate.wMonth) {
tester.day = 60; if (is_start_day_before(&tz.DaylightDate, &tz.StandardDate)) {
DateToSeconds(&tester, &ttime); /* northern hemisphere */
SecondsToDate(ttime, &tester); dst = (!is_day_before(&localTime, &tz.DaylightDate)
if (tester.month == 2) && is_day_before(&localTime, &tz.StandardDate));
/* It is a leap-year */ } else {
yday++; /* southern hemisphere */
# else dst = (is_day_before(&localTime, &tz.StandardDate)
/* PalmOS: */ || !is_day_before(&localTime, &tz.DaylightDate));
if (DaysInMonth(2, year) > 28) }
yday++; }
# endif if (dst) {
tzoffset = (tz.Bias + tz.DaylightBias) * -60;
tzn = NARROW_PATH(tz.DaylightName);
} else {
tzoffset = (tz.Bias + tz.StandardBias) * -60;
tzn = NARROW_PATH(tz.StandardName);
}
} }
# ifdef USE_MACTIME
{
MachineLocation loc;
ReadLocation(&loc);
dst = (loc.u.dlsDelta != 0);
tzoffset = loc.u.gmtDelta; /* 3-byte value in a long!! */
/* Copied from Inside mac: */
tzoffset = tzoffset & 0xFFFFFF;
if (tzoffset & (0x1 << 23))
tzoffset |= 0xFF000000;
}
# else
/* No timezone on PalmOS: */
tzoffset = 0;
# endif
#else #else
hour = localTime->tm_hour; hour = localTime->tm_hour;
min = localTime->tm_min; min = localTime->tm_min;