/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 1997-2011. All Rights Reserved.
*
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved online at http://www.erlang.org/.
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* %CopyrightEnd%
*/
/*
* Purpose: System-dependent time functions.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "sys.h"
#include "assert.h"
#define LL_LITERAL(X) ERTS_I64_LITERAL(X)
/******************* Routines for time measurement *********************/
#define EPOCH_JULIAN_DIFF LL_LITERAL(11644473600)
#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); \
ULI_TO_FILETIME(ft,ull); \
} while(0)
#define FILETIME_TO_EPOCH(epoch, ft) \
do { \
ULARGE_INTEGER ull; \
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;
static erts_smp_spinlock_t wrap_lock;
/* 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;
}
erts_smp_spinlock_init(&wrap_lock, "sys_gethrtime");
return 1;
}
/* 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 ((((*epochs) + EPOCH_JULIAN_DIFF) * TICKS_PER_SECOND) < 0LL) {
return NULL;
}
EPOCH_TO_FILETIME(ft,*epochs);
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;
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;
ULARGE_INTEGER ull;
GetSystemTime(&t);
SystemTimeToFileTime(&t, &ft);
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
sys_gethrtime(void)
{
DWORD ticks = (SysHrTime) (GetTickCount() & 0x7FFFFFFF);
SysHrTime res;
erts_smp_spin_lock(&wrap_lock);
if (ticks < (SysHrTime) last_tick_count) {
wrap += LL_LITERAL(1) << 31;
}
last_tick_count = ticks;
res = ((((LONGLONG) ticks) + wrap) * LL_LITERAL(1000000));
erts_smp_spin_unlock(&wrap_lock);
return res
}
clock_t
sys_times(SysTimes *buffer) {
clock_t kernel_ticks = (GetTickCount() /
(1000 / SYS_CLK_TCK)) & 0x7FFFFFFF;
FILETIME dummy;
LONGLONG user;
LONGLONG system;
buffer->tms_utime = buffer->tms_stime = buffer->tms_cutime =
buffer->tms_cstime = 0;
if (GetProcessTimes(GetCurrentProcess(), &dummy, &dummy,
(FILETIME *) &system, (FILETIME *) &user) == 0)
return kernel_ticks;
system /= (LONGLONG)(10000000 / SYS_CLK_TCK);
user /= (LONGLONG)(10000000 / SYS_CLK_TCK);
buffer->tms_utime = (clock_t) (user & LL_LITERAL(0x7FFFFFFF));
buffer->tms_stime = (clock_t) (system & LL_LITERAL(0x7FFFFFFF));
return kernel_ticks;
}