/*++ Copyright (c) 1997 Microsoft Corporation Module Name : datetime.cxx Abstract: This module exports common functions for date and time fields, Expanding into strings and manipulation. Author: Murali R. Krishnan ( MuraliK ) 3-Jan-1995 --*/ #include "precomp.hxx" #include #include # if !defined(dllexp) # define dllexp __declspec( dllexport) # endif #define DLL_IMPLEMENTATION #define IMPLEMENTATION_EXPORT #include #include #include "date.hxx" class dllexp CDateTime { public: FILETIME_UINT64 m_ftu; SYSTEMTIME m_st; CDateTime() { /* do nothing */ } CDateTime(const SYSTEMTIME& rst) { SetTime(rst); } CDateTime(const FILETIME& rft) { SetTime(rft); } CDateTime(const FILETIME& rft, const SYSTEMTIME& rst) { m_ftu.ft = rft; m_st = rst; } BOOL GetCurrentTime() { GetSystemTimeAsFileTime(&m_ftu.ft); return FileTimeToSystemTime(&m_ftu.ft, &m_st); } BOOL SetTime(const SYSTEMTIME& rst) { m_st = rst; return SystemTimeToFileTime(&m_st, &m_ftu.ft); } BOOL SetTime(const FILETIME& rft) { m_ftu.ft = rft; return FileTimeToSystemTime(&m_ftu.ft, &m_st); } }; static const CHAR g_rgchTwoDigits[100][2] = { { '0', '0' }, { '0', '1' }, { '0', '2' }, { '0', '3' }, { '0', '4' }, { '0', '5' }, { '0', '6' }, { '0', '7' }, { '0', '8' }, { '0', '9' }, { '1', '0' }, { '1', '1' }, { '1', '2' }, { '1', '3' }, { '1', '4' }, { '1', '5' }, { '1', '6' }, { '1', '7' }, { '1', '8' }, { '1', '9' }, { '2', '0' }, { '2', '1' }, { '2', '2' }, { '2', '3' }, { '2', '4' }, { '2', '5' }, { '2', '6' }, { '2', '7' }, { '2', '8' }, { '2', '9' }, { '3', '0' }, { '3', '1' }, { '3', '2' }, { '3', '3' }, { '3', '4' }, { '3', '5' }, { '3', '6' }, { '3', '7' }, { '3', '8' }, { '3', '9' }, { '4', '0' }, { '4', '1' }, { '4', '2' }, { '4', '3' }, { '4', '4' }, { '4', '5' }, { '4', '6' }, { '4', '7' }, { '4', '8' }, { '4', '9' }, { '5', '0' }, { '5', '1' }, { '5', '2' }, { '5', '3' }, { '5', '4' }, { '5', '5' }, { '5', '6' }, { '5', '7' }, { '5', '8' }, { '5', '9' }, { '6', '0' }, { '6', '1' }, { '6', '2' }, { '6', '3' }, { '6', '4' }, { '6', '5' }, { '6', '6' }, { '6', '7' }, { '6', '8' }, { '6', '9' }, { '7', '0' }, { '7', '1' }, { '7', '2' }, { '7', '3' }, { '7', '4' }, { '7', '5' }, { '7', '6' }, { '7', '7' }, { '7', '8' }, { '7', '9' }, { '8', '0' }, { '8', '1' }, { '8', '2' }, { '8', '3' }, { '8', '4' }, { '8', '5' }, { '8', '6' }, { '8', '7' }, { '8', '8' }, { '8', '9' }, { '9', '0' }, { '9', '1' }, { '9', '2' }, { '9', '3' }, { '9', '4' }, { '9', '5' }, { '9', '6' }, { '9', '7' }, { '9', '8' }, { '9', '9' }, }; // // Constants // #define APPEND_STR(a,b) \ {CopyMemory(a,b,sizeof(b)); a += sizeof(b)-sizeof(CHAR);} #define APPEND_PSZ( pszTail, psz ) \ { DWORD cb = strlen( psz ); \ CopyMemory( (pszTail), (psz), cb + 1 );\ (pszTail) += cb; \ } // // Makes a two-digit zero padded number (i.e., "23", or "05") // inline VOID AppendTwoDigits( CHAR*& rpszTail, DWORD Num ) { if ( Num < 100 ) { rpszTail[0] = g_rgchTwoDigits[Num][0]; rpszTail[1] = g_rgchTwoDigits[Num][1]; rpszTail[2] = '\0'; rpszTail += 2; } else { DBG_ASSERT(!"Num >= 100"); } } // // Years conversion // #define MAX_CACHED_YEARS 32 static DWORD g_nMinYear = 0, g_nMaxYear = 0; static char g_aszYears[MAX_CACHED_YEARS][4+1]; typedef CDataCache CCacheTime; static CCacheTime g_ctCurrentTime; void InitializeDateTime() { SYSTEMTIME st; GetSystemTime(&st); g_nMinYear = st.wYear - MAX_CACHED_YEARS / 2; g_nMaxYear = g_nMinYear + MAX_CACHED_YEARS - 1; DBG_ASSERT(1000 <= g_nMinYear && g_nMaxYear <= 9999); for (DWORD i = g_nMinYear; i <= g_nMaxYear; i++) { _itoa( i, g_aszYears[i - g_nMinYear], 10 ); } CDateTime dt(st); g_ctCurrentTime.Write(dt); } void TerminateDateTime() { // nothing to be done, at least for now } inline VOID AppendYear( CHAR* &rpszTail, DWORD dwYear ) { DBG_ASSERT(g_nMinYear >= 1000); DWORD i = dwYear - g_nMinYear; if (i < MAX_CACHED_YEARS) { DBG_ASSERT(g_nMinYear <= dwYear && dwYear <= g_nMaxYear); const char* pszYear = g_aszYears[i]; *rpszTail++ = *pszYear++; *rpszTail++ = *pszYear++; *rpszTail++ = *pszYear++; *rpszTail++ = *pszYear++; *rpszTail = '\0'; } else { CHAR __ach[32]; DBG_ASSERT( dwYear >= 1000 && dwYear <= 9999 ); _itoa( dwYear, __ach, 10 ); CopyMemory( rpszTail, __ach, 4+1 ); rpszTail += 4; } } // Since ::GetSystemTime is relatively expensive (310 instructions) and // ::GetSystemTimeAsFileTime is pretty cheap (20 instructions), we cache // the SYSTEMTIME representation of the current time with an accuracy of // 1.0 seconds. BOOL IISGetCurrentTime( OUT FILETIME* pft, OUT SYSTEMTIME* pst) { BOOL fUpdatedCachedTime = FALSE; CDateTime dt; while (! g_ctCurrentTime.Read(dt)) { // empty loop } FILETIME_UINT64 ftu; GetSystemTimeAsFileTime(&ftu.ft); if (ftu.u64 - dt.m_ftu.u64 >= FILETIME_1_SECOND) { #undef WT_INSTRUCTION_COUNTS #ifndef WT_INSTRUCTION_COUNTS fUpdatedCachedTime = TRUE; dt.SetTime(ftu.ft); g_ctCurrentTime.Write(dt); #endif } if (pft != NULL) *pft = dt.m_ftu.ft; if (pst != NULL) *pst = dt.m_st; return fUpdatedCachedTime; } /************************************************************ * Data ************************************************************/ static const TCHAR* s_rgchDays[] = { TEXT("Sun"), TEXT("Mon"), TEXT("Tue"), TEXT("Wed"), TEXT("Thu"), TEXT("Fri"), TEXT("Sat") }; static const TCHAR* s_rgchMonths[] = { TEXT("Jan"), TEXT("Feb"), TEXT("Mar"), TEXT("Apr"), TEXT("May"), TEXT("Jun"), TEXT("Jul"), TEXT("Aug"), TEXT("Sep"), TEXT("Oct"), TEXT("Nov"), TEXT("Dec") }; LPCTSTR DayOfWeek3CharNames(DWORD dwDayOfWeek) { return s_rgchDays[dwDayOfWeek]; } LPCTSTR Month3CharNames(DWORD dwMonth) { return s_rgchMonths[dwMonth]; } // Custom hash table for make_month() for mapping "Apr" to 4 static const CHAR MonthIndexTable[64] = { -1,'A', 2, 12, -1, -1, -1, 8, // A to G -1, -1, -1, -1, 7, -1,'N', -1, // F to O 9, -1,'R', -1, 10, -1, 11, -1, // P to W -1, 5, -1, -1, -1, -1, -1, -1, // X to Z -1,'A', 2, 12, -1, -1, -1, 8, // a to g -1, -1, -1, -1, 7, -1,'N', -1, // f to o 9, -1,'R', -1, 10, -1, 11, -1, // p to w -1, 5, -1, -1, -1, -1, -1, -1 // x to z }; static const BYTE TensDigit[10] = { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90 }; /************************************************************ * Functions ************************************************************/ WORD iis_2atoi( PCHAR s ) /*++ Converts a 2 character string to integer Arguments: s String to convert Returns: numeric equivalent, 0 on failure. --*/ { DWORD tens = s[0] - '0'; DWORD ones = s[1] - '0'; if ( (tens <= 9) && (ones <= 9) ) { return((WORD)(TensDigit[tens] + ones)); } return(0); } #if 1 WORD make_month( PCHAR s ) { UCHAR monthIndex; UCHAR c; LPCTSTR monthString; // // use the third character as the index // c = (s[2] - 0x40) & 0x3F; monthIndex = MonthIndexTable[c]; if ( monthIndex < 13 ) { goto verify; } // // ok, we need to look at the second character // if ( monthIndex == 'N' ) { // // we got an N which we need to resolve further // // // if s[1] is 'u' then Jun, if 'a' then Jan // if ( MonthIndexTable[(s[1]-0x40) & 0x3f] == 'A' ) { monthIndex = 1; } else { monthIndex = 6; } } else if ( monthIndex == 'R' ) { // // if s[1] is 'a' then March, if 'p' then April // if ( MonthIndexTable[(s[1]-0x40) & 0x3f] == 'A' ) { monthIndex = 3; } else { monthIndex = 4; } } else { goto error_exit; } verify: monthString = s_rgchMonths[monthIndex-1]; if ( (s[0] == monthString[0]) && (s[1] == monthString[1]) && (s[2] == monthString[2]) ) { return(monthIndex); } else if ( (toupper(s[0]) == monthString[0]) && (tolower(s[1]) == monthString[1]) && (tolower(s[2]) == monthString[2]) ) { return monthIndex; } error_exit: return(0); } // make_month #else int make_month( TCHAR * s ) { int i; for (i=0; i<12; i++) if (!_strnicmp(s_rgchMonths[i], s, 3)) return i + 1; return 0; } #endif BOOL SystemTimeToGMT( IN const SYSTEMTIME & st, OUT CHAR * pszBuff, IN DWORD cbBuff ) /*++ Converts the given system time to string representation containing GMT Formatted String. Arguments: st System time that needs to be converted. pstr pointer to string which will contain the GMT time on successful return. cbBuff size of pszBuff in bytes Returns: TRUE on success. FALSE on failure. History: MuraliK 3-Jan-1995 --*/ { DBG_ASSERT( pszBuff != NULL); if ( cbBuff < 40 ) { SetLastError( ERROR_INSUFFICIENT_BUFFER ); return FALSE; } // // Formats a string like: "Thu, 14 Jul 1994 15:26:05 GMT" // APPEND_PSZ( pszBuff, s_rgchDays[st.wDayOfWeek] ); // 0-based *pszBuff++ = ','; *pszBuff++ = ' '; AppendTwoDigits( pszBuff, st.wDay ); *pszBuff++ = ' '; APPEND_PSZ( pszBuff, s_rgchMonths[st.wMonth - 1] ); // 1-based *pszBuff++ = ' '; AppendYear( pszBuff, st.wYear ); *pszBuff++ = ' '; AppendTwoDigits( pszBuff, st.wHour ); *pszBuff++ = ':'; AppendTwoDigits( pszBuff, st.wMinute ); *pszBuff++ = ':'; AppendTwoDigits( pszBuff, st.wSecond ); *pszBuff++ = ' '; *pszBuff++ = 'G'; *pszBuff++ = 'M'; *pszBuff++ = 'T'; *pszBuff = '\0'; return ( TRUE); } // SystemTimeToGMT() BOOL NtLargeIntegerTimeToLocalSystemTime( IN const LARGE_INTEGER * pliTime, OUT SYSTEMTIME * pst) /*++ Converts the time returned by NTIO apis ( which is a LARGE_INTEGER) into Win32 SystemTime in Local Time zone. Arguments: pliTime pointer to large integer containing the time in NT format. pst pointer to SYSTEMTIME structure which contains the time fields on successful conversion. Returns: TRUE on success and FALSE on failure. History: MuraliK 27-Apr-1995 Limitations: This is an NT specific function !! Reason is: Win32 uses FILETIME structure for times. However LARGE_INTEGER and FILETIME both use similar structure with one difference that is one has a LONG while other has a ULONG. --*/ { FILETIME ftLocal; if ( pliTime == NULL || pst == NULL) { SetLastError( ERROR_INVALID_PARAMETER); return ( FALSE); } // // Convert the given large integer to local file time and // then convert that to SYSTEMTIME. // structure, containing the time details. // I dont like this cast ( assumes too much about time structures) // but again suitable methods are not available. // return (FileTimeToLocalFileTime((FILETIME *) pliTime, &ftLocal) && FileTimeToSystemTime(&ftLocal, pst) ); } // NtLargeIntegerTimeToLocalSystemTime() BOOL SystemTimeToGMTEx( IN const SYSTEMTIME & st, OUT CHAR * pszBuff, IN DWORD cbBuff, IN DWORD csecOffset ) /*++ Converts the given system time to string representation containing GMT Formatted String. Arguments: st System time that needs to be converted. pstr pointer to string which will contain the GMT time on successful return. cbBuff size of pszBuff in bytes csecOffset The number of seconds to offset the specified system time Returns: TRUE on success. FALSE on failure. History: MuraliK 3-Jan-1995 --*/ { SYSTEMTIME sttmp; DWORD dwSeconds = 0; ULARGE_INTEGER liTime; FILETIME ft; DBG_ASSERT( pszBuff != NULL); // // If an offset is specified, calculate that now // if (!SystemTimeToFileTime( &st, &ft )) { return(FALSE); } liTime.HighPart = ft.dwHighDateTime; liTime.LowPart = ft.dwLowDateTime; // // Nt Large integer times are stored in 100ns increments, so convert the // second offset to 100ns increments then add it // liTime.QuadPart += ((ULONGLONG) csecOffset) * (ULONGLONG) FILETIME_1_SECOND; ft.dwHighDateTime = liTime.HighPart; ft.dwLowDateTime = liTime.LowPart; FileTimeToSystemTime( &ft, &sttmp ); return SystemTimeToGMT( sttmp, pszBuff, cbBuff ); } // SystemTimeToGMTEx BOOL FileTimeToGMT( IN const FILETIME & ft, OUT CHAR * pszBuff, IN DWORD cbBuff ) /*++ Converts the given system time to string representation containing GMT Formatted String. Arguments: ft File time that needs to be converted. pstr pointer to string which will contain the GMT time on successful return. cbBuff size of pszBuff in bytes Returns: TRUE on success. FALSE on failure. --*/ { SYSTEMTIME st; if (FileTimeToSystemTime(&ft, &st)) return SystemTimeToGMT(st, pszBuff, cbBuff); else return FALSE; } BOOL FileTimeToGMTEx( IN const FILETIME & ft, OUT CHAR * pszBuff, IN DWORD cbBuff, IN DWORD csecOffset ) /*++ Converts the given system time to string representation containing GMT Formatted String. Arguments: ft File time that needs to be converted. pstr pointer to string which will contain the GMT time on successful return. cbBuff size of pszBuff in bytes csecOffset The number of seconds to offset the specified system time Returns: TRUE on success. FALSE on failure. --*/ { SYSTEMTIME sttmp; DWORD dwSeconds = 0; ULARGE_INTEGER liTime; DBG_ASSERT( pszBuff != NULL); liTime.HighPart = ft.dwHighDateTime; liTime.LowPart = ft.dwLowDateTime; // // Nt Large integer times are stored in 100ns increments, so convert the // second offset to 100ns increments then add it // liTime.QuadPart += ((ULONGLONG) csecOffset) * (ULONGLONG) FILETIME_1_SECOND; FILETIME ft2 = ft; ft2.dwHighDateTime = liTime.HighPart; ft2.dwLowDateTime = liTime.LowPart; FileTimeToSystemTime( &ft2, &sttmp ); return SystemTimeToGMT( sttmp, pszBuff, cbBuff ); } BOOL NtLargeIntegerTimeToSystemTime( IN const LARGE_INTEGER & liTime, OUT SYSTEMTIME * pst) /*++ Converts the time returned by NTIO apis ( which is a LARGE_INTEGER) into Win32 SystemTime in GMT Arguments: liTime large integer containing the time in NT format. pst pointer to SYSTEMTIME structure which contains the time fields on successful conversion. Returns: TRUE on success and FALSE on failure. History: MuraliK 3-Jan-1995 Limitations: This is an NT specific function !! Reason is: Win32 uses FILETIME structure for times. However LARGE_INTEGER and FILETIME both use similar structure with one difference that is one has a LONG while other has a ULONG. Will that make a difference ? God knows. Or substitute whatever you want for God... --*/ { FILETIME ft; if ( pst == NULL) { SetLastError( ERROR_INVALID_PARAMETER); return ( FALSE); } // // convert li to filetime // ft.dwLowDateTime = liTime.LowPart; ft.dwHighDateTime = liTime.HighPart; // // convert to system time // if (!FileTimeToSystemTime(&ft,pst)) { return(FALSE); } return ( TRUE); } // NtLargeIntegerTimeToSystemTime() BOOL NtSystemTimeToLargeInteger( IN const SYSTEMTIME * pst, OUT LARGE_INTEGER * pli ) { FILETIME ft; // // Convert to file time // if ( !SystemTimeToFileTime( pst, &ft ) ) { return(FALSE); } // // Convert file time to large integer // pli->LowPart = ft.dwLowDateTime; pli->HighPart = ft.dwHighDateTime; return(TRUE); } BOOL StringTimeToFileTime( IN const TCHAR * pszTime, OUT LARGE_INTEGER * pliTime ) /*++ Converts a string representation of a GMT time (three different varieties) to an NT representation of a file time. We handle the following variations: Sun, 06 Nov 1994 08:49:37 GMT (RFC 822 updated by RFC 1123) Sunday, 06-Nov-94 08:49:37 GMT (RFC 850) Sun Nov 6 08:49:37 1994 (ANSI C's asctime() format Arguments: pszTime String representation of time field pliTime large integer containing the time in NT format. Returns: TRUE on success and FALSE on failure. History: Johnl 24-Jan-1995 Modified from WWW library --*/ { TCHAR * s; SYSTEMTIME st; if (pszTime == NULL) { SetLastError( ERROR_INVALID_PARAMETER ); return FALSE; } st.wMilliseconds = 0; if ((s = strchr(pszTime, ','))) { DWORD len; // // Thursday, 10-Jun-93 01:29:59 GMT // or: Thu, 10 Jan 1993 01:29:59 GMT */ // s++; while (*s && *s==' ') s++; len = strlen(s); if (len < 18) { SetLastError( ERROR_INVALID_PARAMETER ); return FALSE; } if ( *(s+2) == '-' ) { /* First format */ st.wDay = (WORD) atoi(s); st.wMonth = (WORD) make_month(s+3); st.wYear = (WORD) atoi(s+7); st.wHour = (WORD) atoi(s+10); st.wMinute = (WORD) atoi(s+13); st.wSecond = (WORD) atoi(s+16); } else { /* Second format */ if (len < 20) { SetLastError( ERROR_INVALID_PARAMETER ); return FALSE; } st.wDay = iis_2atoi(s); st.wMonth = make_month(s+3); st.wYear = iis_2atoi(s+7) * 100 + iis_2atoi(s+9); st.wHour = iis_2atoi(s+12); st.wMinute = iis_2atoi(s+15); st.wSecond = iis_2atoi(s+18); } } else { /* Try the other format: Wed Jun 9 01:29:59 1993 GMT */ s = (TCHAR *) pszTime; while (*s && *s==' ') s++; if ((int)strlen(s) < 24) { SetLastError( ERROR_INVALID_PARAMETER ); return FALSE; } st.wDay = (WORD) atoi(s+8); st.wMonth = (WORD) make_month(s+4); st.wYear = (WORD) atoi(s+20); st.wHour = (WORD) atoi(s+11); st.wMinute = (WORD) atoi(s+14); st.wSecond = (WORD) atoi(s+17); } // // Adjust for dates with only two digits // if ( st.wYear < 1000 ) { if ( st.wYear < 50 ) { st.wYear += 2000; } else { st.wYear += 1900; } } if ( !NtSystemTimeToLargeInteger( &st,pliTime )) { SetLastError( ERROR_INVALID_PARAMETER ); return FALSE; } return(TRUE); } /************************************************************ * Cached Date Time Formats * * Formatting Date and Time for * HTTP headers & Logging Requests * is a costly operation. * Using default NT Formatting operations with wsprintf() * consumes about 6000 instructions/transaction * * Following code addresses this issue by * 1) Caching formatted date/time pair for all purposes * 2) Caching is done at the granularity of seconds/minute * If there is a match till seconds, we return entire * formatted information. * If there is a match till the minutes, then the seconds * portion is over-written using a seconds-lookup-table. * * Murali R. Krishnan (MuraliK) 23-Feb-1996 ************************************************************/ // // The delimiter string is : // The delimiter char should be same as the one used for LOG_RECORD // in the file: ilogcls.cxx // const TCHAR G_PSZ_LOG_DELIMITER[3] = TEXT(", "); #ifdef ENABLE_AUX_COUNTERS # define CdtCountAccesses() InterlockedIncrement( &m_nAccesses) # define CdtCountMisses() InterlockedIncrement( &m_nMisses) # else // ENABLE_AUX_COUNTERS # define CdtCountAccesses() /* do nothing */ # define CdtCountMisses() /* do nothing */ # endif // ENABLE_AUX_COUNTERS VOID DATETIME_FORMAT_ENTRY::CopyFormattedData( IN const SYSTEMTIME * pst, OUT CHAR * pchDateTime) const { // // copy the formatted date/time information // CopyMemory(pchDateTime, m_rgchDateTime, m_cbDateTime ); if ( m_stDateTime.wSecond != pst->wSecond) { // // seconds do not match. update seconds portion alone // LPSTR pch = pchDateTime + m_cchOffsetSeconds; *pch = g_rgchTwoDigits[pst->wSecond][0]; *(pch + 1) = g_rgchTwoDigits[pst->wSecond][1]; } return; } BOOL CDFTCache::CopyFormattedData( IN const SYSTEMTIME * pst, OUT CHAR * pchDateTime) const { // See for an explanation of this routine const LONG nSequence = _ReadSequence(); // Is the data being updated on another thread? if (nSequence != UPDATING) { // The weird const_cast syntax is necessitated by the volatile // attribute on m_tData (DATETIME_FORMAT_ENTRY). LPCSTR pchDate = FormattedBuffer(); DWORD cbDateTime = DateTimeChars(); // Copy the string CopyMemory(pchDateTime, pchDate, cbDateTime); if (Seconds() != pst->wSecond) { // // seconds do not match. update seconds portion alone // LPSTR pch = pchDateTime + OffsetSeconds(); *pch = g_rgchTwoDigits[pst->wSecond][0]; *(pch + 1) = g_rgchTwoDigits[pst->wSecond][1]; } // If the sequence number is unchanged, the read was valid. const LONG nSequence2 = _ReadSequence(); return (nSequence == nSequence2); } return FALSE; } VOID ASCLOG_DATETIME_CACHE::GenerateDateTimeString( IN PDFT_ENTRY pdft, IN const SYSTEMTIME * pst ) /*++ Description: This function generates the datetime formats for all predefined sequences. It attempts to generate the formatted date/time to the accuracy of a minute. If need be the seconds portion is obtained by indexing an array. This function should be called for a locked pdft entry, and the caller should make sure that the structures can be accessed freely for update Arguments: pst - pointer to system time for which the datetime format is required Returns: None --*/ { CHAR rgchTime[25]; CHAR * pchDateTime; DWORD cchLen; DBG_ASSERT( pdft != NULL && pst != NULL ); // // Format date for Logging (dftLog) // Format is: //