Skip to content

Commit

Permalink
Emulate localtime, gmtime and mktime to enable negative time_t
Browse files Browse the repository at this point in the history
  • Loading branch information
bufflig authored and psyeugenic committed Dec 8, 2011
1 parent 965db64 commit 46eb435
Show file tree
Hide file tree
Showing 3 changed files with 285 additions and 15 deletions.
9 changes: 6 additions & 3 deletions erts/emulator/beam/erl_time_sup.c
Expand Up @@ -491,7 +491,7 @@ get_time(int *hour, int *minute, int *second)

the_clock = time((time_t *)0);
#ifdef HAVE_LOCALTIME_R
localtime_r(&the_clock, (tm = &tmbuf));
tm = localtime_r(&the_clock, &tmbuf);
#else
tm = localtime(&the_clock);
#endif
Expand All @@ -513,7 +513,7 @@ get_date(int *year, int *month, int *day)

the_clock = time((time_t *)0);
#ifdef HAVE_LOCALTIME_R
localtime_r(&the_clock, (tm = &tmbuf));
tm = localtime_r(&the_clock, &tmbuf);
#else
tm = localtime(&the_clock);
#endif
Expand Down Expand Up @@ -762,10 +762,13 @@ local_to_univ(Sint *year, Sint *month, Sint *day,
}
}
#ifdef HAVE_GMTIME_R
gmtime_r(&the_clock, (tm = &tmbuf));
tm = gmtime_r(&the_clock, &tmbuf);
#else
tm = gmtime(&the_clock);
#endif
if (!tm) {
return 0;
}
*year = tm->tm_year + 1900;
*month = tm->tm_mon +1;
*day = tm->tm_mday;
Expand Down
5 changes: 5 additions & 0 deletions erts/emulator/sys/win32/erl_win_sys.h
Expand Up @@ -118,9 +118,14 @@ int erts_check_io_debug(void);
#define SYS_CLOCK_RESOLUTION 1

struct tm *sys_localtime_r(time_t *epochs, struct tm *ptm);
struct tm *sys_gmtime_r(time_t *epochs, struct tm *ptm);
time_t sys_mktime( struct tm *ptm);

#define localtime_r sys_localtime_r
#define HAVE_LOCALTIME_R 1
#define gmtime_r sys_gmtime_r
#define HAVE_GMTIME_R
#define mktime sys_mktime

