1/* 2 * tclWinTime.c -- 3 * 4 * Contains Windows specific versions of Tcl functions that obtain time 5 * values from the operating system. 6 * 7 * Copyright 1995-1998 by Sun Microsystems, Inc. 8 * 9 * See the file "license.terms" for information on usage and redistribution of 10 * this file, and for a DISCLAIMER OF ALL WARRANTIES. 11 * 12 * RCS: @(#) $Id: tclWinTime.c,v 1.33 2005/11/04 00:06:51 dkf Exp $ 13 */ 14 15#include "tclInt.h" 16 17#define SECSPERDAY (60L * 60L * 24L) 18#define SECSPERYEAR (SECSPERDAY * 365L) 19#define SECSPER4YEAR (SECSPERYEAR * 4L + SECSPERDAY) 20 21/* 22 * Number of samples over which to estimate the performance counter. 23 */ 24 25#define SAMPLES 64 26 27/* 28 * The following arrays contain the day of year for the last day of each 29 * month, where index 1 is January. 30 */ 31 32static int normalDays[] = { 33 -1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364 34}; 35 36static int leapDays[] = { 37 -1, 30, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 38}; 39 40typedef struct ThreadSpecificData { 41 char tzName[64]; /* Time zone name */ 42 struct tm tm; /* time information */ 43} ThreadSpecificData; 44static Tcl_ThreadDataKey dataKey; 45 46/* 47 * Data for managing high-resolution timers. 48 */ 49 50typedef struct TimeInfo { 51 CRITICAL_SECTION cs; /* Mutex guarding this structure. */ 52 int initialized; /* Flag == 1 if this structure is 53 * initialized. */ 54 int perfCounterAvailable; /* Flag == 1 if the hardware has a performance 55 * counter. */ 56 HANDLE calibrationThread; /* Handle to the thread that keeps the virtual 57 * clock calibrated. */ 58 HANDLE readyEvent; /* System event used to trigger the requesting 59 * thread when the clock calibration procedure 60 * is initialized for the first time. */ 61 HANDLE exitEvent; /* Event to signal out of an exit handler to 62 * tell the calibration loop to terminate. */ 63 LARGE_INTEGER nominalFreq; /* Nominal frequency of the system performance 64 * counter, that is, the value returned from 65 * QueryPerformanceFrequency. */ 66 67 /* 68 * The following values are used for calculating virtual time. Virtual 69 * time is always equal to: 70 * lastFileTime + (current perf counter - lastCounter) 71 * * 10000000 / curCounterFreq 72 * and lastFileTime and lastCounter are updated any time that virtual time 73 * is returned to a caller. 74 */ 75 76 ULARGE_INTEGER fileTimeLastCall; 77 LARGE_INTEGER perfCounterLastCall; 78 LARGE_INTEGER curCounterFreq; 79 80 /* 81 * Data used in developing the estimate of performance counter frequency 82 */ 83 84 Tcl_WideUInt fileTimeSample[SAMPLES]; 85 /* Last 64 samples of system time. */ 86 Tcl_WideInt perfCounterSample[SAMPLES]; 87 /* Last 64 samples of performance counter. */ 88 int sampleNo; /* Current sample number. */ 89} TimeInfo; 90 91static TimeInfo timeInfo = { 92 { NULL }, 93 0, 94 0, 95 (HANDLE) NULL, 96 (HANDLE) NULL, 97 (HANDLE) NULL, 98#ifdef HAVE_CAST_TO_UNION 99 (LARGE_INTEGER) (Tcl_WideInt) 0, 100 (ULARGE_INTEGER) (DWORDLONG) 0, 101 (LARGE_INTEGER) (Tcl_WideInt) 0, 102 (LARGE_INTEGER) (Tcl_WideInt) 0, 103#else 104 0, 105 0, 106 0, 107 0, 108#endif 109 { 0 }, 110 { 0 }, 111 0 112}; 113 114/* 115 * Declarations for functions defined later in this file. 116 */ 117 118static struct tm * ComputeGMT(const time_t *tp); 119static void StopCalibration(ClientData clientData); 120static DWORD WINAPI CalibrationThread(LPVOID arg); 121static void UpdateTimeEachSecond(void); 122static void ResetCounterSamples(Tcl_WideUInt fileTime, 123 Tcl_WideInt perfCounter, Tcl_WideInt perfFreq); 124static Tcl_WideInt AccumulateSample(Tcl_WideInt perfCounter, 125 Tcl_WideUInt fileTime); 126static void NativeScaleTime(Tcl_Time* timebuf, 127 ClientData clientData); 128static void NativeGetTime(Tcl_Time* timebuf, 129 ClientData clientData); 130 131/* 132 * TIP #233 (Virtualized Time): Data for the time hooks, if any. 133 */ 134 135Tcl_GetTimeProc *tclGetTimeProcPtr = NativeGetTime; 136Tcl_ScaleTimeProc *tclScaleTimeProcPtr = NativeScaleTime; 137ClientData tclTimeClientData = NULL; 138 139/* 140 *---------------------------------------------------------------------- 141 * 142 * TclpGetSeconds -- 143 * 144 * This procedure returns the number of seconds from the epoch. On most 145 * Unix systems the epoch is Midnight Jan 1, 1970 GMT. 146 * 147 * Results: 148 * Number of seconds from the epoch. 149 * 150 * Side effects: 151 * None. 152 * 153 *---------------------------------------------------------------------- 154 */ 155 156unsigned long 157TclpGetSeconds(void) 158{ 159 Tcl_Time t; 160 161 (*tclGetTimeProcPtr) (&t, tclTimeClientData); /* Tcl_GetTime inlined. */ 162 return t.sec; 163} 164 165/* 166 *---------------------------------------------------------------------- 167 * 168 * TclpGetClicks -- 169 * 170 * This procedure returns a value that represents the highest resolution 171 * clock available on the system. There are no guarantees on what the 172 * resolution will be. In Tcl we will call this value a "click". The 173 * start time is also system dependant. 174 * 175 * Results: 176 * Number of clicks from some start time. 177 * 178 * Side effects: 179 * None. 180 * 181 *---------------------------------------------------------------------- 182 */ 183 184unsigned long 185TclpGetClicks(void) 186{ 187 /* 188 * Use the Tcl_GetTime abstraction to get the time in microseconds, as 189 * nearly as we can, and return it. 190 */ 191 192 Tcl_Time now; /* Current Tcl time */ 193 unsigned long retval; /* Value to return */ 194 195 (*tclGetTimeProcPtr) (&now, tclTimeClientData); /* Tcl_GetTime inlined */ 196 197 retval = (now.sec * 1000000) + now.usec; 198 return retval; 199 200} 201 202/* 203 *---------------------------------------------------------------------- 204 * 205 * TclpGetTimeZone -- 206 * 207 * Determines the current timezone. The method varies wildly between 208 * different Platform implementations, so its hidden in this function. 209 * 210 * Results: 211 * Minutes west of GMT. 212 * 213 * Side effects: 214 * None. 215 * 216 *---------------------------------------------------------------------- 217 */ 218 219int 220TclpGetTimeZone( 221 unsigned long currentTime) 222{ 223 int timeZone; 224 225 tzset(); 226 timeZone = timezone / 60; 227 228 return timeZone; 229} 230 231/* 232 *---------------------------------------------------------------------- 233 * 234 * Tcl_GetTime -- 235 * 236 * Gets the current system time in seconds and microseconds since the 237 * beginning of the epoch: 00:00 UCT, January 1, 1970. 238 * 239 * Results: 240 * Returns the current time in timePtr. 241 * 242 * Side effects: 243 * On the first call, initializes a set of static variables to keep track 244 * of the base value of the performance counter, the corresponding wall 245 * clock (obtained through ftime) and the frequency of the performance 246 * counter. Also spins a thread whose function is to wake up periodically 247 * and monitor these values, adjusting them as necessary to correct for 248 * drift in the performance counter's oscillator. 249 * 250 *---------------------------------------------------------------------- 251 */ 252 253void 254Tcl_GetTime( 255 Tcl_Time *timePtr) /* Location to store time information. */ 256{ 257 (*tclGetTimeProcPtr) (timePtr, tclTimeClientData); 258} 259 260/* 261 *---------------------------------------------------------------------- 262 * 263 * NativeScaleTime -- 264 * 265 * TIP #233: Scale from virtual time to the real-time. For native scaling 266 * the relationship is 1:1 and nothing has to be done. 267 * 268 * Results: 269 * Scales the time in timePtr. 270 * 271 * Side effects: 272 * See above. 273 * 274 *---------------------------------------------------------------------- 275 */ 276 277static void 278NativeScaleTime( 279 Tcl_Time *timePtr, 280 ClientData clientData) 281{ 282 /* 283 * Native scale is 1:1. Nothing is done. 284 */ 285} 286 287/* 288 *---------------------------------------------------------------------- 289 * 290 * NativeGetTime -- 291 * 292 * TIP #233: Gets the current system time in seconds and microseconds 293 * since the beginning of the epoch: 00:00 UCT, January 1, 1970. 294 * 295 * Results: 296 * Returns the current time in timePtr. 297 * 298 * Side effects: 299 * On the first call, initializes a set of static variables to keep track 300 * of the base value of the performance counter, the corresponding wall 301 * clock (obtained through ftime) and the frequency of the performance 302 * counter. Also spins a thread whose function is to wake up periodically 303 * and monitor these values, adjusting them as necessary to correct for 304 * drift in the performance counter's oscillator. 305 * 306 *---------------------------------------------------------------------- 307 */ 308 309static void 310NativeGetTime( 311 Tcl_Time *timePtr, 312 ClientData clientData) 313{ 314 struct timeb t; 315 int useFtime = 1; /* Flag == TRUE if we need to fall back on 316 * ftime rather than using the perf counter. */ 317 318 /* 319 * Initialize static storage on the first trip through. 320 * 321 * Note: Outer check for 'initialized' is a performance win since it 322 * avoids an extra mutex lock in the common case. 323 */ 324 325 if (!timeInfo.initialized) { 326 TclpInitLock(); 327 if (!timeInfo.initialized) { 328 timeInfo.perfCounterAvailable = 329 QueryPerformanceFrequency(&timeInfo.nominalFreq); 330 331 /* 332 * Some hardware abstraction layers use the CPU clock in place of 333 * the real-time clock as a performance counter reference. This 334 * results in: 335 * - inconsistent results among the processors on 336 * multi-processor systems. 337 * - unpredictable changes in performance counter frequency on 338 * "gearshift" processors such as Transmeta and SpeedStep. 339 * 340 * There seems to be no way to test whether the performance 341 * counter is reliable, but a useful heuristic is that if its 342 * frequency is 1.193182 MHz or 3.579545 MHz, it's derived from a 343 * colorburst crystal and is therefore the RTC rather than the 344 * TSC. 345 * 346 * A sloppier but serviceable heuristic is that the RTC crystal is 347 * normally less than 15 MHz while the TSC crystal is virtually 348 * assured to be greater than 100 MHz. Since Win98SE appears to 349 * fiddle with the definition of the perf counter frequency 350 * (perhaps in an attempt to calibrate the clock?), we use the 351 * latter rule rather than an exact match. 352 * 353 * We also assume (perhaps questionably) that the vendors have 354 * gotten their act together on Win64, so bypass all this rubbish 355 * on that platform. 356 */ 357 358#if !defined(_WIN64) 359 if (timeInfo.perfCounterAvailable 360 /* 361 * The following lines would do an exact match on crystal 362 * frequency: 363 * && timeInfo.nominalFreq.QuadPart != (Tcl_WideInt)1193182 364 * && timeInfo.nominalFreq.QuadPart != (Tcl_WideInt)3579545 365 */ 366 && timeInfo.nominalFreq.QuadPart > (Tcl_WideInt) 15000000){ 367 /* 368 * As an exception, if every logical processor on the system 369 * is on the same chip, we use the performance counter anyway, 370 * presuming that everyone's TSC is locked to the same 371 * oscillator. 372 */ 373 374 SYSTEM_INFO systemInfo; 375 unsigned int regs[4]; 376 377 GetSystemInfo(&systemInfo); 378 if (TclWinCPUID(0, regs) == TCL_OK 379 && regs[1] == 0x756e6547 /* "Genu" */ 380 && regs[3] == 0x49656e69 /* "ineI" */ 381 && regs[2] == 0x6c65746e /* "ntel" */ 382 && TclWinCPUID(1, regs) == TCL_OK 383 && ((regs[0]&0x00000F00) == 0x00000F00 /* Pentium 4 */ 384 || ((regs[0] & 0x00F00000) /* Extended family */ 385 && (regs[3] & 0x10000000))) /* Hyperthread */ 386 && (((regs[1]&0x00FF0000) >> 16)/* CPU count */ 387 == systemInfo.dwNumberOfProcessors)) { 388 timeInfo.perfCounterAvailable = TRUE; 389 } else { 390 timeInfo.perfCounterAvailable = FALSE; 391 } 392 } 393#endif /* above code is Win32 only */ 394 395 /* 396 * If the performance counter is available, start a thread to 397 * calibrate it. 398 */ 399 400 if (timeInfo.perfCounterAvailable) { 401 DWORD id; 402 403 InitializeCriticalSection(&timeInfo.cs); 404 timeInfo.readyEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 405 timeInfo.exitEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 406 timeInfo.calibrationThread = CreateThread(NULL, 256, 407 CalibrationThread, (LPVOID) NULL, 0, &id); 408 SetThreadPriority(timeInfo.calibrationThread, 409 THREAD_PRIORITY_HIGHEST); 410 411 /* 412 * Wait for the thread just launched to start running, and 413 * create an exit handler that kills it so that it doesn't 414 * outlive unloading tclXX.dll 415 */ 416 417 WaitForSingleObject(timeInfo.readyEvent, INFINITE); 418 CloseHandle(timeInfo.readyEvent); 419 Tcl_CreateExitHandler(StopCalibration, (ClientData) NULL); 420 } 421 timeInfo.initialized = TRUE; 422 } 423 TclpInitUnlock(); 424 } 425 426 if (timeInfo.perfCounterAvailable && timeInfo.curCounterFreq.QuadPart!=0) { 427 /* 428 * Query the performance counter and use it to calculate the current 429 * time. 430 */ 431 432 LARGE_INTEGER curCounter; 433 /* Current performance counter. */ 434 Tcl_WideInt curFileTime;/* Current estimated time, expressed as 100-ns 435 * ticks since the Windows epoch. */ 436 static LARGE_INTEGER posixEpoch; 437 /* Posix epoch expressed as 100-ns ticks since 438 * the windows epoch. */ 439 Tcl_WideInt usecSincePosixEpoch; 440 /* Current microseconds since Posix epoch. */ 441 442 posixEpoch.LowPart = 0xD53E8000; 443 posixEpoch.HighPart = 0x019DB1DE; 444 445 EnterCriticalSection(&timeInfo.cs); 446 447 QueryPerformanceCounter(&curCounter); 448 449 /* 450 * If it appears to be more than 1.1 seconds since the last trip 451 * through the calibration loop, the performance counter may have 452 * jumped forward. (See MSDN Knowledge Base article Q274323 for a 453 * description of the hardware problem that makes this test 454 * necessary.) If the counter jumps, we don't want to use it directly. 455 * Instead, we must return system time. Eventually, the calibration 456 * loop should recover. 457 */ 458 459 if (curCounter.QuadPart - timeInfo.perfCounterLastCall.QuadPart < 460 11 * timeInfo.curCounterFreq.QuadPart / 10) { 461 curFileTime = timeInfo.fileTimeLastCall.QuadPart + 462 ((curCounter.QuadPart - timeInfo.perfCounterLastCall.QuadPart) 463 * 10000000 / timeInfo.curCounterFreq.QuadPart); 464 timeInfo.fileTimeLastCall.QuadPart = curFileTime; 465 timeInfo.perfCounterLastCall.QuadPart = curCounter.QuadPart; 466 usecSincePosixEpoch = (curFileTime - posixEpoch.QuadPart) / 10; 467 timePtr->sec = (long) (usecSincePosixEpoch / 1000000); 468 timePtr->usec = (unsigned long) (usecSincePosixEpoch % 1000000); 469 useFtime = 0; 470 } 471 472 LeaveCriticalSection(&timeInfo.cs); 473 } 474 475 if (useFtime) { 476 /* 477 * High resolution timer is not available. Just use ftime. 478 */ 479 480 ftime(&t); 481 timePtr->sec = (long)t.time; 482 timePtr->usec = t.millitm * 1000; 483 } 484} 485 486/* 487 *---------------------------------------------------------------------- 488 * 489 * StopCalibration -- 490 * 491 * Turns off the calibration thread in preparation for exiting the 492 * process. 493 * 494 * Results: 495 * None. 496 * 497 * Side effects: 498 * Sets the 'exitEvent' event in the 'timeInfo' structure to ask the 499 * thread in question to exit, and waits for it to do so. 500 * 501 *---------------------------------------------------------------------- 502 */ 503 504static void 505StopCalibration( 506 ClientData unused) /* Client data is unused */ 507{ 508 SetEvent(timeInfo.exitEvent); 509 510 /* 511 * If Tcl_Finalize was called from DllMain, the calibration thread is in a 512 * paused state so we need to timeout and continue. 513 */ 514 515 WaitForSingleObject(timeInfo.calibrationThread, 100); 516 CloseHandle(timeInfo.exitEvent); 517 CloseHandle(timeInfo.calibrationThread); 518} 519 520/* 521 *---------------------------------------------------------------------- 522 * 523 * TclpGetTZName -- 524 * 525 * Gets the current timezone string. 526 * 527 * Results: 528 * Returns a pointer to a static string, or NULL on failure. 529 * 530 * Side effects: 531 * None. 532 * 533 *---------------------------------------------------------------------- 534 */ 535 536char * 537TclpGetTZName( 538 int dst) 539{ 540 int len; 541 char *zone, *p; 542 TIME_ZONE_INFORMATION tz; 543 Tcl_Encoding encoding; 544 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); 545 char *name = tsdPtr->tzName; 546 547 /* 548 * tzset() under Borland doesn't seem to set up tzname[] at all. 549 * tzset() under MSVC has the following weird observed behavior: 550 * First time we call "clock format [clock seconds] -format %Z -gmt 1" 551 * we get "GMT", but on all subsequent calls we get the current time 552 * ezone string, even though env(TZ) is GMT and the variable _timezone 553 * is 0. 554 */ 555 556 name[0] = '\0'; 557 558 zone = getenv("TZ"); 559 if (zone != NULL) { 560 /* 561 * TZ is of form "NST-4:30NDT", where "NST" would be the name of the 562 * standard time zone for this area, "-4:30" is the offset from GMT in 563 * hours, and "NDT is the name of the daylight savings time zone in 564 * this area. The offset and DST strings are optional. 565 */ 566 567 len = strlen(zone); 568 if (len > 3) { 569 len = 3; 570 } 571 if (dst != 0) { 572 /* 573 * Skip the offset string and get the DST string. 574 */ 575 576 p = zone + len; 577 p += strspn(p, "+-:0123456789"); 578 if (*p != '\0') { 579 zone = p; 580 len = strlen(zone); 581 if (len > 3) { 582 len = 3; 583 } 584 } 585 } 586 Tcl_ExternalToUtf(NULL, NULL, zone, len, 0, NULL, name, 587 sizeof(tsdPtr->tzName), NULL, NULL, NULL); 588 } 589 if (name[0] == '\0') { 590 if (GetTimeZoneInformation(&tz) == TIME_ZONE_ID_UNKNOWN) { 591 /* 592 * MSDN: On NT this is returned if DST is not used in the current 593 * TZ 594 */ 595 596 dst = 0; 597 } 598 encoding = Tcl_GetEncoding(NULL, "unicode"); 599 Tcl_ExternalToUtf(NULL, encoding, 600 (char *) ((dst) ? tz.DaylightName : tz.StandardName), -1, 601 0, NULL, name, sizeof(tsdPtr->tzName), NULL, NULL, NULL); 602 Tcl_FreeEncoding(encoding); 603 } 604 return name; 605} 606 607/* 608 *---------------------------------------------------------------------- 609 * 610 * TclpGetDate -- 611 * 612 * This function converts between seconds and struct tm. If useGMT is 613 * true, then the returned date will be in Greenwich Mean Time (GMT). 614 * Otherwise, it will be in the local time zone. 615 * 616 * Results: 617 * Returns a static tm structure. 618 * 619 * Side effects: 620 * None. 621 * 622 *---------------------------------------------------------------------- 623 */ 624 625struct tm * 626TclpGetDate( 627 CONST time_t *t, 628 int useGMT) 629{ 630 struct tm *tmPtr; 631 time_t time; 632 633 if (!useGMT) { 634 tzset(); 635 636 /* 637 * If we are in the valid range, let the C run-time library handle it. 638 * Otherwise we need to fake it. Note that this algorithm ignores 639 * daylight savings time before the epoch. 640 */ 641 642 /* 643 * Hm, Borland's localtime manages to return NULL under certain 644 * circumstances (e.g. wintime.test, test 1.2). Nobody tests for this, 645 * since 'localtime' isn't supposed to do this, possibly leading to 646 * crashes. 647 * 648 * Patch: We only call this function if we are at least one day into 649 * the epoch, else we handle it ourselves (like we do for times < 0). 650 * H. Giese, June 2003 651 */ 652 653#ifdef __BORLANDC__ 654#define LOCALTIME_VALIDITY_BOUNDARY SECSPERDAY 655#else 656#define LOCALTIME_VALIDITY_BOUNDARY 0 657#endif 658 659 if (*t >= LOCALTIME_VALIDITY_BOUNDARY) { 660 return TclpLocaltime(t); 661 } 662 663 time = *t - timezone; 664 665 /* 666 * If we aren't near to overflowing the long, just add the bias and 667 * use the normal calculation. Otherwise we will need to adjust the 668 * result at the end. 669 */ 670 671 if (*t < (LONG_MAX - 2*SECSPERDAY) && *t > (LONG_MIN + 2*SECSPERDAY)) { 672 tmPtr = ComputeGMT(&time); 673 } else { 674 tmPtr = ComputeGMT(t); 675 676 tzset(); 677 678 /* 679 * Add the bias directly to the tm structure to avoid overflow. 680 * Propagate seconds overflow into minutes, hours and days. 681 */ 682 683 time = tmPtr->tm_sec - timezone; 684 tmPtr->tm_sec = (int)(time % 60); 685 if (tmPtr->tm_sec < 0) { 686 tmPtr->tm_sec += 60; 687 time -= 60; 688 } 689 690 time = tmPtr->tm_min + time/60; 691 tmPtr->tm_min = (int)(time % 60); 692 if (tmPtr->tm_min < 0) { 693 tmPtr->tm_min += 60; 694 time -= 60; 695 } 696 697 time = tmPtr->tm_hour + time/60; 698 tmPtr->tm_hour = (int)(time % 24); 699 if (tmPtr->tm_hour < 0) { 700 tmPtr->tm_hour += 24; 701 time -= 24; 702 } 703 704 time /= 24; 705 tmPtr->tm_mday += (int)time; 706 tmPtr->tm_yday += (int)time; 707 tmPtr->tm_wday = (tmPtr->tm_wday + (int)time) % 7; 708 } 709 } else { 710 tmPtr = ComputeGMT(t); 711 } 712 return tmPtr; 713} 714 715/* 716 *---------------------------------------------------------------------- 717 * 718 * ComputeGMT -- 719 * 720 * This function computes GMT given the number of seconds since the epoch 721 * (midnight Jan 1 1970). 722 * 723 * Results: 724 * Returns a (per thread) statically allocated struct tm. 725 * 726 * Side effects: 727 * Updates the values of the static struct tm. 728 * 729 *---------------------------------------------------------------------- 730 */ 731 732static struct tm * 733ComputeGMT( 734 const time_t *tp) 735{ 736 struct tm *tmPtr; 737 long tmp, rem; 738 int isLeap; 739 int *days; 740 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); 741 742 tmPtr = &tsdPtr->tm; 743 744 /* 745 * Compute the 4 year span containing the specified time. 746 */ 747 748 tmp = (long)(*tp / SECSPER4YEAR); 749 rem = (long)(*tp % SECSPER4YEAR); 750 751 /* 752 * Correct for weird mod semantics so the remainder is always positive. 753 */ 754 755 if (rem < 0) { 756 tmp--; 757 rem += SECSPER4YEAR; 758 } 759 760 /* 761 * Compute the year after 1900 by taking the 4 year span and adjusting for 762 * the remainder. This works because 2000 is a leap year, and 1900/2100 763 * are out of the range. 764 */ 765 766 tmp = (tmp * 4) + 70; 767 isLeap = 0; 768 if (rem >= SECSPERYEAR) { /* 1971, etc. */ 769 tmp++; 770 rem -= SECSPERYEAR; 771 if (rem >= SECSPERYEAR) { /* 1972, etc. */ 772 tmp++; 773 rem -= SECSPERYEAR; 774 if (rem >= SECSPERYEAR + SECSPERDAY) { /* 1973, etc. */ 775 tmp++; 776 rem -= SECSPERYEAR + SECSPERDAY; 777 } else { 778 isLeap = 1; 779 } 780 } 781 } 782 tmPtr->tm_year = tmp; 783 784 /* 785 * Compute the day of year and leave the seconds in the current day in the 786 * remainder. 787 */ 788 789 tmPtr->tm_yday = rem / SECSPERDAY; 790 rem %= SECSPERDAY; 791 792 /* 793 * Compute the time of day. 794 */ 795 796 tmPtr->tm_hour = rem / 3600; 797 rem %= 3600; 798 tmPtr->tm_min = rem / 60; 799 tmPtr->tm_sec = rem % 60; 800 801 /* 802 * Compute the month and day of month. 803 */ 804 805 days = (isLeap) ? leapDays : normalDays; 806 for (tmp = 1; days[tmp] < tmPtr->tm_yday; tmp++) { 807 /* empty body */ 808 } 809 tmPtr->tm_mon = --tmp; 810 tmPtr->tm_mday = tmPtr->tm_yday - days[tmp]; 811 812 /* 813 * Compute day of week. Epoch started on a Thursday. 814 */ 815 816 tmPtr->tm_wday = (long)(*tp / SECSPERDAY) + 4; 817 if ((*tp % SECSPERDAY) < 0) { 818 tmPtr->tm_wday--; 819 } 820 tmPtr->tm_wday %= 7; 821 if (tmPtr->tm_wday < 0) { 822 tmPtr->tm_wday += 7; 823 } 824 825 return tmPtr; 826} 827 828/* 829 *---------------------------------------------------------------------- 830 * 831 * CalibrationThread -- 832 * 833 * Thread that manages calibration of the hi-resolution time derived from 834 * the performance counter, to keep it synchronized with the system 835 * clock. 836 * 837 * Parameters: 838 * arg - Client data from the CreateThread call. This parameter points to 839 * the static TimeInfo structure. 840 * 841 * Return value: 842 * None. This thread embeds an infinite loop. 843 * 844 * Side effects: 845 * At an interval of 1s, this thread performs virtual time discipline. 846 * 847 * Note: When this thread is entered, TclpInitLock has been called to 848 * safeguard the static storage. There is therefore no synchronization in the 849 * body of this procedure. 850 * 851 *---------------------------------------------------------------------- 852 */ 853 854static DWORD WINAPI 855CalibrationThread( 856 LPVOID arg) 857{ 858 FILETIME curFileTime; 859 DWORD waitResult; 860 861 /* 862 * Get initial system time and performance counter. 863 */ 864 865 GetSystemTimeAsFileTime(&curFileTime); 866 QueryPerformanceCounter(&timeInfo.perfCounterLastCall); 867 QueryPerformanceFrequency(&timeInfo.curCounterFreq); 868 timeInfo.fileTimeLastCall.LowPart = curFileTime.dwLowDateTime; 869 timeInfo.fileTimeLastCall.HighPart = curFileTime.dwHighDateTime; 870 871 ResetCounterSamples(timeInfo.fileTimeLastCall.QuadPart, 872 timeInfo.perfCounterLastCall.QuadPart, 873 timeInfo.curCounterFreq.QuadPart); 874 875 /* 876 * Wake up the calling thread. When it wakes up, it will release the 877 * initialization lock. 878 */ 879 880 SetEvent(timeInfo.readyEvent); 881 882 /* 883 * Run the calibration once a second. 884 */ 885 886 while (timeInfo.perfCounterAvailable) { 887 /* 888 * If the exitEvent is set, break out of the loop. 889 */ 890 891 waitResult = WaitForSingleObjectEx(timeInfo.exitEvent, 1000, FALSE); 892 if (waitResult == WAIT_OBJECT_0) { 893 break; 894 } 895 UpdateTimeEachSecond(); 896 } 897 898 /* lint */ 899 return (DWORD) 0; 900} 901 902/* 903 *---------------------------------------------------------------------- 904 * 905 * UpdateTimeEachSecond -- 906 * 907 * Callback from the waitable timer in the clock calibration thread that 908 * updates system time. 909 * 910 * Parameters: 911 * info - Pointer to the static TimeInfo structure 912 * 913 * Results: 914 * None. 915 * 916 * Side effects: 917 * Performs virtual time calibration discipline. 918 * 919 *---------------------------------------------------------------------- 920 */ 921 922static void 923UpdateTimeEachSecond(void) 924{ 925 LARGE_INTEGER curPerfCounter; 926 /* Current value returned from 927 * QueryPerformanceCounter. */ 928 FILETIME curSysTime; /* Current system time. */ 929 LARGE_INTEGER curFileTime; /* File time at the time this callback was 930 * scheduled. */ 931 Tcl_WideInt estFreq; /* Estimated perf counter frequency. */ 932 Tcl_WideInt vt0; /* Tcl time right now. */ 933 Tcl_WideInt vt1; /* Tcl time one second from now. */ 934 Tcl_WideInt tdiff; /* Difference between system clock and Tcl 935 * time. */ 936 Tcl_WideInt driftFreq; /* Frequency needed to drift virtual time into 937 * step over 1 second. */ 938 939 /* 940 * Sample performance counter and system time. 941 */ 942 943 QueryPerformanceCounter(&curPerfCounter); 944 GetSystemTimeAsFileTime(&curSysTime); 945 curFileTime.LowPart = curSysTime.dwLowDateTime; 946 curFileTime.HighPart = curSysTime.dwHighDateTime; 947 948 EnterCriticalSection(&timeInfo.cs); 949 950 /* 951 * We devide by timeInfo.curCounterFreq.QuadPart in several places. That 952 * value should always be positive on a correctly functioning system. But 953 * it is good to be defensive about such matters. So if something goes 954 * wrong and the value does goes to zero, we clear the 955 * timeInfo.perfCounterAvailable in order to cause the calibration thread 956 * to shut itself down, then return without additional processing. 957 */ 958 959 if (timeInfo.curCounterFreq.QuadPart == 0){ 960 LeaveCriticalSection(&timeInfo.cs); 961 timeInfo.perfCounterAvailable = 0; 962 return; 963 } 964 965 /* 966 * Several things may have gone wrong here that have to be checked for. 967 * (1) The performance counter may have jumped. 968 * (2) The system clock may have been reset. 969 * 970 * In either case, we'll need to reinitialize the circular buffer with 971 * samples relative to the current system time and the NOMINAL performance 972 * frequency (not the actual, because the actual has probably run slow in 973 * the first case). Our estimated frequency will be the nominal frequency. 974 * 975 * Store the current sample into the circular buffer of samples, and 976 * estimate the performance counter frequency. 977 */ 978 979 estFreq = AccumulateSample(curPerfCounter.QuadPart, 980 (Tcl_WideUInt) curFileTime.QuadPart); 981 982 /* 983 * We want to adjust things so that time appears to be continuous. 984 * Virtual file time, right now, is 985 * 986 * vt0 = 10000000 * (curPerfCounter - perfCounterLastCall) 987 * / curCounterFreq 988 * + fileTimeLastCall 989 * 990 * Ideally, we would like to drift the clock into place over a period of 2 991 * sec, so that virtual time 2 sec from now will be 992 * 993 * vt1 = 20000000 + curFileTime 994 * 995 * The frequency that we need to use to drift the counter back into place 996 * is estFreq * 20000000 / (vt1 - vt0) 997 */ 998 999 vt0 = 10000000 * (curPerfCounter.QuadPart 1000 - timeInfo.perfCounterLastCall.QuadPart) 1001 / timeInfo.curCounterFreq.QuadPart 1002 + timeInfo.fileTimeLastCall.QuadPart; 1003 vt1 = 20000000 + curFileTime.QuadPart; 1004 1005 /* 1006 * If we've gotten more than a second away from system time, then drifting 1007 * the clock is going to be pretty hopeless. Just let it jump. Otherwise, 1008 * compute the drift frequency and fill in everything. 1009 */ 1010 1011 tdiff = vt0 - curFileTime.QuadPart; 1012 if (tdiff > 10000000 || tdiff < -10000000) { 1013 timeInfo.fileTimeLastCall.QuadPart = curFileTime.QuadPart; 1014 timeInfo.curCounterFreq.QuadPart = estFreq; 1015 } else { 1016 driftFreq = estFreq * 20000000 / (vt1 - vt0); 1017 1018 if (driftFreq > 1003*estFreq/1000) { 1019 driftFreq = 1003*estFreq/1000; 1020 } else if (driftFreq < 997*estFreq/1000) { 1021 driftFreq = 997*estFreq/1000; 1022 } 1023 1024 timeInfo.fileTimeLastCall.QuadPart = vt0; 1025 timeInfo.curCounterFreq.QuadPart = driftFreq; 1026 } 1027 1028 timeInfo.perfCounterLastCall.QuadPart = curPerfCounter.QuadPart; 1029 1030 LeaveCriticalSection(&timeInfo.cs); 1031} 1032 1033/* 1034 *---------------------------------------------------------------------- 1035 * 1036 * ResetCounterSamples -- 1037 * 1038 * Fills the sample arrays in 'timeInfo' with dummy values that will 1039 * yield the current performance counter and frequency. 1040 * 1041 * Results: 1042 * None. 1043 * 1044 * Side effects: 1045 * The array of samples is filled in so that it appears that there are 1046 * SAMPLES samples at one-second intervals, separated by precisely the 1047 * given frequency. 1048 * 1049 *---------------------------------------------------------------------- 1050 */ 1051 1052static void 1053ResetCounterSamples( 1054 Tcl_WideUInt fileTime, /* Current file time */ 1055 Tcl_WideInt perfCounter, /* Current performance counter */ 1056 Tcl_WideInt perfFreq) /* Target performance frequency */ 1057{ 1058 int i; 1059 for (i=SAMPLES-1 ; i>=0 ; --i) { 1060 timeInfo.perfCounterSample[i] = perfCounter; 1061 timeInfo.fileTimeSample[i] = fileTime; 1062 perfCounter -= perfFreq; 1063 fileTime -= 10000000; 1064 } 1065 timeInfo.sampleNo = 0; 1066} 1067 1068/* 1069 *---------------------------------------------------------------------- 1070 * 1071 * AccumulateSample -- 1072 * 1073 * Updates the circular buffer of performance counter and system time 1074 * samples with a new data point. 1075 * 1076 * Results: 1077 * None. 1078 * 1079 * Side effects: 1080 * The new data point replaces the oldest point in the circular buffer, 1081 * and the descriptive statistics are updated to accumulate the new 1082 * point. 1083 * 1084 * Several things may have gone wrong here that have to be checked for. 1085 * (1) The performance counter may have jumped. 1086 * (2) The system clock may have been reset. 1087 * 1088 * In either case, we'll need to reinitialize the circular buffer with samples 1089 * relative to the current system time and the NOMINAL performance frequency 1090 * (not the actual, because the actual has probably run slow in the first 1091 * case). 1092 */ 1093 1094static Tcl_WideInt 1095AccumulateSample( 1096 Tcl_WideInt perfCounter, 1097 Tcl_WideUInt fileTime) 1098{ 1099 Tcl_WideUInt workFTSample; /* File time sample being removed from or 1100 * added to the circular buffer. */ 1101 Tcl_WideInt workPCSample; /* Performance counter sample being removed 1102 * from or added to the circular buffer. */ 1103 Tcl_WideUInt lastFTSample; /* Last file time sample recorded */ 1104 Tcl_WideInt lastPCSample; /* Last performance counter sample recorded */ 1105 Tcl_WideInt FTdiff; /* Difference between last FT and current */ 1106 Tcl_WideInt PCdiff; /* Difference between last PC and current */ 1107 Tcl_WideInt estFreq; /* Estimated performance counter frequency */ 1108 1109 /* 1110 * Test for jumps and reset the samples if we have one. 1111 */ 1112 1113 if (timeInfo.sampleNo == 0) { 1114 lastPCSample = 1115 timeInfo.perfCounterSample[timeInfo.sampleNo + SAMPLES - 1]; 1116 lastFTSample = 1117 timeInfo.fileTimeSample[timeInfo.sampleNo + SAMPLES - 1]; 1118 } else { 1119 lastPCSample = timeInfo.perfCounterSample[timeInfo.sampleNo - 1]; 1120 lastFTSample = timeInfo.fileTimeSample[timeInfo.sampleNo - 1]; 1121 } 1122 1123 PCdiff = perfCounter - lastPCSample; 1124 FTdiff = fileTime - lastFTSample; 1125 if (PCdiff < timeInfo.nominalFreq.QuadPart * 9 / 10 1126 || PCdiff > timeInfo.nominalFreq.QuadPart * 11 / 10 1127 || FTdiff < 9000000 || FTdiff > 11000000) { 1128 ResetCounterSamples(fileTime, perfCounter, 1129 timeInfo.nominalFreq.QuadPart); 1130 return timeInfo.nominalFreq.QuadPart; 1131 } else { 1132 /* 1133 * Estimate the frequency. 1134 */ 1135 1136 workPCSample = timeInfo.perfCounterSample[timeInfo.sampleNo]; 1137 workFTSample = timeInfo.fileTimeSample[timeInfo.sampleNo]; 1138 estFreq = 10000000 * (perfCounter - workPCSample) 1139 / (fileTime - workFTSample); 1140 timeInfo.perfCounterSample[timeInfo.sampleNo] = perfCounter; 1141 timeInfo.fileTimeSample[timeInfo.sampleNo] = (Tcl_WideInt) fileTime; 1142 1143 /* 1144 * Advance the sample number. 1145 */ 1146 1147 if (++timeInfo.sampleNo >= SAMPLES) { 1148 timeInfo.sampleNo = 0; 1149 } 1150 1151 return estFreq; 1152 } 1153} 1154 1155/* 1156 *---------------------------------------------------------------------- 1157 * 1158 * TclpGmtime -- 1159 * 1160 * Wrapper around the 'gmtime' library function to make it thread safe. 1161 * 1162 * Results: 1163 * Returns a pointer to a 'struct tm' in thread-specific data. 1164 * 1165 * Side effects: 1166 * Invokes gmtime or gmtime_r as appropriate. 1167 * 1168 *---------------------------------------------------------------------- 1169 */ 1170 1171struct tm * 1172TclpGmtime( 1173 CONST time_t *timePtr) /* Pointer to the number of seconds since the 1174 * local system's epoch */ 1175{ 1176 /* 1177 * The MS implementation of gmtime is thread safe because it returns the 1178 * time in a block of thread-local storage, and Windows does not provide a 1179 * Posix gmtime_r function. 1180 */ 1181 1182 return gmtime(timePtr); 1183} 1184 1185/* 1186 *---------------------------------------------------------------------- 1187 * 1188 * TclpLocaltime -- 1189 * 1190 * Wrapper around the 'localtime' library function to make it thread 1191 * safe. 1192 * 1193 * Results: 1194 * Returns a pointer to a 'struct tm' in thread-specific data. 1195 * 1196 * Side effects: 1197 * Invokes localtime or localtime_r as appropriate. 1198 * 1199 *---------------------------------------------------------------------- 1200 */ 1201 1202struct tm * 1203TclpLocaltime( 1204 CONST time_t *timePtr) /* Pointer to the number of seconds since the 1205 * local system's epoch */ 1206 1207{ 1208 /* 1209 * The MS implementation of localtime is thread safe because it returns 1210 * the time in a block of thread-local storage, and Windows does not 1211 * provide a Posix localtime_r function. 1212 */ 1213 1214 return localtime(timePtr); 1215} 1216 1217/* 1218 *---------------------------------------------------------------------- 1219 * 1220 * Tcl_SetTimeProc -- 1221 * 1222 * TIP #233 (Virtualized Time): Registers two handlers for the 1223 * virtualization of Tcl's access to time information. 1224 * 1225 * Results: 1226 * None. 1227 * 1228 * Side effects: 1229 * Remembers the handlers, alters core behaviour. 1230 * 1231 *---------------------------------------------------------------------- 1232 */ 1233 1234void 1235Tcl_SetTimeProc( 1236 Tcl_GetTimeProc *getProc, 1237 Tcl_ScaleTimeProc *scaleProc, 1238 ClientData clientData) 1239{ 1240 tclGetTimeProcPtr = getProc; 1241 tclScaleTimeProcPtr = scaleProc; 1242 tclTimeClientData = clientData; 1243} 1244 1245/* 1246 *---------------------------------------------------------------------- 1247 * 1248 * Tcl_QueryTimeProc -- 1249 * 1250 * TIP #233 (Virtualized Time): Query which time handlers are registered. 1251 * 1252 * Results: 1253 * None. 1254 * 1255 * Side effects: 1256 * None. 1257 * 1258 *---------------------------------------------------------------------- 1259 */ 1260 1261void 1262Tcl_QueryTimeProc( 1263 Tcl_GetTimeProc **getProc, 1264 Tcl_ScaleTimeProc **scaleProc, 1265 ClientData *clientData) 1266{ 1267 if (getProc) { 1268 *getProc = tclGetTimeProcPtr; 1269 } 1270 if (scaleProc) { 1271 *scaleProc = tclScaleTimeProcPtr; 1272 } 1273 if (clientData) { 1274 *clientData = tclTimeClientData; 1275 } 1276} 1277 1278/* 1279 * Local Variables: 1280 * mode: c 1281 * c-basic-offset: 4 1282 * fill-column: 78 1283 * End: 1284 */ 1285