// timeutil.cpp -- CLinearTimeAbsolute and CLinearTimeDelta modules.
//
// $Id: timeutil.cpp,v 1.8 2000/06/20 16:37:47 sdennis Exp $
//
// Date/Time code based on algorithms presented in "Calendrical Calculations",
// Cambridge Press, 1998.
//
// do_convtime() is heavily modified from previous game server code.
//
// MUX 2.0
// Copyright (C) 1998 through 2000 Solid Vertical Domains, Ltd. All
// rights not explicitly given are reserved. Permission is given to
// use this code for building and hosting text-based game servers.
// Permission is given to use this code for other non-commercial
// purposes. To use this code for commercial purposes other than
// building/hosting text-based game servers, contact the author at
// Stephen Dennis <sdennis@svdltd.com> for another license.
//
#include "autoconf.h"
#include "config.h"
// for tzset() and localtime()
//
#include <time.h>
#include "timeutil.h"
#include "stringutil.h"
int iMod(int x, int y)
{
if (x < 0)
{
return ((x+1) % y) + y - 1;
}
else
{
return x % y;
}
}
int iModAdjusted(int x, int y)
{
return iMod(x - 1, y) + 1;
}
#if 0
INT64 i64Mod(INT64 x, INT64 y)
{
if (x < 0)
{
return ((x+1) % y) + y - 1;
}
else
{
return x % y;
}
}
INT64 i64ModAdjusted(INT64 x, INT64 y)
{
return i64Mod(x - 1, y) + 1;
}
#endif
int DCL_CDECL iFloorDivision(int x, int y)
{
if (x < 0)
{
return (x - y + 1) / y;
}
else
{
return x / y;
}
}
INT64 DCL_CDECL i64FloorDivision(INT64 x, INT64 y)
{
if (x < 0)
{
return (x - y + 1) / y;
}
else
{
return x / y;
}
}
int DCL_CDECL iFloorDivisionMod(int x, int y, int *piMod)
{
if (x < 0)
{
*piMod = ((x+1) % y) + y - 1;
return (x - y + 1) / y;
}
else
{
*piMod = x % y;
return x / y;
}
}
INT64 DCL_CDECL i64FloorDivisionMod(INT64 x, INT64 y, INT64 *piMod)
{
if (x < 0)
{
*piMod = ((x+1) % y) + y - 1;
return (x - y + 1) / y;
}
else
{
*piMod = x % y;
return x / y;
}
}
#if 0
int iCeilingDivision(int x, int y)
{
if (x < 0)
{
return x / y;
}
else
{
return (x + y - 1) / y;
}
}
INT64 i64CeilingDivision(INT64 x, INT64 y)
{
if (x < 0)
{
return x / y;
}
else
{
return (x + y - 1) / y;
}
}
#endif
const INT64 FACTOR_100NS_PER_MINUTE = FACTOR_100NS_PER_SECOND*60;
const INT64 FACTOR_100NS_PER_HOUR = FACTOR_100NS_PER_MINUTE*60;
const INT64 FACTOR_100NS_PER_DAY = FACTOR_100NS_PER_HOUR*24;
const INT64 FACTOR_100NS_PER_WEEK = FACTOR_100NS_PER_DAY*7;
int CLinearTimeAbsolute::m_nCount = 0;
char CLinearTimeAbsolute::m_Buffer[204];
void GetUTCLinearTime(INT64 *plt);
void GetLocalFieldedTime(FIELDEDTIME *ft);
int operator<(const CLinearTimeAbsolute& lta, const CLinearTimeAbsolute& ltb)
{
return lta.m_tAbsolute < ltb.m_tAbsolute;
}
int operator>(const CLinearTimeAbsolute& lta, const CLinearTimeAbsolute& ltb)
{
return lta.m_tAbsolute > ltb.m_tAbsolute;
}
int operator==(const CLinearTimeAbsolute& lta, const CLinearTimeAbsolute& ltb)
{
return lta.m_tAbsolute == ltb.m_tAbsolute;
}
int operator==(const CLinearTimeDelta& lta, const CLinearTimeDelta& ltb)
{
return lta.m_tDelta == ltb.m_tDelta;
}
int operator!=(const CLinearTimeDelta& lta, const CLinearTimeDelta& ltb)
{
return lta.m_tDelta != ltb.m_tDelta;
}
int operator<=(const CLinearTimeAbsolute& lta, const CLinearTimeAbsolute& ltb)
{
return lta.m_tAbsolute <= ltb.m_tAbsolute;
}
CLinearTimeAbsolute operator-(const CLinearTimeAbsolute& lta, const CLinearTimeDelta& ltd)
{
CLinearTimeAbsolute t;
t.m_tAbsolute = lta.m_tAbsolute - ltd.m_tDelta;
return t;
}
CLinearTimeAbsolute::CLinearTimeAbsolute(const CLinearTimeAbsolute& ltaOrigin, const CLinearTimeDelta& ltdOffset)
{
m_tAbsolute = ltaOrigin.m_tAbsolute + ltdOffset.m_tDelta;
}
char *CLinearTimeAbsolute::ReturnSecondsString(void)
{
INT64 lt = i64FloorDivision(m_tAbsolute - EPOCH_OFFSET, FACTOR_100NS_PER_SECOND);
Tiny_i64toa(lt, m_Buffer);
return m_Buffer;
}
CLinearTimeAbsolute::CLinearTimeAbsolute(const CLinearTimeAbsolute <a)
{
m_tAbsolute = lta.m_tAbsolute;
}
CLinearTimeAbsolute::CLinearTimeAbsolute(void)
{
m_tAbsolute = 0;
}
CLinearTimeDelta::CLinearTimeDelta(void)
{
m_tDelta = 0;
}
void CLinearTimeDelta::ReturnTimeValueStruct(struct timeval *tv)
{
INT64 Leftover;
tv->tv_sec = (long)i64FloorDivisionMod(m_tDelta, FACTOR_100NS_PER_SECOND, &Leftover);
tv->tv_usec = (long)i64FloorDivision(Leftover, FACTOR_100NS_PER_MICROSECOND);
}
void CLinearTimeDelta::SetTimeValueStruct(struct timeval *tv)
{
m_tDelta = FACTOR_100NS_PER_SECOND * tv->tv_sec
+ FACTOR_100NS_PER_MICROSECOND * tv->tv_usec;
}
// Time string format is usually 24 characters long, in format
// Ddd Mmm DD HH:MM:SS YYYY
//
// However, the year may be larger than 4 characters.
//
char *DayOfWeekString[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
const char daystab[] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
const char *monthtab[] =
{
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
void CLinearTimeDelta::SetMilliseconds(unsigned long arg_dwMilliseconds)
{
m_tDelta = arg_dwMilliseconds * FACTOR_100NS_PER_MILLISECOND;
}
long CLinearTimeDelta::ReturnMilliseconds(void)
{
return (long)(m_tDelta/FACTOR_100NS_PER_MILLISECOND);
}
void CLinearTimeDelta::SetSeconds(INT64 arg_tSeconds)
{
m_tDelta = arg_tSeconds * FACTOR_100NS_PER_SECOND;
}
void CLinearTimeDelta::Set100ns(INT64 arg_t100ns)
{
m_tDelta = arg_t100ns;
}
INT64 CLinearTimeDelta::Return100ns(void)
{
return m_tDelta;
}
void CLinearTimeAbsolute::Set100ns(INT64 arg_t100ns)
{
m_tAbsolute = arg_t100ns;
}
INT64 CLinearTimeAbsolute::Return100ns(void)
{
return m_tAbsolute;
}
void CLinearTimeAbsolute::SetSeconds(INT64 arg_tSeconds)
{
m_tAbsolute = arg_tSeconds;
m_tAbsolute *= FACTOR_100NS_PER_SECOND;
// Epoch difference between (00:00:00 UTC, January 1, 1970) and
// (00:00:00 UTC, January 1, 1601).
//
m_tAbsolute += EPOCH_OFFSET;
}
INT64 CLinearTimeAbsolute::ReturnSeconds(void)
{
// INT64 is in hundreds of nanoseconds.
// And the Epoch is 0:00 1/1/1601 instead of 0:00 1/1/1970
//
return i64FloorDivision(m_tAbsolute - EPOCH_OFFSET, FACTOR_100NS_PER_SECOND);
}
BOOL isLeapYear(long iYear)
{
if (iMod(iYear, 4) != 0)
{
// Not a leap year.
//
return FALSE;
}
unsigned long wMod = iMod(iYear, 400);
if ((wMod == 100) || (wMod == 200) || (wMod == 300))
{
// Not a leap year.
//
return FALSE;
}
return TRUE;
}
BOOL isValidDate(int iYear, int iMonth, int iDay)
{
if (iYear < -27256 || 30826 < iYear)
{
return FALSE;
}
if (iMonth < 1 || 12 < iMonth)
{
return FALSE;
}
if (iDay < 1 || daystab[iMonth-1] < iDay)
{
return FALSE;
}
if (iMonth == 2 && iDay == 29 && !isLeapYear(iYear))
{
return FALSE;
}
return TRUE;
}
int FixedFromGregorian(int iYear, int iMonth, int iDay)
{
iYear = iYear - 1;
int iFixedDay = 365 * iYear;
iFixedDay += iFloorDivision(iYear, 4);
iFixedDay -= iFloorDivision(iYear, 100);
iFixedDay += iFloorDivision(iYear, 400);
iFixedDay += iFloorDivision(367 * iMonth - 362, 12);
iFixedDay += iDay;
if (iMonth > 2)
{
if (isLeapYear(iYear+1))
{
iFixedDay -= 1;
}
else
{
iFixedDay -= 2;
}
}
// At this point, iFixedDay has an epoch of 1 R.D.
//
return iFixedDay;
}
int FixedFromGregorian_Adjusted(int iYear, int iMonth, int iDay)
{
int iFixedDay = FixedFromGregorian(iYear, iMonth, iDay);
// At this point, iFixedDay has an epoch of 1 R.D.
// We need an Epoch of (00:00:00 UTC, January 1, 1601)
//
return iFixedDay - 584389;
}
// Epoch of iFixedDay should be 1 R.D.
//
void GregorianFromFixed(int iFixedDay, int &iYear, int &iMonth, int &iDayOfYear, int &iDayOfMonth, int &iDayOfWeek)
{
int d0 = iFixedDay - 1;
int d1, n400 = iFloorDivisionMod(d0, 146097, &d1);
int d2, n100 = iFloorDivisionMod(d1, 36524, &d2);
int d3, n4 = iFloorDivisionMod(d2, 1461, &d3);
int d4, n1 = iFloorDivisionMod(d3, 365, &d4);
d4 = d4 + 1;
iYear = 400*n400 + 100*n100 + 4*n4 + n1;
if (n100 != 4 && n1 != 4)
{
iYear = iYear + 1;
}
static int cache_iYear = 99999;
static int cache_iJan1st = 0;
static int cache_iMar1st = 0;
int iFixedDayOfJanuary1st;
int iFixedDayOfMarch1st;
if (iYear == cache_iYear)
{
iFixedDayOfJanuary1st = cache_iJan1st;
iFixedDayOfMarch1st = cache_iMar1st;
}
else
{
cache_iYear = iYear;
cache_iJan1st = iFixedDayOfJanuary1st = FixedFromGregorian(iYear, 1, 1);
cache_iMar1st = iFixedDayOfMarch1st = FixedFromGregorian(iYear, 3, 1);
}
int iPriorDays = iFixedDay - iFixedDayOfJanuary1st;
int iCorrection;
if (iFixedDay < iFixedDayOfMarch1st)
{
iCorrection = 0;
}
else if (isLeapYear(iYear))
{
iCorrection = 1;
}
else
{
iCorrection = 2;
}
iMonth = (12*(iPriorDays+iCorrection)+373)/367;
iDayOfMonth = iFixedDay - FixedFromGregorian(iYear, iMonth, 1) + 1;
iDayOfYear = iPriorDays + 1;
// Calculate the Day of week using the linear progression of days.
//
iDayOfWeek = iMod(iFixedDay, 7);
}
void GregorianFromFixed_Adjusted(int iFixedDay, int &iYear, int &iMonth, int &iDayOfYear, int &iDayOfMonth, int &iDayOfWeek)
{
// We need to convert the Epoch to 1 R.D. from
// (00:00:00 UTC, January 1, 1601)
//
GregorianFromFixed(iFixedDay + 584389, iYear, iMonth, iDayOfYear, iDayOfMonth, iDayOfWeek);
}
// do_convtime()
//
// converts time string to time structure (fielded time). Returns 1 on
// success, 0 on fail. Time string format is:
//
// [Ddd] Mmm DD HH:MM:SS YYYY
//
// The initial Day-of-week token is optional.
//
int MonthTabHash[12] =
{
0x004a414e, 0x00464542, 0x004d4152, 0x00415052,
0x004d4159, 0x004a554e, 0x004a554c, 0x00415547,
0x00534550, 0x004f4354, 0x004e4f56, 0x00444543
};
BOOL ParseThreeLetters(const char **pp, int *piHash)
{
*piHash = 0;
// Skip Initial spaces
//
const char *p = *pp;
while (*p == ' ')
{
p++;
}
// Parse space-seperate token.
//
const char *q = p;
int iHash = 0;
while (*q && *q != ' ')
{
if (!Tiny_IsAlpha[(unsigned char)*q])
{
return FALSE;
}
iHash = (iHash << 8) | Tiny_ToUpper[(unsigned char)*q];
q++;
}
// Must be exactly 3 letters long.
//
if (q - p != 3)
{
return FALSE;
}
p = q;
// Skip final spaces
//
while (*p == ' ')
{
p++;
}
*pp = p;
*piHash = iHash;
return TRUE;
}
int do_convtime(const char *str, FIELDEDTIME *ft)
{
if (!str || !ft)
{
return 0;
}
// Day-of-week OR month.
//
const char *p = str;
int i, iHash;
if (!ParseThreeLetters(&p, &iHash))
{
return 0;
}
for (i = 0; (i < 12) && iHash != MonthTabHash[i]; i++) ;
if (i == 12)
{
// The above three letters were probably the Day-Of-Week, the
// next three letters are required to be the month name.
//
if (!ParseThreeLetters(&p, &iHash))
{
return 0;
}
for (i = 0; (i < 12) && iHash != MonthTabHash[i]; i++) ;
if (i == 12)
{
return 0;
}
}
ft->iMonth = i + 1; // January = 1, February = 2, etc.
// Day of month.
//
ft->iDayOfMonth = (unsigned short)Tiny_atol(p);
if (ft->iDayOfMonth < 1 || daystab[i] < ft->iDayOfMonth)
{
return 0;
}
while (*p && *p != ' ') p++;
while (*p == ' ') p++;
// Hours
//
ft->iHour = (unsigned short)Tiny_atol(p);
if (ft->iHour > 23 || (ft->iHour == 0 && *p != '0'))
{
return 0;
}
while (*p && *p != ':') p++;
if (*p == ':') p++;
while (*p == ' ') p++;
// Minutes
//
ft->iMinute = (unsigned short)Tiny_atol(p);
if (ft->iMinute > 59 || (ft->iMinute == 0 && *p != '0'))
{
return 0;
}
while (*p && *p != ':') p++;
if (*p == ':') p++;
while (*p == ' ') p++;
// Seconds
//
ft->iSecond = (unsigned short)Tiny_atol(p);
if (ft->iSecond > 59 || (ft->iSecond == 0 && *p != '0'))
{
return 0;
}
while (*p && *p != ' ') p++;
while (*p == ' ') p++;
// Year
//
ft->iYear = (short)Tiny_atol(p);
if (ft->iYear == 0 && *p != '0')
{
return 0;
}
// Milliseconds, Microseconds and Nanoseconds
//
ft->iMillisecond = 0;
ft->iMicrosecond = 0;
ft->iNanosecond = 0;
// DayOfYear and DayOfWeek
//
ft->iDayOfYear = 0;
ft->iDayOfWeek = 0;
return isValidDate(ft->iYear, ft->iMonth, ft->iDayOfMonth);
}
CLinearTimeDelta::CLinearTimeDelta(CLinearTimeAbsolute t0, CLinearTimeAbsolute t1)
{
m_tDelta = t1.m_tAbsolute - t0.m_tAbsolute;
}
long CLinearTimeDelta::ReturnDays(void)
{
return (long)(m_tDelta/FACTOR_100NS_PER_DAY);
}
long CLinearTimeDelta::ReturnSeconds(void)
{
return (long)(m_tDelta/FACTOR_100NS_PER_SECOND);
}
BOOL CLinearTimeAbsolute::ReturnFields(FIELDEDTIME *arg_tStruct)
{
return LinearTimeToFieldedTime(m_tAbsolute, arg_tStruct);
}
BOOL CLinearTimeAbsolute::SetString(const char *arg_tBuffer)
{
FIELDEDTIME ft;
if (do_convtime(arg_tBuffer, &ft))
{
if (FieldedTimeToLinearTime(&ft, &m_tAbsolute))
{
return TRUE;
}
}
m_tAbsolute = 0;
return FALSE;
}
void CLinearTimeDelta::operator+=(const CLinearTimeDelta& ltd)
{
m_tDelta += ltd.m_tDelta;
}
CLinearTimeDelta operator-(const CLinearTimeAbsolute& ltaA, const CLinearTimeAbsolute& ltaB)
{
CLinearTimeDelta ltd;
ltd.m_tDelta = ltaA.m_tAbsolute - ltaB.m_tAbsolute;
return ltd;
}
CLinearTimeDelta operator-(const CLinearTimeDelta& lta, const CLinearTimeDelta& ltb)
{
CLinearTimeDelta ltd;
ltd.m_tDelta = lta.m_tDelta - ltb.m_tDelta;
return ltd;
}
CLinearTimeAbsolute operator+(const CLinearTimeAbsolute& ltaA, const CLinearTimeDelta& ltdB)
{
CLinearTimeAbsolute lta;
lta.m_tAbsolute = ltaA.m_tAbsolute + ltdB.m_tDelta;
return lta;
}
void CLinearTimeAbsolute::operator=(const CLinearTimeAbsolute& lta)
{
m_tAbsolute = lta.m_tAbsolute;
}
CLinearTimeDelta operator*(const CLinearTimeDelta& ltd, int Scale)
{
CLinearTimeDelta ltdResult;
ltdResult.m_tDelta = ltd.m_tDelta * Scale;
return ltdResult;
}
int operator/(const CLinearTimeDelta& ltdA, const CLinearTimeDelta& ltdB)
{
int iResult = (int)(ltdA.m_tDelta / ltdB.m_tDelta);
return iResult;
}
int operator<(const CLinearTimeDelta& ltdA, const CLinearTimeDelta& ltdB)
{
return ltdA.m_tDelta < ltdB.m_tDelta;
}
int operator>(const CLinearTimeDelta& ltdA, const CLinearTimeDelta& ltdB)
{
return ltdA.m_tDelta > ltdB.m_tDelta;
}
void CLinearTimeAbsolute::operator-=(const CLinearTimeDelta& ltd)
{
m_tAbsolute -= ltd.m_tDelta;
}
void CLinearTimeAbsolute::operator+=(const CLinearTimeDelta& ltd)
{
m_tAbsolute += ltd.m_tDelta;
}
BOOL CLinearTimeAbsolute::SetFields(FIELDEDTIME *arg_tStruct)
{
m_tAbsolute = 0;
return FieldedTimeToLinearTime(arg_tStruct, &m_tAbsolute);
}
void SetStructTm(FIELDEDTIME *ft, struct tm *ptm)
{
ft->iYear = ptm->tm_year + 1900;
ft->iMonth = ptm->tm_mon + 1;
ft->iDayOfMonth = ptm->tm_mday;
ft->iDayOfWeek = ptm->tm_wday;
ft->iDayOfYear = 0;
ft->iHour = ptm->tm_hour;
ft->iMinute = ptm->tm_min;
ft->iSecond = ptm->tm_sec;
}
void CLinearTimeAbsolute::ReturnUniqueString(char *buffer)
{
FIELDEDTIME ft;
if (LinearTimeToFieldedTime(m_tAbsolute, &ft))
{
sprintf(buffer, "%04d%02d%02d-%02d%02d%02d", ft.iYear, ft.iMonth,
ft.iDayOfMonth, ft.iHour, ft.iMinute, ft.iSecond);
}
else
{
sprintf(buffer, "%03d", m_nCount++);
}
}
char *CLinearTimeAbsolute::ReturnDateString(void)
{
FIELDEDTIME ft;
if (LinearTimeToFieldedTime(m_tAbsolute, &ft))
{
sprintf(m_Buffer, "%s %s %02d %02d:%02d:%02d %04d", DayOfWeekString[ft.iDayOfWeek],
monthtab[ft.iMonth-1], ft.iDayOfMonth, ft.iHour, ft.iMinute, ft.iSecond, ft.iYear);
}
else
{
m_Buffer[0] = 0;
}
return m_Buffer;
}
void CLinearTimeAbsolute::GetUTC(void)
{
GetUTCLinearTime(&m_tAbsolute);
}
void CLinearTimeAbsolute::GetLocal(void)
{
FIELDEDTIME ft;
GetLocalFieldedTime(&ft);
FieldedTimeToLinearTime(&ft, &m_tAbsolute);
}
BOOL FieldedTimeToLinearTime(FIELDEDTIME *ft, INT64 *plt)
{
if (!isValidDate(ft->iYear, ft->iMonth, ft->iDayOfMonth))
{
*plt = 0;
return FALSE;
}
int iFixedDay = FixedFromGregorian_Adjusted(ft->iYear, ft->iMonth, ft->iDayOfMonth);
ft->iDayOfWeek = iMod(iFixedDay+1, 7);
INT64 lt;
lt = iFixedDay * FACTOR_100NS_PER_DAY;
lt += ft->iHour * FACTOR_100NS_PER_HOUR;
lt += ft->iMinute * FACTOR_100NS_PER_MINUTE;
lt += ft->iSecond * FACTOR_100NS_PER_SECOND;
lt += ft->iMicrosecond * FACTOR_100NS_PER_MICROSECOND;
lt += ft->iMillisecond * FACTOR_100NS_PER_MILLISECOND;
lt += ft->iNanosecond / FACTOR_NANOSECONDS_PER_100NS;
*plt = lt;
return TRUE;
}
BOOL LinearTimeToFieldedTime(INT64 lt, FIELDEDTIME *ft)
{
INT64 ns100;
int iYear, iMonth, iDayOfYear, iDayOfMonth, iDayOfWeek;
memset(ft, 0, sizeof(FIELDEDTIME));
int d0 = (int)i64FloorDivisionMod(lt, FACTOR_100NS_PER_DAY, &ns100);
GregorianFromFixed_Adjusted(d0, iYear, iMonth, iDayOfYear, iDayOfMonth, iDayOfWeek);
if (!isValidDate(iYear, iMonth, iDayOfMonth))
{
return FALSE;
}
ft->iYear = iYear;
ft->iMonth = iMonth;
ft->iDayOfYear = iDayOfYear;
ft->iDayOfMonth = iDayOfMonth;
ft->iDayOfWeek = iDayOfWeek;
ft->iHour = (int)(ns100 / FACTOR_100NS_PER_HOUR);
ns100 = ns100 % FACTOR_100NS_PER_HOUR;
ft->iMinute = (int)(ns100 / FACTOR_100NS_PER_MINUTE);
ns100 = ns100 % FACTOR_100NS_PER_MINUTE;
ft->iSecond = (int)(ns100 / FACTOR_100NS_PER_SECOND);
ns100 = ns100 % FACTOR_100NS_PER_SECOND;
ft->iMillisecond = (int)(ns100 % FACTOR_100NS_PER_MILLISECOND);
ns100 = ns100 % FACTOR_100NS_PER_MILLISECOND;
ft->iMicrosecond = (int)(ns100 % FACTOR_100NS_PER_MICROSECOND);
ns100 = ns100 % FACTOR_100NS_PER_MICROSECOND;
ft->iNanosecond = (int)(ns100 * FACTOR_NANOSECONDS_PER_100NS);
return TRUE;
}
void CLinearTimeAbsolute::SetSecondsString(char *arg_szSeconds)
{
INT64 lt = Tiny_atoi64(arg_szSeconds);
m_tAbsolute = (lt * FACTOR_100NS_PER_SECOND) + EPOCH_OFFSET;
}
// OS Dependent Routines:
//
#ifdef WIN32
void GetUTCLinearTime(INT64 *plt)
{
GetSystemTimeAsFileTime((struct _FILETIME *)plt);
}
void GetLocalFieldedTime(FIELDEDTIME *ft)
{
SYSTEMTIME st;
GetLocalTime(&st);
ft->iYear = st.wYear;
ft->iMonth = st.wMonth;
ft->iDayOfMonth = st.wDay;
ft->iDayOfWeek = st.wDayOfWeek;
ft->iDayOfYear = 0;
ft->iHour = st.wHour;
ft->iMinute = st.wMinute;
ft->iSecond = st.wSecond;
ft->iMillisecond = st.wMilliseconds;
ft->iMicrosecond = 0;
ft->iNanosecond = 0;
}
#else // !WIN32
void GetUTCLinearTime(INT64 *plt)
{
struct timeval tv;
struct timezone tz;
tz.tz_minuteswest = 0;
tz.tz_dsttime = 0;
gettimeofday(&tv, &tz);
*plt = (((INT64)tv.tv_sec) * FACTOR_100NS_PER_SECOND)
+ (tv.tv_usec * FACTOR_100NS_PER_MICROSECOND)
+ EPOCH_OFFSET;
}
void GetLocalFieldedTime(FIELDEDTIME *ft)
{
struct timeval tv;
struct timezone tz;
tz.tz_minuteswest = 0;
tz.tz_dsttime = 0;
gettimeofday(&tv, &tz);
time_t seconds = tv.tv_sec;
struct tm *ptm = localtime(&seconds);
SetStructTm(ft, ptm);
ft->iMillisecond = (tv.tv_usec/1000);
ft->iMicrosecond = (tv.tv_usec%1000);
ft->iNanosecond = 0;
}
#endif // !WIN32
#if 0
CLinearTimeAbsolute FirstInMonth(int iYear, int iMonth, int iDayOfWeek)
{
FIELDEDTIME ft;
memset(&ft, 0, sizeof(FIELDEDTIME));
ft.iYear = iYear;
ft.iMonth = iMonth;
ft.iDayOfMonth = 1;
CLinearTimeAbsolute lta;
lta.SetFields(&ft);
ft.iDayOfMonth = iModAdjusted(ft.iDayOfMonth - ft.iDayOfWeek + iDayOfWeek, 7);
lta.SetFields(&ft);
return lta;
}
CLinearTimeAbsolute LastInMonth(int iYear, int iMonth, int iDayOfWeek)
{
FIELDEDTIME ft;
memset(&ft, 0, sizeof(FIELDEDTIME));
if (iMonth == 12)
{
ft.iYear = iYear+1;
ft.iMonth = 1;
}
else
{
ft.iYear = iYear;
ft.iMonth = iMonth+1;
}
ft.iDayOfMonth = 1;
CLinearTimeAbsolute lta;
lta.SetFields(&ft);
ft.iDayOfMonth = iModAdjusted(ft.iDayOfMonth - ft.iDayOfWeek + iDayOfWeek, 7);
lta.SetFields(&ft);
lta.Set100ns(lta.Return100ns()-FACTOR_100NS_PER_WEEK);
return lta;
}
#endif
static int YearType(int iYear)
{
FIELDEDTIME ft;
memset(&ft, 0, sizeof(FIELDEDTIME));
ft.iYear = iYear;
ft.iMonth = 1;
ft.iDayOfMonth = 1;
CLinearTimeAbsolute ltaLocal;
ltaLocal.SetFields(&ft);
if (isLeapYear(iYear))
{
return ft.iDayOfWeek + 8;
}
else
{
return ft.iDayOfWeek + 1;
}
}
static CLinearTimeAbsolute ltaLowerBound;
static CLinearTimeAbsolute ltaUpperBound;
static CLinearTimeDelta ltdTimeZoneStandard;
// This determines the valid range of time_t and finds a 'standard'
// time zone near the earliest supported time_t.
//
void test_time_t(void)
{
time_t ulUpper, ulMid, ulLower;
// Determine the upper bound.
//
ulUpper = LONG_MAX;
ulLower = 0;
if (localtime(&ulUpper) == NULL)
{
// Search for upper bound.
//
do
{
ulMid = (ulLower + ulUpper)/2;
if (localtime(&ulMid))
{
ulLower = ulMid;
}
else
{
ulUpper = ulMid-1;
}
} while (ulLower != ulUpper);
}
ltaUpperBound.SetSeconds(ulLower);
// Determine the lower bound.
//
ulUpper = 0;
ulLower = LONG_MIN;
if (localtime(&ulLower) == NULL)
{
// Search for lower bound.
//
do
{
ulMid = (ulLower + ulUpper)/2;
if (localtime(&ulMid))
{
ulUpper = ulMid;
}
else
{
ulLower = ulMid+1;
}
} while (ulLower != ulUpper);
}
ltaLowerBound.SetSeconds(ulUpper);
// Find a time near ulLower for which DST is not in affect.
//
for (;;)
{
struct tm *ptm = localtime(&ulLower);
if (ptm->tm_isdst <= 0)
{
// Daylight savings time is either not in effect or
// we have no way of knowing whether it is in effect
// or not.
//
FIELDEDTIME ft;
SetStructTm(&ft, ptm);
ft.iMillisecond = 0;
ft.iMicrosecond = 0;
ft.iNanosecond = 0;
CLinearTimeAbsolute ltaLocal;
CLinearTimeAbsolute ltaUTC;
ltaLocal.SetFields(&ft);
ltaUTC.SetSeconds(ulLower);
ltdTimeZoneStandard = ltaLocal - ltaUTC;
break;
}
// Advance the time by 1 month (expressed as seconds).
//
ulLower += 30*24*60*60;
}
}
int NearestYearOfType[15];
static CLinearTimeDelta ltdIntervalMinimum;
void TIME_Initialize(void)
{
tzset();
test_time_t();
ltdIntervalMinimum.Set100ns(FACTOR_100NS_PER_WEEK);
int i;
for (i = 0; i < 15; i++)
{
NearestYearOfType[i] = -1;
}
int cnt = 14;
FIELDEDTIME ft;
ltaUpperBound.ReturnFields(&ft);
for (i = ft.iYear-1; cnt; i--)
{
int iYearType = YearType(i);
if (NearestYearOfType[iYearType] < 0)
{
NearestYearOfType[iYearType] = i;
cnt--;
}
}
}
// Explanation of the table.
//
// The table contains intervals of time for which (ltdOffset, isDST)
// tuples are known.
//
// Two intervals may be combined when they share the same tuple
// value and the time between them is less than ltdIntervalMinimum.
//
// Intervals are thrown away in a least-recently-used (LRU) fashion.
//
typedef struct
{
CLinearTimeAbsolute ltaStart;
CLinearTimeAbsolute ltaEnd;
CLinearTimeDelta ltdOffset;
BOOL isDST;
int nTouched;
} OffsetEntry;
#define MAX_OFFSETS 50
int nOffsetTable = 0;
int nTouched0 = 0;
OffsetEntry OffsetTable[MAX_OFFSETS];
// This function finds the entry in the table (0...nOffsetTable-1)
// whose ltaStart is less than or equal to the search key.
// If no entry satisfies this search, -1 is returned.
//
static int FindOffsetEntry(const CLinearTimeAbsolute& lta)
{
int lo = 0;
int hi = nOffsetTable - 1;
int mid = 0;
while (lo <= hi)
{
mid = ((hi - lo) >> 1) + lo;
if (OffsetTable[mid].ltaStart <= lta)
{
lo = mid + 1;
}
else
{
hi = mid - 1;
}
}
return lo-1;
}
static BOOL QueryOffsetTable
(
CLinearTimeAbsolute lta,
CLinearTimeDelta *pltdOffset,
BOOL *pisDST,
int *piEntry
)
{
nTouched0++;
int i = FindOffsetEntry(lta);
*piEntry = i;
// Is the interval defined?
//
if ( 0 <= i
&& lta <= OffsetTable[i].ltaEnd)
{
*pltdOffset = OffsetTable[i].ltdOffset;
*pisDST = OffsetTable[i].isDST;
OffsetTable[i].nTouched = nTouched0;
return TRUE;
}
return FALSE;
}
static void UpdateOffsetTable
(
CLinearTimeAbsolute <a,
CLinearTimeDelta ltdOffset,
BOOL isDST,
int i
)
{
Again:
nTouched0++;
// Is the interval defined?
//
if ( 0 <= i
&& lta <= OffsetTable[i].ltaEnd)
{
OffsetTable[i].nTouched = nTouched0;
return;
}
BOOL bTryMerge = FALSE;
// Coalesce new data point into this interval if:
//
// 1. Tuple for this interval matches the new tuple value.
// 2. It's close enough that we can assume all intervening
// values are the same.
//
if ( 0 <= i
&& OffsetTable[i].ltdOffset == ltdOffset
&& OffsetTable[i].isDST == isDST
&& lta <= OffsetTable[i].ltaEnd + ltdIntervalMinimum)
{
// Cool. We can just extend this interval to include our new
// data point.
//
OffsetTable[i].ltaEnd = lta;
OffsetTable[i].nTouched = nTouched0;
// Since we have changed this interval, we may be able to
// coalesce it with the next interval.
//
bTryMerge = TRUE;
}
// Coalesce new data point into next interval if:
//
// 1. Next interval exists.
// 2. Tuple in next interval matches the new tuple value.
// 3. It's close enough that we can assume all intervening
// values are the same.
//
int iNext = i+1;
if ( 0 <= iNext
&& iNext < nOffsetTable
&& OffsetTable[iNext].ltdOffset == ltdOffset
&& OffsetTable[iNext].isDST == isDST
&& OffsetTable[iNext].ltaStart - ltdIntervalMinimum <= lta)
{
// Cool. We can just extend the next interval to include our
// new data point.
//
OffsetTable[iNext].ltaStart = lta;
OffsetTable[iNext].nTouched = nTouched0;
// Since we have changed the next interval, we may be able
// to coalesce it with the previous interval.
//
bTryMerge = TRUE;
}
if (bTryMerge)
{
// We should combine the current and next intervals if we can.
//
if ( 0 <= i
&& iNext < nOffsetTable
&& OffsetTable[i].ltdOffset == OffsetTable[iNext].ltdOffset
&& OffsetTable[i].isDST == OffsetTable[iNext].isDST
&& OffsetTable[iNext].ltaStart - ltdIntervalMinimum
<= OffsetTable[i].ltaEnd)
{
if (0 <= i && 0 <= iNext)
{
OffsetTable[i].ltaEnd = OffsetTable[iNext].ltaEnd;
}
int nSize = sizeof(OffsetEntry)*(nOffsetTable-i-2);
memmove(OffsetTable+i+1, OffsetTable+i+2, nSize);
nOffsetTable--;
}
}
else
{
// We'll have'ta create a new interval.
//
if (nOffsetTable < MAX_OFFSETS)
{
int nSize = sizeof(OffsetEntry)*(nOffsetTable-i-1);
memmove(OffsetTable+i+2, OffsetTable+i+1, nSize);
nOffsetTable++;
i++;
OffsetTable[i].isDST = isDST;
OffsetTable[i].ltdOffset = ltdOffset;
OffsetTable[i].ltaStart= lta;
OffsetTable[i].ltaEnd= lta;
OffsetTable[i].nTouched = nTouched0;
}
else
{
// We ran out of room. Throw away the least used
// interval and try again.
//
int nMinTouched = OffsetTable[0].nTouched;
int iMinTouched = 0;
for (int j = 1; j < nOffsetTable; j++)
{
if (OffsetTable[j].nTouched - nMinTouched < 0)
{
nMinTouched = OffsetTable[j].nTouched;
iMinTouched = j;
}
}
int nSize = sizeof(OffsetEntry)*(nOffsetTable-iMinTouched-1);
memmove(OffsetTable+iMinTouched, OffsetTable+iMinTouched+1, nSize);
nOffsetTable--;
goto Again;
}
}
}
static CLinearTimeDelta QueryLocalOffsetAt_Internal
(
CLinearTimeAbsolute lta,
BOOL *pisDST,
int iEntry
)
{
// At this point, we much use localtime() to discover what the
// UTC to local time offset is for the requested UTC time.
//
// However, localtime() does not support times beyond around
// the 2038 year on machines with 32-bit integers, so to
// compensant for this, and knowing that we are already dealing
// with fictionalized adjustments, we associate a future year
// that is outside the supported range with one that is inside
// the support range of the same type (there are 14 different
// year types depending on leap-year-ness and which day of the
// week that January 1st falls on.
//
// Note: Laws beyond the current year have not been written yet
// and are subject to change at any time. For example, Israel
// doesn't have regular rules for DST but makes a directive each
// year...sometimes to avoid conflicts with Jewish holidays.
//
if (lta > ltaUpperBound)
{
// Map the specified year to the closest year with the same
// pattern of weeks.
//
FIELDEDTIME ft;
lta.ReturnFields(&ft);
ft.iYear = NearestYearOfType[YearType(ft.iYear)];
lta.SetFields(&ft);
}
// Rely on localtime() to take a UTC count of seconds and convert
// to a fielded local time complete with known timezone and DST
// adjustments.
//
time_t lt = (time_t)lta.ReturnSeconds();
struct tm *ptm = localtime(<);
if (!ptm)
{
// This should never happen as we have already taken pains
// to restrict the range of UTC seconds gives to localtime().
//
return ltdTimeZoneStandard;
}
// With the fielded (or broken down) time from localtime(), we
// can convert to a linear time in the same time zone.
//
FIELDEDTIME ft;
SetStructTm(&ft, ptm);
ft.iMillisecond = 0;
ft.iMicrosecond = 0;
ft.iNanosecond = 0;
CLinearTimeAbsolute ltaLocal;
CLinearTimeDelta ltdOffset;
ltaLocal.SetFields(&ft);
ltdOffset = ltaLocal - lta;
*pisDST = ptm->tm_isdst > 0 ? TRUE: FALSE;
// We now have a mapping from UTC lta to a (ltdOffset, *pisDST)
// tuple which will will use to update the cache.
//
UpdateOffsetTable(lta, ltdOffset, *pisDST, iEntry);
return ltdOffset;
}
static CLinearTimeDelta QueryLocalOffsetAtUTC
(
const CLinearTimeAbsolute <a,
BOOL *pisDST
)
{
*pisDST = FALSE;
// DST started in Britain in May 1916 and in the US in 1918.
// Germany used it a little before May 1916, but I'm not sure
// of exactly when.
//
// So, there is locale specific information about DST adjustments
// that could reasonable be made between 1916 and 1970.
// Because Unix supports negative time_t values while Win32 does
// not, it can also support that 1916 to 1970 interval with
// timezone information.
//
// Win32 only supports one timezone rule at a time, or rather
// it doesn't have any historical timezone information, but you
// can/must provide it yourself. So, in the Win32 case, unless we
// are willing to provide historical information (from a tzfile
// perhaps), it will only give us the current timezone rule
// (the one selected by the control panel or by a TZ environment
// variable). It projects this rule forwards and backwards.
//
// Feel free to fill that gap in yourself with a tzfile file
// reader for Win32.
//
if (lta < ltaLowerBound)
{
return ltdTimeZoneStandard;
}
// Next, we check our table for whether this time falls into a
// previously discovered interval. You could view this as a
// cache, or you could also view it as a way of reading in the
// tzfile without becoming system-dependent enough to actually
// read the tzfile.
//
CLinearTimeDelta ltdOffset;
int iEntry;
if (QueryOffsetTable(lta, <dOffset, pisDST, &iEntry))
{
return ltdOffset;
}
ltdOffset = QueryLocalOffsetAt_Internal(lta, pisDST, iEntry);
// Since the cache failed, let's make sure we have a useful
// interval surrounding this last request so that future queries
// nearby will be serviced by the cache.
//
CLinearTimeAbsolute ltaProbe;
CLinearTimeDelta ltdDontCare;
BOOL bDontCare;
ltaProbe = lta - ltdIntervalMinimum;
if (!QueryOffsetTable(ltaProbe, <dDontCare, &bDontCare, &iEntry))
{
QueryLocalOffsetAt_Internal(ltaProbe, &bDontCare, iEntry);
}
ltaProbe = lta + ltdIntervalMinimum;
if (!QueryOffsetTable(ltaProbe, <dDontCare, &bDontCare, &iEntry))
{
QueryLocalOffsetAt_Internal(ltaProbe, &bDontCare, iEntry);
}
return ltdOffset;
}
void CLinearTimeAbsolute::UTC2Local(void)
{
BOOL bDST;
CLinearTimeDelta ltd = QueryLocalOffsetAtUTC(*this, &bDST);
m_tAbsolute += ltd.m_tDelta;
}
void CLinearTimeAbsolute::Local2UTC(void)
{
BOOL bDST1, bDST2;
CLinearTimeDelta ltdOffset1 = QueryLocalOffsetAtUTC(*this, &bDST1);
CLinearTimeAbsolute ltaUTC2 = *this - ltdOffset1;
CLinearTimeDelta ltdOffset2 = QueryLocalOffsetAtUTC(ltaUTC2, &bDST2);
CLinearTimeAbsolute ltaLocalGuess = ltaUTC2 + ltdOffset2;
if (ltaLocalGuess == *this)
{
// We found an offset, UTC, local time combination that
// works.
//
m_tAbsolute = ltaUTC2.m_tAbsolute;
}
else
{
CLinearTimeAbsolute ltaUTC1 = *this - ltdOffset2;
m_tAbsolute = ltaUTC1.m_tAbsolute;
}
}