'C++ call to API function ::GetTickCount() jumps ~18 days

On a few Windows computers I have seen that two, on each other following, calls to ::GetTickCount() returns a difference of 1610619236 ms (around 18 days). This is not due to wrap around og int/unsigned int mismatch. I use Visual C++ 2015/2017.

Has anybody else seen this behaviour? Does anybody have any idea about what could cause behaviour like this?

Best regards John

Code sample that shows the bug:

class CLTemp
{
    DWORD nLastCheck;
    CLTemp() 
    {
        nLastCheck=::GetTickCount();
    }
    //Service is called every 200ms by a timer
    void Service()
    {
        if( ::GetTickCount() - nLastCheck > 20000 )//check every 20 sec
        {
            //On some Windows machines, after an uptime of 776 days, the
            //::GetTickCount() - nLastCheck gives a value of 1610619236
            //(corresponding to around 18 days)
            nLastCheck = ::GetTickCount();
        }
    }

};

Update - problem description, a way of recreating and solution:

The Windows API function GetTickCount() unexpectedly jumps 18 days forward in time when passing 776 days after Windows Restart. We have experienced several times that some of our long running Windows pc applications coded in Microsoft Visual C++ suddenly reported a time-out error. In many of our applications we call GetTickCount() to perform some tasks with certain intervals or to watch for a time-out condition. The example code could go as this:

    DWORD dwTimeNow, dwPrevTime = ::GetTickCount();
    bool bExit = false;
    While (!bExit)
    {
        dwTimeNow = ::GetTickCount();
        if (dwTimeNow – dwPrevTime >= 5000)
        {
            dwPrevTime = dwTimeNow;
            // Perform my task
        }
        else
        {
             ::Sleep(10);
        }
     }

GetTickCount() returns a DWORD, which is an unsigned 32-bit int. GetTickCount() wraps around from its maximum value of 0xFFFFFFFF to zero after app. 49 days. The wrap around is easily handled by using unsigned arithmetic and always subtracting the previous value from the new value to calculate the distance. Do never compare two values from GetTickCount() against each other.

So, the wrap around at its maximum value each 49 days it expected and handled. But we have experienced an unexpected wrap around to zero of GetTickCount() after 776 days after latest Windows Restart. And in this case GetTickCount() wraps from 0x9FFFFFFF to zero, which is 1610612736 milliseconds too early corresponding to around 18.6 days. When GetTickCount() is used to check for a time-out condition and it suddenly reports that 18 days have elapsed since last check, then the software reports a false time-out condition. Note that it is 776 days after a Windows Restart. A Windows Restart resets the GetTickCount() value to zero. A pc reboot does not, instead the time elapsed while switched off is added to the initial GetTickCount() value.

We have made a test program that provides evidence of this issue. The test program reads the values of GetTickCount(), GetTickCount64(), InterruptTime(), and UnbiasedInterruptTime() each 5000 milliseconds scheduled by a Windows Timer. Each time the sample program calculates the distance in time for each of the four time-functions. If the distance in time is 10000 milliseconds or more, it is marked as a time jump event and logged. Each time it also keeps track of the minimum distance and the maximum distance in time for each time-function.

Before starting the test program, a Windows Restart is carried out. Ensure no automatic time synchronization is enabled. Then make a Windows shut down. Start the pc again and make it enter its Bios setup when it boots. In the Bios, advance the real time clock 776 days. Let the pc boot up and start the test program. Then after 17 hours the unexpected wraparound of GetTickCount() occurs (776 days, 17 hours, and 21 minutes). It is only GetTickCount() that shows this behavior. The other time-functions do not.

The following excerpt from the logfile of the test program shows the start values reported by the four time-functions. In this example the time has only been advanced to 775 days after Windows Restart. The format of the log entry is the time-function value converted into: days hh:mm:ss.msec. TickCount32 is the plain GetTickCount(). Because it is a 32-bit value it has wrapped around and shows a different value. At GetTickCount64() we can see the 775 days.

    2024-05-14 09:13:27.262 Start times
    TickCount32 : 029 08:30:11.591
    TickCount64 : 775 00:12:01.031
    InterruptTime : 775 00:12:01.036
    UnbiasedInterruptTime: 000 00:05:48.411

The next excerpt from the logfile shows the unexpected wrap around of GetTickCount() (TickCount32). The format is: Distance between the previous value and the new value (should always be around 5000 msec). Then follows the new value converted into days and time, and finally follows the previous value converted into days and time. We can see that GetTickCount() jumps 1610617752 milliseconds (app. 18.6 days) while the other three time-functions only advances app. 5000 msec as expected. At TickCount64 one can see that it occurs at 776 days, 17 hours, and 21 minutes.

    2024-05-16 02:22:30.394 Time jump *****
    TickCount32 : 1610617752 - 000 00:00:00.156 - 031 01:39:09.700
    TickCount64 : 5016 - 776 17:21:04.156 - 776 17:20:59.140
    InterruptTime : 5015 - 776 17:21:04.165 - 776 17:20:59.150
    UnbiasedInterruptTime: 5015 - 001 17:14:51.540 - 001 17:14:46.525

If you increase the time that the real time clock is advanced to two times 776 days and 17 hours – for example 1551 days – the phenomenon shows up once more. It has a cyclic nature.

    2026-06-30 06:34:26.663 Start times
    TickCount32 : 029 12:41:57.888
    TickCount64 : 1551 21:44:51.328
    InterruptTime : 1551 21:44:51.334
    UnbiasedInterruptTime: 004 21:24:24.593
    2026-07-01 19:31:47.641 Time jump *****
    TickCount32 : 1610617736 - 000 00:00:04.296 - 031 01:39:13.856
    TickCount64 : 5000 - 1553 10:42:12.296 - 1553 10:42:07.296
    InterruptTime : 5007 - 1553 10:42:12.310 - 1553 10:42:07.303
    UnbiasedInterruptTime: 5007 - 006 10:21:45.569 - 006 10:21:40.562

The only viable solution to this issue seems to be using GetTickCount64() and totally abandon usage of GetTickCount().



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source