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
# define TIME_SYNTAX
# define USE_FTIME
# define USE_TIMEZONE_VAR_W_DLS
# define USE_TZNAME_VAR
# define USE_WIN32_TIME
# define WINDOWS_GET_PROCESS_TIMES
# define GETENV_FUNCTION
# define DIR_FUNCTION
@ -991,6 +989,8 @@
/* 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. */
/* 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
`current-seconds', vary a lot from platform to platform. */
#ifdef TIME_SYNTAX
# ifdef USE_MACTIME
# include <OSUtils.h>
# include <Timer.h>
# ifdef USE_WIN32_TIME
# include <Windows.h>
# else
# ifndef USE_PALMTIME
# if defined(OSKIT) && !defined(OSKIT_TEST)
@ -217,6 +216,15 @@ typedef void (*DW_PrePost_Proc)(void *);
static void register_traversers(void);
#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: */
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->so.type = scheme_prompt_type;
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
@ -9222,6 +9244,11 @@ static Scheme_Object *jump_to_alt_continuation()
#define CLOCKS_PER_SEC 1000000
#endif
#ifdef USE_WIN32_TIME
/* Number of milliseconds from 1601 to 1970: */
# define MSEC_OFFSET 11644473600000
#endif
intptr_t scheme_get_milliseconds(void)
XFORM_SKIP_PROC
/* this function can be called from any OS thread */
@ -9234,9 +9261,13 @@ intptr_t scheme_get_milliseconds(void)
MSC_IZE(ftime)(&now);
return (intptr_t)(now.time * 1000 + now.millitm);
# else
# ifdef PALMOS_STUFF
/* FIXME */
return 0;
# ifdef USE_WIN32_TIME
FILETIME ft;
mzlonglong v;
GetSystemTimeAsFileTime(&ft);
v = ((mzlonglong)ft.dwHighDateTime << 32) | ft.dwLowDateTime;
v = (v / 10000) - MSEC_OFFSET;
return (intptr_t)v;
# else
struct timeval now;
gettimeofday(&now, NULL);
@ -9265,9 +9296,13 @@ double scheme_get_inexact_milliseconds(void)
MSC_IZE(ftime)(&now);
return (double)now.time * 1000.0 + (double)now.millitm;
# else
# ifdef PALMOS_STUFF
/* FIXME */
return 0;
# ifdef USE_WIN32_TIME
FILETIME ft;
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
struct timeval now;
gettimeofday(&now, NULL);
@ -9312,10 +9347,12 @@ intptr_t scheme_get_process_milliseconds(void)
v = ((((mzlonglong)kr.dwHighDateTime << 32) + kr.dwLowDateTime)
+ (((mzlonglong)us.dwHighDateTime << 32) + us.dwLowDateTime));
return (uintptr_t)(v / 10000);
}
} else
return 0; /* anything better to do? */
}
# endif
# else
return clock() * 1000 / CLOCKS_PER_SEC;
# endif
# endif
# endif
@ -9338,11 +9375,8 @@ intptr_t scheme_get_thread_milliseconds(Scheme_Object *thrd)
intptr_t scheme_get_seconds(void)
{
#ifdef USE_MACTIME
/* This is wrong, since it's not since January 1, 1970 */
unsigned long secs;
GetDateTime(&secs);
return secs;
#ifdef USE_WIN32_TIME
return scheme_get_milliseconds() / 1000;
#else
# ifdef USE_PALMTIME
return TimGetSeconds();
@ -9366,10 +9400,66 @@ intptr_t scheme_get_seconds(void)
#endif
}
#if defined(USE_MACTIME) || defined(USE_PALMTIME)
static int month_offsets[12] = { 0, 31, 59, 90,
120, 151, 181, 212,
243, 273, 304, 334 };
#if defined(USE_WIN32_TIME)
/* Assuming not a leap year (and adjusted elsewhere): */
static int month_offsets[13] = { 0, 31, 59, 90,
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
#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 hour, min, sec, month, day, year, wday, yday, dst;
long tzoffset;
#ifdef USE_MACTIME
# define CHECK_TIME_T unsigned long
DateTimeRec localTime;
#ifdef USE_WIN32_TIME
# define CHECK_TIME_T uintptr_t
SYSTEMTIME localTime;
#else
# ifdef USE_PALMTIME
# define CHECK_TIME_T UInt32
@ -9479,9 +9569,22 @@ static Scheme_Object *seconds_to_date(int argc, Scheme_Object **argv)
&& VALID_TIME_RANGE(lnow)) {
int success;
#ifdef USE_MACTIME
SecondsToDate(lnow, &localTime);
success = 1;
#ifdef USE_WIN32_TIME
{
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
# ifdef USE_PALMTIME
TimSecondsToDateTime(lnow, &localTime) ;
@ -9495,65 +9598,53 @@ static Scheme_Object *seconds_to_date(int argc, Scheme_Object **argv)
#endif
if (success) {
#if defined(USE_MACTIME) || defined(USE_PALMTIME)
# ifdef USE_MACTIME
# define mzDOW(localTime) localTime.dayOfWeek - 1
# else
# define mzDOW(localTime) localTime.weekDay
# endif
#ifdef USE_WIN32_TIME
hour = localTime.hour;
min = localTime.minute;
sec = localTime.second;
hour = localTime.wHour;
min = localTime.wMinute;
sec = localTime.wSecond;
month = localTime.month;
day = localTime.day;
year = localTime.year;
month = localTime.wMonth;
day = localTime.wDay;
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;
/* If month > 2, is it a leap-year? */
if (localTime.month > 2) {
# ifdef USE_MACTIME
unsigned long ttime;
DateTimeRec tester;
tester.year = localTime.year;
tester.hour = tester.minute = 0;
tester.second = 1;
tester.month = 1;
tester.day = 60;
DateToSeconds(&tester, &ttime);
SecondsToDate(ttime, &tester);
if (tester.month == 2)
/* It is a leap-year */
yday++;
# else
/* PalmOS: */
if (DaysInMonth(2, year) > 28)
yday++;
# endif
dst = 0;
if (get_gmt) {
tzoffset = 0;
tzn = "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) {
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
hour = localTime->tm_hour;
min = localTime->tm_min;