typedef struct {
long tv_sec;
Expand Down
286 changes: 274 additions & 12 deletions erts/emulator/sys/win32/sys_time.c
Expand Up @@ -35,49 +35,222 @@
/******************* Routines for time measurement *********************/

#define EPOCH_JULIAN_DIFF LL_LITERAL(11644473600)
#define TICKS_PER_SECOND (10000000LL)
#define TICKS_PER_SECOND LL_LITERAL(10000000)
#define SECONDS_PER_DAY LL_LITERAL(86400)

#define ULI_TO_FILETIME(ft,ull) \
do { \
(ft).dwLowDateTime = (ull).LowPart; \
(ft).dwHighDateTime = (ull).HighPart; \
} while (0)

#define FILETIME_TO_ULI(ull,ft) \
do { \
(ull).LowPart = (ft).dwLowDateTime; \
(ull).HighPart = (ft).dwHighDateTime; \
} while (0)


#define EPOCH_TO_FILETIME(ft, epoch) \
do { \
ULARGE_INTEGER ull; \
ull.QuadPart = (((epoch) + EPOCH_JULIAN_DIFF) * TICKS_PER_SECOND); \
(ft).dwLowDateTime = ull.LowPart; \
(ft).dwHighDateTime = ull.HighPart; \
ULI_TO_FILETIME(ft,ull); \
} while(0)

#define FILETIME_TO_EPOCH(epoch, ft) \
do { \
ULARGE_INTEGER ull; \
ull.LowPart = (ft).dwLowDateTime; \
ull.HighPart = (ft).dwHighDateTime; \
FILETIME_TO_ULI(ull,ft); \
(epoch) = ((ull.QuadPart / TICKS_PER_SECOND) - EPOCH_JULIAN_DIFF); \
} while(0)

static SysHrTime wrap = 0;
static DWORD last_tick_count = 0;

/* Getting timezone information is a heavy operation, so we want to do this
only once */

static TIME_ZONE_INFORMATION static_tzi;
static int have_static_tzi = 0;

static int days_in_month[2][13] = {
{0,31,28,31,30,31,30,31,31,30,31,30,31},
{0,31,29,31,30,31,30,31,31,30,31,30,31}};

int
sys_init_time(void)
{
if(GetTimeZoneInformation(&static_tzi) &&
static_tzi.StandardDate.wMonth != 0 &&
static_tzi.DaylightDate.wMonth != 0) {
have_static_tzi = 1;
}
return 1;
}

struct tm * sys_localtime_r(time_t *epochs, struct tm *ptm)
/* Returns a switchtimes for DST as UTC filetimes given data from a
TIME_ZONE_INFORMATION, see sys_localtime_r for usage. */
static void
get_dst_switchtime(DWORD year,
SYSTEMTIME dstinfo, LONG bias,
FILETIME *utc_switchtime)
{
DWORD occu;
DWORD weekday,wday_1st;
DWORD day, days_in;
FILETIME tmp,tmp2;
ULARGE_INTEGER ull;
int leap_year = 0;
if (dstinfo.wYear != 0) {
/* A year specific transition, in which case the data in the structure
is already properly set for a specific year. Compare year
with parameter and see if they correspond, in that case generate a
filetime directly, otherwise set the filetime to 0 */
if (year != dstinfo.wYear) {
utc_switchtime->dwLowDateTime = utc_switchtime->dwHighDateTime = 0;
return;
}
} else {
occu = dstinfo.wDay;
weekday = dstinfo.wDayOfWeek;

dstinfo.wDayOfWeek = 0;
dstinfo.wDay = 1;
dstinfo.wYear = year;

SystemTimeToFileTime(&dstinfo,&tmp);
ull.LowPart = tmp.dwLowDateTime;
ull.HighPart = tmp.dwHighDateTime;

ull.QuadPart /= (TICKS_PER_SECOND*SECONDS_PER_DAY); /* Julian Day */
wday_1st = (DWORD) ((ull.QuadPart + LL_LITERAL(1)) % LL_LITERAL(7));
day = (weekday >= wday_1st) ?
weekday - wday_1st + 1 :
weekday - wday_1st + 8;
--occu;
if (((dstinfo.wYear % 4) == 0 && (dstinfo.wYear % 100) > 0) ||
((dstinfo.wYear % 400) == 0)) {
leap_year = 1;
}
days_in = days_in_month[leap_year][dstinfo.wMonth];
while (occu > 0 && (day + 7 <= days_in)) {
--occu;
day += 7;
}
dstinfo.wDay = day;
}
SystemTimeToFileTime(&dstinfo,&tmp);
/* correct for bias */
ull.LowPart = tmp.dwLowDateTime;
ull.HighPart = tmp.dwHighDateTime;
ull.QuadPart += (((LONGLONG) bias) * LL_LITERAL(60) * TICKS_PER_SECOND);
utc_switchtime->dwLowDateTime = ull.LowPart;
utc_switchtime->dwHighDateTime = ull.HighPart;
return;
}

/* This function gives approximately the correct year from a FILETIME
Around the actual new year, it may return the wrong value, but that's OK
as DST never switches around new year. */
static DWORD
approx_year(FILETIME ft)
{
ULARGE_INTEGER ull;
FILETIME_TO_ULI(ull,ft);
ull.QuadPart /= LL_LITERAL(1000);
ull.QuadPart /= SECONDS_PER_DAY;
ull.QuadPart /= LL_LITERAL(3652425);
ull.QuadPart += 1601;
return (DWORD) ull.QuadPart;
}

struct tm *
sys_localtime_r(time_t *epochs, struct tm *ptm)
{
FILETIME ft,lft;
SYSTEMTIME st;

if ((((*epochs) + EPOCH_JULIAN_DIFF) * TICKS_PER_SECOND) < 0LL) {
fprintf(stderr,"1\r\n"); fflush(stderr);
return NULL;
}

EPOCH_TO_FILETIME(ft,*epochs);
ptm->tm_isdst = 0;
if (have_static_tzi) {
FILETIME dst_start, dst_stop;
ULARGE_INTEGER ull;
DWORD year = approx_year(ft);
get_dst_switchtime(year,static_tzi.DaylightDate,
static_tzi.Bias+static_tzi.StandardBias,&dst_start);
get_dst_switchtime(year,static_tzi.StandardDate,
static_tzi.Bias+static_tzi.StandardBias+
static_tzi.DaylightBias,
&dst_stop);
FILETIME_TO_ULI(ull,ft);

if (CompareFileTime(&ft,&dst_start) >= 0 &&
CompareFileTime(&ft,&dst_stop) < 0) {
ull.QuadPart -=
((LONGLONG) static_tzi.Bias+static_tzi.StandardBias+
static_tzi.DaylightBias) *
LL_LITERAL(60) * TICKS_PER_SECOND;
ptm->tm_isdst = 1;
} else {
ull.QuadPart -=
((LONGLONG) static_tzi.Bias+static_tzi.StandardBias)
* LL_LITERAL(60) * TICKS_PER_SECOND;
}
ULI_TO_FILETIME(ft,ull);
} else {
if (!FileTimeToLocalFileTime(&ft,&lft)) {
return NULL;
}
ft = lft;
}

if (!FileTimeToSystemTime(&ft,&st)) {
return NULL;
}

ptm->tm_year = (int) st.wYear - 1900;
ptm->tm_mon = (int) st.wMonth - 1;
ptm->tm_mday = (int) st.wDay;
ptm->tm_hour = (int) st.wHour;
ptm->tm_min = (int) st.wMinute;
ptm->tm_sec = (int) st.wSecond;
ptm->tm_wday = (int) st.wDayOfWeek;
{
int yday = ptm->tm_mday - 1;
int m = ptm->tm_mon;
int leap_year = 0;
if (((st.wYear % 4) == 0 && (st.wYear % 100) > 0) ||
((st.wYear % 400) == 0)) {
leap_year = 1;
}
while (m > 0) {
yday +=days_in_month[leap_year][m];
--m;
}
ptm->tm_yday = yday;
}
return ptm;
}

struct tm *
sys_gmtime_r(time_t *epochs, struct tm *ptm)
{
FILETIME ft;
SYSTEMTIME st;

if (!FileTimeToLocalFileTime(&ft,&lft)) {
if ((((*epochs) + EPOCH_JULIAN_DIFF) * TICKS_PER_SECOND) < 0LL) {
return NULL;
}

if (!FileTimeToSystemTime(&lft,&st)) {
EPOCH_TO_FILETIME(ft,*epochs);

if (!FileTimeToSystemTime(&ft,&st)) {
return NULL;
}

Expand All @@ -87,22 +260,111 @@ struct tm * sys_localtime_r(time_t *epochs, struct tm *ptm)
ptm->tm_hour = (int) st.wHour;
ptm->tm_min = (int) st.wMinute;
ptm->tm_sec = (int) st.wSecond;
ptm->tm_wday = (int) st.wDayOfWeek;
ptm->tm_isdst = 0;
{
int yday = ptm->tm_mday - 1;
int m = ptm->tm_mon;
int leap_year = 0;
if (((st.wYear % 4) == 0 && (st.wYear % 100) > 0) ||
((st.wYear % 400) == 0)) {
leap_year = 1;
}
while (m > 0) {
yday +=days_in_month[leap_year][m];
--m;
}
ptm->tm_yday = yday;
}

return ptm;
}

time_t
sys_mktime(struct tm *ptm)
{
FILETIME ft;
SYSTEMTIME st;
int dst = 0;
time_t epochs;

memset(&st,0,sizeof(st));
/* Convert relevant parts of truct tm to SYSTEMTIME */
st.wYear = (USHORT) (ptm->tm_year + 1900);
st.wMonth = (USHORT) (ptm->tm_mon + 1);
st.wDay = (USHORT) ptm->tm_mday;
st.wHour = (USHORT) ptm->tm_hour;
st.wMinute = (USHORT) ptm->tm_min;
st.wSecond = (USHORT) ptm->tm_sec;

SystemTimeToFileTime(&st,&ft);

/* ft is now some kind of local file time, but it may be wrong depending
on what is in the tm_dst field. We need to manually convert it to
UTC before turning it into epochs */

if (have_static_tzi) {
FILETIME dst_start, dst_stop;
ULARGE_INTEGER ull_start,ull_stop,ull_ft;

FILETIME_TO_ULI(ull_ft,ft);

/* Correct everything except DST */
ull_ft.QuadPart += (static_tzi.Bias+static_tzi.StandardBias)
* LL_LITERAL(60) * TICKS_PER_SECOND;

/* Determine if DST is active */
if (ptm->tm_isdst >= 0) {
dst = ptm->tm_isdst;
} else if (static_tzi.DaylightDate.wMonth != 0){
/* This is how windows mktime does it, meaning it does not
take nonexisting local times into account */
get_dst_switchtime(st.wYear,static_tzi.DaylightDate,
static_tzi.Bias+static_tzi.StandardBias,
&dst_start);
get_dst_switchtime(st.wYear,static_tzi.StandardDate,
static_tzi.Bias+static_tzi.StandardBias+
static_tzi.DaylightBias,
&dst_stop);
FILETIME_TO_ULI(ull_start,dst_start);
FILETIME_TO_ULI(ull_stop,dst_stop);
if ((ull_ft.QuadPart >= ull_start.QuadPart) &&
(ull_ft.QuadPart < ull_stop.QuadPart)) {
/* We are in DST */
dst = 1;
}
}
/* Correct for DST */
if (dst) {
ull_ft.QuadPart += static_tzi.DaylightBias *
LL_LITERAL(60) * TICKS_PER_SECOND;
}
epochs = ((ull_ft.QuadPart / TICKS_PER_SECOND) - EPOCH_JULIAN_DIFF);
} else {
/* No DST, life is easy... */
FILETIME lft;
LocalFileTimeToFileTime(&ft,&lft);
FILETIME_TO_EPOCH(epochs,lft);
}
/* Normalize the struct tm */
sys_localtime_r(&epochs,ptm);
return epochs;
}

void
sys_gettimeofday(SysTimeval *tv)
{
SYSTEMTIME t;
FILETIME ft;
LONGLONG lft;
ULARGE_INTEGER ull;

GetSystemTime(&t);
SystemTimeToFileTime(&t, &ft);
memcpy(&lft, &ft, sizeof(lft));
tv->tv_usec = (long) ((lft / LL_LITERAL(10)) % LL_LITERAL(1000000));
tv->tv_sec = (long) ((lft / LL_LITERAL(10000000)) - EPOCH_JULIAN_DIFF);
FILETIME_TO_ULI(ull,ft);
tv->tv_usec = (long) ((ull.QuadPart / LL_LITERAL(10)) %
LL_LITERAL(1000000));
tv->tv_sec = (long) ((ull.QuadPart / LL_LITERAL(10000000)) -
EPOCH_JULIAN_DIFF);
}

SysHrTime
Expand Down

0 comments on commit 46eb435

Please sign in to comment.