How do I upgrade a 32-bit tick count to a 64-bit one?

Raymond Chen

There are a number of Windows functions that produce 32-bit tick counts, like GetMessageTime. But you may have a general policy in your program of always working with 64-bit tick counts to avoid timer rollover problems. How can you upgrade these to 64-bit tick counts?

Well, clearly, the 32-bit timestamp is ambiguous, since the 32-bit timer rolls over every 49 days (approximately). But there is only one point within the previous 49 days that has the specified 32-bit timestamp. It is probably a reasonable assumption that the timestamp refers to something that occurred within the past 49 days, because most of these system-provided timestamps are reporting the time at which something occurred in the recent past, like the time the message or input was generated prior to delivery. In practice, the timestamp age will be on the order of tens of milliseconds, maybe a few seconds if your program is having a bad day. A delay of over 49 days is extremely unlikely.

All we have to do is reconstruct the upper 32 bits of the timestamp.

uint64_t UpgradeTickCount(uint32_t then, uint64_t now)
{
    auto diff = static_cast<uint32_t>(now) - then;
    return now - diff;
}

First, we calculate the 32-bit elapsed time between the provided timestamp and the current time. This works even in the face of timer rollover thanks to unsigned integer arithmetic, which is modulo 2³² for uint32_t.

Now we know how long ago the 32-bit timestamp was, assuming it represents a time at most 49 days in the past. We can then apply that same adjustment to the current 64-bit time to obtain a full 64-bit timestamp.

The value for now need not be the current time. It just needs to be any 64-bit time value that is at or later than the then value.

Now, there are some functions that report the most recent time an event occurred, and it’s possible that those events are extremely rare. For example, the GetLastInputInfo function tells you the last time the system received input, but a server in a closet might go for an extended period without receiving any input.

What you can do is establish a baseline by querying the timestamp and immediately upgrading it to a 64-bit timestamp. (You have no way of knowing whether that timestamp is less than 49 days old, so you may as well assume that it is.) Each time you want to upgrade a new timestamp, you check whether it is the same as the previous 32-bit timestamp: If so, then assume it’s the same old timestamp. If not, then assume that the new event occurred within the past 49 days. If you perform this check at least once every 49 days (pretty easy to arrange), then that will be a valid assumption.

uint64_t UpgradeTickCount2(uint32_t then, uint64_t previous, uint64_t now)
{
    if (static_cast<uint32_t>(then) == static_cast<uint32_t>(previous)) {
        return previous;
    } else {
        return UpgradeTickCount(then, now);
    }
}

Still, to get to 49 days, it means you’re not applying monthly security updates like some sort of monster who welcomes your new botnet overlords. But at least you got timestamps.

7 comments

Discussion is closed. Login to edit/delete existing comments.

  • Joshua Hudson 0

    I find assuming that auto update will reboot the server every month disturbing. We’ve had server core installs go longer than that because they managed to have no patches on a patch cycle.

    • switchdesktopwithfade@hotmail.com 0

      The switch to Unix file deletion semantics ought to have made most patch reboots a thing of the past. Sets and Activities were a brilliant way to counter the total loss of user workflow that results from a reboot, but then the concept disappeared. It feels like we’re all the targets of Project Mayhem.

      • Georg Rottensteiner 0

        That does not really help though, does it? You then have running processes using the old files, and newly started with the new files. Sounds like a perfect road to chaos.

      • Me Gusta 0

        The big issue comes when you see CVEs for kernel and driver exploits which are fixed by a patch. As an example, the December updated included updates to ntoskernel.exe and ntfs.sys.
        I also ask other people who complain about the system have to restart for updates the simple question of when do they sleep? This is an important question because sleeping would be a perfect time for the system to restart, and you have to be doing something wrong if your workflow can’t handle you sleeping.

        • Pavel Kostromitinov 0

          You know, there was a word in initial comment that you may have missed. The word was “server”.

        • Brian Boorman 0
          >you have to be doing something wrong if your workflow can’t handle you sleeping.

          I’m an EE. My computer often runs overnight simulations or IC routing flows overnight so that I can come back in the morning and use the results. Your SW dev workflow != everyone else’s workflow. The nice thing about computers is that they don’t have to sleep, and can be accomplishing productive work while we sleep.

          In fact, if your computer only sees use during the 40 hours you’re at work, your company is wasting at least 75% of the capability/benefit/value/etc that the computer can provide.

  • Jan RingoÅ¡ 0

    Those functions should’ve been updated to return UINT64 on 64-bit OS, the result is in RAX, which is 64-bit register, anyway.
    MSG struct field ‘time’ also should’ve been extended, the MSG struct contains useless padding anyway. IMHO it wouldn’t break any compatibility.

    Same for GetLastInputInfo. It could’ve been easily extended to support some hypothetical LASTINPUTINFOEX with 64-bit time, and all the developers around the world, who need this functionality, could have avoided doing the complicated, and error-prone, subtractions. But for some infuriating and frustrating reasons, that elude me, Microsoft refuses.

    And not just this. I could easily collect hundreds of such small details that could be described as API rot.

Feedback usabilla icon