sl@0: /* sl@0: * tclWinTime.c -- sl@0: * sl@0: * Contains Windows specific versions of Tcl functions that sl@0: * obtain time values from the operating system. sl@0: * sl@0: * Copyright 1995-1998 by Sun Microsystems, Inc. sl@0: * sl@0: * See the file "license.terms" for information on usage and redistribution sl@0: * of this file, and for a DISCLAIMER OF ALL WARRANTIES. sl@0: * sl@0: * RCS: @(#) $Id: tclWinTime.c,v 1.14.2.11 2007/04/21 19:52:15 kennykb Exp $ sl@0: */ sl@0: sl@0: #include "tclWinInt.h" sl@0: sl@0: #define SECSPERDAY (60L * 60L * 24L) sl@0: #define SECSPERYEAR (SECSPERDAY * 365L) sl@0: #define SECSPER4YEAR (SECSPERYEAR * 4L + SECSPERDAY) sl@0: sl@0: /* sl@0: * Number of samples over which to estimate the performance counter sl@0: */ sl@0: #define SAMPLES 64 sl@0: sl@0: /* sl@0: * The following arrays contain the day of year for the last day of sl@0: * each month, where index 1 is January. sl@0: */ sl@0: sl@0: static int normalDays[] = { sl@0: -1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364 sl@0: }; sl@0: sl@0: static int leapDays[] = { sl@0: -1, 30, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 sl@0: }; sl@0: sl@0: typedef struct ThreadSpecificData { sl@0: char tzName[64]; /* Time zone name */ sl@0: struct tm tm; /* time information */ sl@0: } ThreadSpecificData; sl@0: static Tcl_ThreadDataKey dataKey; sl@0: sl@0: /* sl@0: * Data for managing high-resolution timers. sl@0: */ sl@0: sl@0: typedef struct TimeInfo { sl@0: sl@0: CRITICAL_SECTION cs; /* Mutex guarding this structure */ sl@0: sl@0: int initialized; /* Flag == 1 if this structure is sl@0: * initialized. */ sl@0: sl@0: int perfCounterAvailable; /* Flag == 1 if the hardware has a sl@0: * performance counter */ sl@0: sl@0: HANDLE calibrationThread; /* Handle to the thread that keeps the sl@0: * virtual clock calibrated. */ sl@0: sl@0: HANDLE readyEvent; /* System event used to sl@0: * trigger the requesting thread sl@0: * when the clock calibration procedure sl@0: * is initialized for the first time */ sl@0: sl@0: HANDLE exitEvent; /* Event to signal out of an exit handler sl@0: * to tell the calibration loop to sl@0: * terminate */ sl@0: sl@0: LARGE_INTEGER nominalFreq; /* Nominal frequency of the system sl@0: * performance counter, that is, the value sl@0: * returned from QueryPerformanceFrequency. */ sl@0: sl@0: /* sl@0: * The following values are used for calculating virtual time. sl@0: * Virtual time is always equal to: sl@0: * lastFileTime + (current perf counter - lastCounter) sl@0: * * 10000000 / curCounterFreq sl@0: * and lastFileTime and lastCounter are updated any time that sl@0: * virtual time is returned to a caller. sl@0: */ sl@0: sl@0: ULARGE_INTEGER fileTimeLastCall; sl@0: LARGE_INTEGER perfCounterLastCall; sl@0: LARGE_INTEGER curCounterFreq; sl@0: sl@0: /* sl@0: * Data used in developing the estimate of performance counter sl@0: * frequency sl@0: */ sl@0: Tcl_WideUInt fileTimeSample[SAMPLES]; sl@0: /* Last 64 samples of system time */ sl@0: Tcl_WideInt perfCounterSample[SAMPLES]; sl@0: /* Last 64 samples of performance counter */ sl@0: int sampleNo; /* Current sample number */ sl@0: sl@0: sl@0: } TimeInfo; sl@0: sl@0: static TimeInfo timeInfo = { sl@0: { NULL }, sl@0: 0, sl@0: 0, sl@0: (HANDLE) NULL, sl@0: (HANDLE) NULL, sl@0: (HANDLE) NULL, sl@0: #ifdef HAVE_CAST_TO_UNION sl@0: (LARGE_INTEGER) (Tcl_WideInt) 0, sl@0: (ULARGE_INTEGER) (DWORDLONG) 0, sl@0: (LARGE_INTEGER) (Tcl_WideInt) 0, sl@0: (LARGE_INTEGER) (Tcl_WideInt) 0, sl@0: #else sl@0: 0, sl@0: 0, sl@0: 0, sl@0: 0, sl@0: #endif sl@0: { 0 }, sl@0: { 0 }, sl@0: 0 sl@0: }; sl@0: sl@0: CONST static FILETIME posixEpoch = { 0xD53E8000, 0x019DB1DE }; sl@0: sl@0: /* sl@0: * Declarations for functions defined later in this file. sl@0: */ sl@0: sl@0: static struct tm * ComputeGMT _ANSI_ARGS_((const time_t *tp)); sl@0: static void StopCalibration _ANSI_ARGS_(( ClientData )); sl@0: static DWORD WINAPI CalibrationThread _ANSI_ARGS_(( LPVOID arg )); sl@0: static void UpdateTimeEachSecond _ANSI_ARGS_(( void )); sl@0: static void ResetCounterSamples _ANSI_ARGS_(( sl@0: Tcl_WideUInt fileTime, sl@0: Tcl_WideInt perfCounter, sl@0: Tcl_WideInt perfFreq sl@0: )); sl@0: static Tcl_WideInt AccumulateSample _ANSI_ARGS_(( sl@0: Tcl_WideInt perfCounter, sl@0: Tcl_WideUInt fileTime sl@0: )); sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpGetSeconds -- sl@0: * sl@0: * This procedure returns the number of seconds from the epoch. sl@0: * On most Unix systems the epoch is Midnight Jan 1, 1970 GMT. sl@0: * sl@0: * Results: sl@0: * Number of seconds from the epoch. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: unsigned long sl@0: TclpGetSeconds() sl@0: { sl@0: Tcl_Time t; sl@0: Tcl_GetTime( &t ); sl@0: return t.sec; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpGetClicks -- sl@0: * sl@0: * This procedure returns a value that represents the highest sl@0: * resolution clock available on the system. There are no sl@0: * guarantees on what the resolution will be. In Tcl we will sl@0: * call this value a "click". The start time is also system sl@0: * dependant. sl@0: * sl@0: * Results: sl@0: * Number of clicks from some start time. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: unsigned long sl@0: TclpGetClicks() sl@0: { sl@0: /* sl@0: * Use the Tcl_GetTime abstraction to get the time in microseconds, sl@0: * as nearly as we can, and return it. sl@0: */ sl@0: sl@0: Tcl_Time now; /* Current Tcl time */ sl@0: unsigned long retval; /* Value to return */ sl@0: sl@0: Tcl_GetTime( &now ); sl@0: retval = ( now.sec * 1000000 ) + now.usec; sl@0: return retval; sl@0: sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpGetTimeZone -- sl@0: * sl@0: * Determines the current timezone. The method varies wildly sl@0: * between different Platform implementations, so its hidden in sl@0: * this function. sl@0: * sl@0: * Results: sl@0: * Minutes west of GMT. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: int sl@0: TclpGetTimeZone (currentTime) sl@0: Tcl_WideInt currentTime; sl@0: { sl@0: int timeZone; sl@0: sl@0: tzset(); sl@0: timeZone = _timezone / 60; sl@0: sl@0: return timeZone; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_GetTime -- sl@0: * sl@0: * Gets the current system time in seconds and microseconds sl@0: * since the beginning of the epoch: 00:00 UCT, January 1, 1970. sl@0: * sl@0: * Results: sl@0: * Returns the current time in timePtr. sl@0: * sl@0: * Side effects: sl@0: * On the first call, initializes a set of static variables to sl@0: * keep track of the base value of the performance counter, the sl@0: * corresponding wall clock (obtained through ftime) and the sl@0: * frequency of the performance counter. Also spins a thread sl@0: * whose function is to wake up periodically and monitor these sl@0: * values, adjusting them as necessary to correct for drift sl@0: * in the performance counter's oscillator. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: void sl@0: Tcl_GetTime(timePtr) sl@0: Tcl_Time *timePtr; /* Location to store time information. */ sl@0: { sl@0: struct timeb t; sl@0: sl@0: int useFtime = 1; /* Flag == TRUE if we need to fall back sl@0: * on ftime rather than using the perf sl@0: * counter */ sl@0: sl@0: /* Initialize static storage on the first trip through. */ sl@0: sl@0: /* sl@0: * Note: Outer check for 'initialized' is a performance win sl@0: * since it avoids an extra mutex lock in the common case. sl@0: */ sl@0: sl@0: if ( !timeInfo.initialized ) { sl@0: TclpInitLock(); sl@0: if ( !timeInfo.initialized ) { sl@0: timeInfo.perfCounterAvailable sl@0: = QueryPerformanceFrequency( &timeInfo.nominalFreq ); sl@0: sl@0: /* sl@0: * Some hardware abstraction layers use the CPU clock sl@0: * in place of the real-time clock as a performance counter sl@0: * reference. This results in: sl@0: * - inconsistent results among the processors on sl@0: * multi-processor systems. sl@0: * - unpredictable changes in performance counter frequency sl@0: * on "gearshift" processors such as Transmeta and sl@0: * SpeedStep. sl@0: * sl@0: * There seems to be no way to test whether the performance sl@0: * counter is reliable, but a useful heuristic is that sl@0: * if its frequency is 1.193182 MHz or 3.579545 MHz, it's sl@0: * derived from a colorburst crystal and is therefore sl@0: * the RTC rather than the TSC. sl@0: * sl@0: * A sloppier but serviceable heuristic is that the RTC crystal sl@0: * is normally less than 15 MHz while the TSC crystal is sl@0: * virtually assured to be greater than 100 MHz. Since Win98SE sl@0: * appears to fiddle with the definition of the perf counter sl@0: * frequency (perhaps in an attempt to calibrate the clock?) sl@0: * we use the latter rule rather than an exact match. sl@0: */ sl@0: sl@0: if ( timeInfo.perfCounterAvailable sl@0: /* The following lines would do an exact match on sl@0: * crystal frequency: sl@0: * && timeInfo.nominalFreq.QuadPart != (Tcl_WideInt) 1193182 sl@0: * && timeInfo.nominalFreq.QuadPart != (Tcl_WideInt) 3579545 sl@0: */ sl@0: && timeInfo.nominalFreq.QuadPart > (Tcl_WideInt) 15000000 ) { sl@0: sl@0: /* sl@0: * As an exception, if every logical processor on the system sl@0: * is on the same chip, we use the performance counter anyway, sl@0: * presuming that everyone's TSC is locked to the same sl@0: * oscillator. sl@0: */ sl@0: sl@0: SYSTEM_INFO systemInfo; sl@0: unsigned int regs[4]; sl@0: GetSystemInfo( &systemInfo ); sl@0: if ( TclWinCPUID( 0, regs ) == TCL_OK sl@0: sl@0: && regs[1] == 0x756e6547 /* "Genu" */ sl@0: && regs[3] == 0x49656e69 /* "ineI" */ sl@0: && regs[2] == 0x6c65746e /* "ntel" */ sl@0: sl@0: && TclWinCPUID( 1, regs ) == TCL_OK sl@0: sl@0: && ( (regs[0] & 0x00000F00) == 0x00000F00 /* Pentium 4 */ sl@0: || ( (regs[0] & 0x00F00000) /* Extended family */ sl@0: && (regs[3] & 0x10000000) ) ) /* Hyperthread */ sl@0: && ( ( ( regs[1] & 0x00FF0000 ) >> 16 ) /* CPU count */ sl@0: == systemInfo.dwNumberOfProcessors ) sl@0: sl@0: ) { sl@0: timeInfo.perfCounterAvailable = TRUE; sl@0: } else { sl@0: timeInfo.perfCounterAvailable = FALSE; sl@0: } sl@0: sl@0: } sl@0: sl@0: /* sl@0: * If the performance counter is available, start a thread to sl@0: * calibrate it. sl@0: */ sl@0: sl@0: if ( timeInfo.perfCounterAvailable ) { sl@0: DWORD id; sl@0: InitializeCriticalSection( &timeInfo.cs ); sl@0: timeInfo.readyEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); sl@0: timeInfo.exitEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); sl@0: timeInfo.calibrationThread = CreateThread( NULL, sl@0: 256, sl@0: CalibrationThread, sl@0: (LPVOID) NULL, sl@0: 0, sl@0: &id ); sl@0: SetThreadPriority( timeInfo.calibrationThread, sl@0: THREAD_PRIORITY_HIGHEST ); sl@0: sl@0: /* sl@0: * Wait for the thread just launched to start running, sl@0: * and create an exit handler that kills it so that it sl@0: * doesn't outlive unloading tclXX.dll sl@0: */ sl@0: sl@0: WaitForSingleObject( timeInfo.readyEvent, INFINITE ); sl@0: CloseHandle( timeInfo.readyEvent ); sl@0: Tcl_CreateExitHandler( StopCalibration, (ClientData) NULL ); sl@0: } sl@0: timeInfo.initialized = TRUE; sl@0: } sl@0: TclpInitUnlock(); sl@0: } sl@0: sl@0: if ( timeInfo.perfCounterAvailable ) { sl@0: /* sl@0: * Query the performance counter and use it to calculate the sl@0: * current time. sl@0: */ sl@0: sl@0: LARGE_INTEGER curCounter; sl@0: /* Current performance counter */ sl@0: sl@0: Tcl_WideInt curFileTime; sl@0: /* Current estimated time, expressed sl@0: * as 100-ns ticks since the Windows epoch */ sl@0: sl@0: static LARGE_INTEGER posixEpoch; sl@0: /* Posix epoch expressed as 100-ns ticks sl@0: * since the windows epoch */ sl@0: sl@0: Tcl_WideInt usecSincePosixEpoch; sl@0: /* Current microseconds since Posix epoch */ sl@0: sl@0: posixEpoch.LowPart = 0xD53E8000; sl@0: posixEpoch.HighPart = 0x019DB1DE; sl@0: sl@0: EnterCriticalSection( &timeInfo.cs ); sl@0: sl@0: QueryPerformanceCounter( &curCounter ); sl@0: sl@0: /* sl@0: * If it appears to be more than 1.1 seconds since the last trip sl@0: * through the calibration loop, the performance counter may sl@0: * have jumped forward. (See MSDN Knowledge Base article sl@0: * Q274323 for a description of the hardware problem that makes sl@0: * this test necessary.) If the counter jumps, we don't want sl@0: * to use it directly. Instead, we must return system time. sl@0: * Eventually, the calibration loop should recover. sl@0: */ sl@0: if ( curCounter.QuadPart - timeInfo.perfCounterLastCall.QuadPart sl@0: < 11 * timeInfo.curCounterFreq.QuadPart / 10 ) { sl@0: sl@0: curFileTime = timeInfo.fileTimeLastCall.QuadPart sl@0: + ( ( curCounter.QuadPart - timeInfo.perfCounterLastCall.QuadPart ) sl@0: * 10000000 / timeInfo.curCounterFreq.QuadPart ); sl@0: timeInfo.fileTimeLastCall.QuadPart = curFileTime; sl@0: timeInfo.perfCounterLastCall.QuadPart = curCounter.QuadPart; sl@0: usecSincePosixEpoch = ( curFileTime - posixEpoch.QuadPart ) / 10; sl@0: timePtr->sec = (long) ( usecSincePosixEpoch / 1000000 ); sl@0: timePtr->usec = (unsigned long ) ( usecSincePosixEpoch % 1000000 ); sl@0: useFtime = 0; sl@0: } sl@0: sl@0: LeaveCriticalSection( &timeInfo.cs ); sl@0: } sl@0: sl@0: if ( useFtime ) { sl@0: /* High resolution timer is not available. Just use ftime */ sl@0: sl@0: ftime(&t); sl@0: timePtr->sec = (long)t.time; sl@0: timePtr->usec = t.millitm * 1000; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * StopCalibration -- sl@0: * sl@0: * Turns off the calibration thread in preparation for exiting the sl@0: * process. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Sets the 'exitEvent' event in the 'timeInfo' structure to ask sl@0: * the thread in question to exit, and waits for it to do so. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: StopCalibration( ClientData unused ) sl@0: /* Client data is unused */ sl@0: { sl@0: SetEvent( timeInfo.exitEvent ); sl@0: WaitForSingleObject( timeInfo.calibrationThread, INFINITE ); sl@0: CloseHandle( timeInfo.exitEvent ); sl@0: CloseHandle( timeInfo.calibrationThread ); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpGetTZName -- sl@0: * sl@0: * Gets the current timezone string. sl@0: * sl@0: * Results: sl@0: * Returns a pointer to a static string, or NULL on failure. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: char * sl@0: TclpGetTZName(int dst) sl@0: { sl@0: size_t len; sl@0: char *zone, *p; sl@0: TIME_ZONE_INFORMATION tz; sl@0: Tcl_Encoding encoding; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: char *name = tsdPtr->tzName; sl@0: sl@0: /* sl@0: * tzset() under Borland doesn't seem to set up tzname[] at all. sl@0: * tzset() under MSVC has the following weird observed behavior: sl@0: * First time we call "clock format [clock seconds] -format %Z -gmt 1" sl@0: * we get "GMT", but on all subsequent calls we get the current time sl@0: * zone string, even though env(TZ) is GMT and the variable _timezone sl@0: * is 0. sl@0: */ sl@0: sl@0: name[0] = '\0'; sl@0: sl@0: zone = getenv("TZ"); sl@0: if (zone != NULL) { sl@0: /* sl@0: * TZ is of form "NST-4:30NDT", where "NST" would be the sl@0: * name of the standard time zone for this area, "-4:30" is sl@0: * the offset from GMT in hours, and "NDT is the name of sl@0: * the daylight savings time zone in this area. The offset sl@0: * and DST strings are optional. sl@0: */ sl@0: sl@0: len = strlen(zone); sl@0: if (len > 3) { sl@0: len = 3; sl@0: } sl@0: if (dst != 0) { sl@0: /* sl@0: * Skip the offset string and get the DST string. sl@0: */ sl@0: sl@0: p = zone + len; sl@0: p += strspn(p, "+-:0123456789"); sl@0: if (*p != '\0') { sl@0: zone = p; sl@0: len = strlen(zone); sl@0: if (len > 3) { sl@0: len = 3; sl@0: } sl@0: } sl@0: } sl@0: Tcl_ExternalToUtf(NULL, NULL, zone, (int)len, 0, NULL, name, sl@0: sizeof(tsdPtr->tzName), NULL, NULL, NULL); sl@0: } sl@0: if (name[0] == '\0') { sl@0: if (GetTimeZoneInformation(&tz) == TIME_ZONE_ID_UNKNOWN) { sl@0: /* sl@0: * MSDN: On NT this is returned if DST is not used in sl@0: * the current TZ sl@0: */ sl@0: dst = 0; sl@0: } sl@0: encoding = Tcl_GetEncoding(NULL, "unicode"); sl@0: Tcl_ExternalToUtf(NULL, encoding, sl@0: (char *) ((dst) ? tz.DaylightName : tz.StandardName), -1, sl@0: 0, NULL, name, sizeof(tsdPtr->tzName), NULL, NULL, NULL); sl@0: Tcl_FreeEncoding(encoding); sl@0: } sl@0: return name; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpGetDate -- sl@0: * sl@0: * This function converts between seconds and struct tm. If sl@0: * useGMT is true, then the returned date will be in Greenwich sl@0: * Mean Time (GMT). Otherwise, it will be in the local time zone. sl@0: * sl@0: * Results: sl@0: * Returns a static tm structure. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: struct tm * sl@0: TclpGetDate(t, useGMT) sl@0: TclpTime_t t; sl@0: int useGMT; sl@0: { sl@0: const time_t *tp = (const time_t *) t; sl@0: struct tm *tmPtr; sl@0: time_t time; sl@0: sl@0: if (!useGMT) { sl@0: tzset(); sl@0: sl@0: /* sl@0: * If we are in the valid range, let the C run-time library sl@0: * handle it. Otherwise we need to fake it. Note that this sl@0: * algorithm ignores daylight savings time before the epoch. sl@0: */ sl@0: sl@0: if (*tp >= 0) { sl@0: return localtime(tp); sl@0: } sl@0: sl@0: time = *tp - _timezone; sl@0: sl@0: /* sl@0: * If we aren't near to overflowing the long, just add the bias and sl@0: * use the normal calculation. Otherwise we will need to adjust sl@0: * the result at the end. sl@0: */ sl@0: sl@0: if (*tp < (LONG_MAX - 2 * SECSPERDAY) sl@0: && *tp > (LONG_MIN + 2 * SECSPERDAY)) { sl@0: tmPtr = ComputeGMT(&time); sl@0: } else { sl@0: tmPtr = ComputeGMT(tp); sl@0: sl@0: tzset(); sl@0: sl@0: /* sl@0: * Add the bias directly to the tm structure to avoid overflow. sl@0: * Propagate seconds overflow into minutes, hours and days. sl@0: */ sl@0: sl@0: time = tmPtr->tm_sec - _timezone; sl@0: tmPtr->tm_sec = (int)(time % 60); sl@0: if (tmPtr->tm_sec < 0) { sl@0: tmPtr->tm_sec += 60; sl@0: time -= 60; sl@0: } sl@0: sl@0: time = tmPtr->tm_min + time/60; sl@0: tmPtr->tm_min = (int)(time % 60); sl@0: if (tmPtr->tm_min < 0) { sl@0: tmPtr->tm_min += 60; sl@0: time -= 60; sl@0: } sl@0: sl@0: time = tmPtr->tm_hour + time/60; sl@0: tmPtr->tm_hour = (int)(time % 24); sl@0: if (tmPtr->tm_hour < 0) { sl@0: tmPtr->tm_hour += 24; sl@0: time -= 24; sl@0: } sl@0: sl@0: time /= 24; sl@0: tmPtr->tm_mday += (int)time; sl@0: tmPtr->tm_yday += (int)time; sl@0: tmPtr->tm_wday = (tmPtr->tm_wday + (int)time) % 7; sl@0: } sl@0: } else { sl@0: tmPtr = ComputeGMT(tp); sl@0: } sl@0: return tmPtr; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ComputeGMT -- sl@0: * sl@0: * This function computes GMT given the number of seconds since sl@0: * the epoch (midnight Jan 1 1970). sl@0: * sl@0: * Results: sl@0: * Returns a (per thread) statically allocated struct tm. sl@0: * sl@0: * Side effects: sl@0: * Updates the values of the static struct tm. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static struct tm * sl@0: ComputeGMT(tp) sl@0: const time_t *tp; sl@0: { sl@0: struct tm *tmPtr; sl@0: long tmp, rem; sl@0: int isLeap; sl@0: int *days; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: tmPtr = &tsdPtr->tm; sl@0: sl@0: /* sl@0: * Compute the 4 year span containing the specified time. sl@0: */ sl@0: sl@0: tmp = (long)(*tp / SECSPER4YEAR); sl@0: rem = (LONG)(*tp % SECSPER4YEAR); sl@0: sl@0: /* sl@0: * Correct for weird mod semantics so the remainder is always positive. sl@0: */ sl@0: sl@0: if (rem < 0) { sl@0: tmp--; sl@0: rem += SECSPER4YEAR; sl@0: } sl@0: sl@0: /* sl@0: * Compute the year after 1900 by taking the 4 year span and adjusting sl@0: * for the remainder. This works because 2000 is a leap year, and sl@0: * 1900/2100 are out of the range. sl@0: */ sl@0: sl@0: tmp = (tmp * 4) + 70; sl@0: isLeap = 0; sl@0: if (rem >= SECSPERYEAR) { /* 1971, etc. */ sl@0: tmp++; sl@0: rem -= SECSPERYEAR; sl@0: if (rem >= SECSPERYEAR) { /* 1972, etc. */ sl@0: tmp++; sl@0: rem -= SECSPERYEAR; sl@0: if (rem >= SECSPERYEAR + SECSPERDAY) { /* 1973, etc. */ sl@0: tmp++; sl@0: rem -= SECSPERYEAR + SECSPERDAY; sl@0: } else { sl@0: isLeap = 1; sl@0: } sl@0: } sl@0: } sl@0: tmPtr->tm_year = tmp; sl@0: sl@0: /* sl@0: * Compute the day of year and leave the seconds in the current day in sl@0: * the remainder. sl@0: */ sl@0: sl@0: tmPtr->tm_yday = rem / SECSPERDAY; sl@0: rem %= SECSPERDAY; sl@0: sl@0: /* sl@0: * Compute the time of day. sl@0: */ sl@0: sl@0: tmPtr->tm_hour = rem / 3600; sl@0: rem %= 3600; sl@0: tmPtr->tm_min = rem / 60; sl@0: tmPtr->tm_sec = rem % 60; sl@0: sl@0: /* sl@0: * Compute the month and day of month. sl@0: */ sl@0: sl@0: days = (isLeap) ? leapDays : normalDays; sl@0: for (tmp = 1; days[tmp] < tmPtr->tm_yday; tmp++) { sl@0: } sl@0: tmPtr->tm_mon = --tmp; sl@0: tmPtr->tm_mday = tmPtr->tm_yday - days[tmp]; sl@0: sl@0: /* sl@0: * Compute day of week. Epoch started on a Thursday. sl@0: */ sl@0: sl@0: tmPtr->tm_wday = (long)(*tp / SECSPERDAY) + 4; sl@0: if ((*tp % SECSPERDAY) < 0) { sl@0: tmPtr->tm_wday--; sl@0: } sl@0: tmPtr->tm_wday %= 7; sl@0: if (tmPtr->tm_wday < 0) { sl@0: tmPtr->tm_wday += 7; sl@0: } sl@0: sl@0: return tmPtr; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * CalibrationThread -- sl@0: * sl@0: * Thread that manages calibration of the hi-resolution time sl@0: * derived from the performance counter, to keep it synchronized sl@0: * with the system clock. sl@0: * sl@0: * Parameters: sl@0: * arg -- Client data from the CreateThread call. This parameter sl@0: * points to the static TimeInfo structure. sl@0: * sl@0: * Return value: sl@0: * None. This thread embeds an infinite loop. sl@0: * sl@0: * Side effects: sl@0: * At an interval of 1 s, this thread performs virtual time discipline. sl@0: * sl@0: * Note: When this thread is entered, TclpInitLock has been called sl@0: * to safeguard the static storage. There is therefore no synchronization sl@0: * in the body of this procedure. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static DWORD WINAPI sl@0: CalibrationThread( LPVOID arg ) sl@0: { sl@0: FILETIME curFileTime; sl@0: DWORD waitResult; sl@0: sl@0: /* Get initial system time and performance counter */ sl@0: sl@0: GetSystemTimeAsFileTime( &curFileTime ); sl@0: QueryPerformanceCounter( &timeInfo.perfCounterLastCall ); sl@0: QueryPerformanceFrequency( &timeInfo.curCounterFreq ); sl@0: timeInfo.fileTimeLastCall.LowPart = curFileTime.dwLowDateTime; sl@0: timeInfo.fileTimeLastCall.HighPart = curFileTime.dwHighDateTime; sl@0: sl@0: ResetCounterSamples( timeInfo.fileTimeLastCall.QuadPart, sl@0: timeInfo.perfCounterLastCall.QuadPart, sl@0: timeInfo.curCounterFreq.QuadPart ); sl@0: sl@0: /* sl@0: * Wake up the calling thread. When it wakes up, it will release the sl@0: * initialization lock. sl@0: */ sl@0: sl@0: SetEvent( timeInfo.readyEvent ); sl@0: sl@0: /* Run the calibration once a second */ sl@0: sl@0: for ( ; ; ) { sl@0: sl@0: /* If the exitEvent is set, break out of the loop. */ sl@0: sl@0: waitResult = WaitForSingleObjectEx(timeInfo.exitEvent, 1000, FALSE); sl@0: if ( waitResult == WAIT_OBJECT_0 ) { sl@0: break; sl@0: } sl@0: UpdateTimeEachSecond(); sl@0: } sl@0: sl@0: /* lint */ sl@0: return (DWORD) 0; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * UpdateTimeEachSecond -- sl@0: * sl@0: * Callback from the waitable timer in the clock calibration thread sl@0: * that updates system time. sl@0: * sl@0: * Parameters: sl@0: * info -- Pointer to the static TimeInfo structure sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Performs virtual time calibration discipline. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: UpdateTimeEachSecond() sl@0: { sl@0: sl@0: LARGE_INTEGER curPerfCounter; sl@0: /* Current value returned from sl@0: * QueryPerformanceCounter */ sl@0: sl@0: FILETIME curSysTime; /* Current system time */ sl@0: sl@0: LARGE_INTEGER curFileTime; /* File time at the time this callback sl@0: * was scheduled. */ sl@0: sl@0: Tcl_WideInt estFreq; /* Estimated perf counter frequency */ sl@0: sl@0: Tcl_WideInt vt0; /* Tcl time right now */ sl@0: Tcl_WideInt vt1; /* Tcl time one second from now */ sl@0: sl@0: Tcl_WideInt tdiff; /* Difference between system clock and sl@0: * Tcl time. */ sl@0: sl@0: Tcl_WideInt driftFreq; /* Frequency needed to drift virtual time sl@0: * into step over 1 second */ sl@0: sl@0: /* sl@0: * Sample performance counter and system time. sl@0: */ sl@0: sl@0: QueryPerformanceCounter( &curPerfCounter ); sl@0: GetSystemTimeAsFileTime( &curSysTime ); sl@0: curFileTime.LowPart = curSysTime.dwLowDateTime; sl@0: curFileTime.HighPart = curSysTime.dwHighDateTime; sl@0: sl@0: EnterCriticalSection( &timeInfo.cs ); sl@0: sl@0: /* sl@0: * Several things may have gone wrong here that have to sl@0: * be checked for. sl@0: * (1) The performance counter may have jumped. sl@0: * (2) The system clock may have been reset. sl@0: * sl@0: * In either case, we'll need to reinitialize the circular buffer sl@0: * with samples relative to the current system time and the NOMINAL sl@0: * performance frequency (not the actual, because the actual has sl@0: * probably run slow in the first case). Our estimated frequency sl@0: * will be the nominal frequency. sl@0: */ sl@0: sl@0: /* sl@0: * Store the current sample into the circular buffer of samples, sl@0: * and estimate the performance counter frequency. sl@0: */ sl@0: sl@0: estFreq = AccumulateSample( curPerfCounter.QuadPart, sl@0: (Tcl_WideUInt) curFileTime.QuadPart ); sl@0: sl@0: /* sl@0: * We want to adjust things so that time appears to be continuous. sl@0: * Virtual file time, right now, is sl@0: * sl@0: * vt0 = 10000000 * ( curPerfCounter - perfCounterLastCall ) sl@0: * / curCounterFreq sl@0: * + fileTimeLastCall sl@0: * sl@0: * Ideally, we would like to drift the clock into place over a sl@0: * period of 2 sec, so that virtual time 2 sec from now will be sl@0: * sl@0: * vt1 = 20000000 + curFileTime sl@0: * sl@0: * The frequency that we need to use to drift the counter back into sl@0: * place is estFreq * 20000000 / ( vt1 - vt0 ) sl@0: */ sl@0: sl@0: vt0 = 10000000 * ( curPerfCounter.QuadPart sl@0: - timeInfo.perfCounterLastCall.QuadPart ) sl@0: / timeInfo.curCounterFreq.QuadPart sl@0: + timeInfo.fileTimeLastCall.QuadPart; sl@0: vt1 = 20000000 + curFileTime.QuadPart; sl@0: sl@0: /* sl@0: * If we've gotten more than a second away from system time, sl@0: * then drifting the clock is going to be pretty hopeless. sl@0: * Just let it jump. Otherwise, compute the drift frequency and sl@0: * fill in everything. sl@0: */ sl@0: sl@0: tdiff = vt0 - curFileTime.QuadPart; sl@0: if ( tdiff > 10000000 || tdiff < -10000000 ) { sl@0: timeInfo.fileTimeLastCall.QuadPart = curFileTime.QuadPart; sl@0: timeInfo.curCounterFreq.QuadPart = estFreq; sl@0: } else { sl@0: driftFreq = estFreq * 20000000 / ( vt1 - vt0 ); sl@0: if ( driftFreq > 1003 * estFreq / 1000 ) { sl@0: driftFreq = 1003 * estFreq / 1000; sl@0: } sl@0: if ( driftFreq < 997 * estFreq / 1000 ) { sl@0: driftFreq = 997 * estFreq / 1000; sl@0: } sl@0: timeInfo.fileTimeLastCall.QuadPart = vt0; sl@0: timeInfo.curCounterFreq.QuadPart = driftFreq; sl@0: } sl@0: sl@0: timeInfo.perfCounterLastCall.QuadPart = curPerfCounter.QuadPart; sl@0: sl@0: LeaveCriticalSection( &timeInfo.cs ); sl@0: sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ResetCounterSamples -- sl@0: * sl@0: * Fills the sample arrays in 'timeInfo' with dummy values that will sl@0: * yield the current performance counter and frequency. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * The array of samples is filled in so that it appears that there sl@0: * are SAMPLES samples at one-second intervals, separated by precisely sl@0: * the given frequency. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: ResetCounterSamples( Tcl_WideUInt fileTime, sl@0: /* Current file time */ sl@0: Tcl_WideInt perfCounter, sl@0: /* Current performance counter */ sl@0: Tcl_WideInt perfFreq ) sl@0: /* Target performance frequency */ sl@0: { sl@0: int i; sl@0: for ( i = SAMPLES-1; i >= 0; --i ) { sl@0: timeInfo.perfCounterSample[i] = perfCounter; sl@0: timeInfo.fileTimeSample[i] = fileTime; sl@0: perfCounter -= perfFreq; sl@0: fileTime -= 10000000; sl@0: } sl@0: timeInfo.sampleNo = 0; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * AccumulateSample -- sl@0: * sl@0: * Updates the circular buffer of performance counter and system sl@0: * time samples with a new data point. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * The new data point replaces the oldest point in the circular sl@0: * buffer, and the descriptive statistics are updated to accumulate sl@0: * the new point. sl@0: * sl@0: * Several things may have gone wrong here that have to sl@0: * be checked for. sl@0: * (1) The performance counter may have jumped. sl@0: * (2) The system clock may have been reset. sl@0: * sl@0: * In either case, we'll need to reinitialize the circular buffer sl@0: * with samples relative to the current system time and the NOMINAL sl@0: * performance frequency (not the actual, because the actual has sl@0: * probably run slow in the first case). sl@0: */ sl@0: sl@0: static Tcl_WideInt sl@0: AccumulateSample( Tcl_WideInt perfCounter, sl@0: Tcl_WideUInt fileTime ) sl@0: { sl@0: Tcl_WideUInt workFTSample; /* File time sample being removed sl@0: * from or added to the circular buffer */ sl@0: sl@0: Tcl_WideInt workPCSample; /* Performance counter sample being sl@0: * removed from or added to the circular sl@0: * buffer */ sl@0: sl@0: Tcl_WideUInt lastFTSample; /* Last file time sample recorded */ sl@0: sl@0: Tcl_WideInt lastPCSample; /* Last performance counter sample recorded */ sl@0: sl@0: Tcl_WideInt FTdiff; /* Difference between last FT and current */ sl@0: sl@0: Tcl_WideInt PCdiff; /* Difference between last PC and current */ sl@0: sl@0: Tcl_WideInt estFreq; /* Estimated performance counter frequency */ sl@0: sl@0: /* Test for jumps and reset the samples if we have one. */ sl@0: sl@0: if ( timeInfo.sampleNo == 0 ) { sl@0: lastPCSample = timeInfo.perfCounterSample[ timeInfo.sampleNo sl@0: + SAMPLES - 1 ]; sl@0: lastFTSample = timeInfo.fileTimeSample[ timeInfo.sampleNo sl@0: + SAMPLES - 1 ]; sl@0: } else { sl@0: lastPCSample = timeInfo.perfCounterSample[ timeInfo.sampleNo - 1 ]; sl@0: lastFTSample = timeInfo.fileTimeSample[ timeInfo.sampleNo - 1 ]; sl@0: } sl@0: PCdiff = perfCounter - lastPCSample; sl@0: FTdiff = fileTime - lastFTSample; sl@0: if ( PCdiff < timeInfo.nominalFreq.QuadPart * 9 / 10 sl@0: || PCdiff > timeInfo.nominalFreq.QuadPart * 11 / 10 sl@0: || FTdiff < 9000000 sl@0: || FTdiff > 11000000 ) { sl@0: ResetCounterSamples( fileTime, perfCounter, sl@0: timeInfo.nominalFreq.QuadPart ); sl@0: return timeInfo.nominalFreq.QuadPart; sl@0: sl@0: } else { sl@0: sl@0: /* Estimate the frequency */ sl@0: sl@0: workPCSample = timeInfo.perfCounterSample[ timeInfo.sampleNo ]; sl@0: workFTSample = timeInfo.fileTimeSample[ timeInfo.sampleNo ]; sl@0: estFreq = 10000000 * ( perfCounter - workPCSample ) sl@0: / ( fileTime - workFTSample ); sl@0: timeInfo.perfCounterSample[ timeInfo.sampleNo ] = perfCounter; sl@0: timeInfo.fileTimeSample[ timeInfo.sampleNo ] = (Tcl_WideInt) fileTime; sl@0: sl@0: /* Advance the sample number */ sl@0: sl@0: if ( ++timeInfo.sampleNo >= SAMPLES ) { sl@0: timeInfo.sampleNo = 0; sl@0: } sl@0: sl@0: return estFreq; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpGmtime -- sl@0: * sl@0: * Wrapper around the 'gmtime' library function to make it thread sl@0: * safe. sl@0: * sl@0: * Results: sl@0: * Returns a pointer to a 'struct tm' in thread-specific data. sl@0: * sl@0: * Side effects: sl@0: * Invokes gmtime or gmtime_r as appropriate. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: struct tm * sl@0: TclpGmtime( tt ) sl@0: TclpTime_t_CONST tt; sl@0: { sl@0: CONST time_t *timePtr = (CONST time_t *) tt; sl@0: /* Pointer to the number of seconds sl@0: * since the local system's epoch */ sl@0: /* sl@0: * The MS implementation of gmtime is thread safe because sl@0: * it returns the time in a block of thread-local storage, sl@0: * and Windows does not provide a Posix gmtime_r function. sl@0: */ sl@0: return gmtime( timePtr ); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpLocaltime -- sl@0: * sl@0: * Wrapper around the 'localtime' library function to make it thread sl@0: * safe. sl@0: * sl@0: * Results: sl@0: * Returns a pointer to a 'struct tm' in thread-specific data. sl@0: * sl@0: * Side effects: sl@0: * Invokes localtime or localtime_r as appropriate. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: struct tm * sl@0: TclpLocaltime( tt ) sl@0: TclpTime_t_CONST tt; sl@0: { sl@0: CONST time_t *timePtr = (CONST time_t *) tt; sl@0: /* Pointer to the number of seconds sl@0: * since the local system's epoch */ sl@0: sl@0: /* sl@0: * The MS implementation of localtime is thread safe because sl@0: * it returns the time in a block of thread-local storage, sl@0: * and Windows does not provide a Posix localtime_r function. sl@0: */ sl@0: return localtime( timePtr ); sl@0: }