// 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; } }