There are several ways to get a number that represents "the time right now."  Some of them wrap; some of them don't.

Some that wrap:

Some that don't:

  • time()  (well, this will wrap in 2038... unless you use the 64-bit version... which is the default...)
  • DateTime.Now

A common programming meme is to start an operation and then wait for it to complete... but give up after a certain preset "timeout."  There are multi-threaded ways of doing this that are perhaps better, but for better or worse, there is a fair amount of code out there that looks like this:

StartSomething();

while ( ! Done() ) {
    if ( BeenWaitingTooLong(Timeout) ) { break; }

    WaitABit();
}

if ( Done() ) {
    Great();
} else {
    Darn();
}

For this post I want to focus on the BeenWaitingTooLong(Timeout) snippet emphasized above.  If the timing function being used wraps, it is all too easy to get this to work most of the time... when it's just as easy to get it to work all of the time.  Alas, the consequences of software that works most of the time tend to be more severe than software that never works.

I wouldn't last long in the banking business being accurate most of the time! -- The Music Man

I'll use GetTickCount() for these examples, but I want to emphasize that the same malady affects all of the wrappy time counters.

Here are some different ways to do it:

StartSomething();

DWORD msTimeout = 10 * 1000; // give up after ten seconds
DWORD msAtStart = GetTickCount();

// assume Done() always returns within, say, a day
while ( ! Done() ) {
    if (
        // most of the time, these are the same.
        // which one will always work?
        // GetTickCount() - msAtStart > msTimeout
        // GetTickCount() - msTimeout > msAtStart
        // GetTickCount() > msAtStart + msTimeOut
    ) { break; }

    // assume that WaitABit() always returns within, say, a day
    WaitABit();
}

if ( Done() ) {
    Great();
} else {
    Darn();
}

The correct answer is:

GetTickCount() - msAtStart > msTimeout

The other two will work most of the time, but will occasionally fail.

There's an easy rule I use to always remember the right one.

When dealing with a clock that wraps, only compare intervals.

Allow me to redecorate my variable names using Joel Spolsky's "Making Wrong Code Look Wrong" technique.

StartSomething();

DWORD intervalTimeout = 10 * 1000; // give up after ten seconds
DWORD instantStart = GetTickCount();

// assume Done() always returns within, say, a day
while ( ! Done() ) {
    if (
        // which one will always work?
        // GetTickCount() - instantAtStart > intervalTimeout
        // GetTickCount() - intervalTimeout > instantAtStart
        // GetTickCount() > instantAtStart + intervalTimeout

    ) { break; }

    // assume that WaitABit() always returns within, say, a day
    WaitABit();
}

if ( Done() ) {
    Great();
} else {
    Darn();
}

Some thought experiments immediately fall out of this.

  • instantA + intervalX = instantB (instantB is later than instantA)
  • instantA - intervalX = instantB (instantB is earlier than instantA)
  • instantB - instantA = intervalX  (instantB is later than instantA)
  • intervalX - intervalY = intervalZ (intervalZ < intervalX; intervalY < intervalX)
  • intervalX + intervalY = intervalZ (intervalZ > intervalX; intervalZ > intervalX)
Some "it is wrong because it looks wrong" rules fall out of this too...
  • instantA + instantB is always a bug due to wrapping
    • Yes, even in (instantA + instantB) / 2
    • Correct version is instantA + (instantB - instantA) / 2
  • instantA < instantB is always a bug
Once you get used to applying the Hungarian in your head, you can catch all sorts of bugs... and avoid introducing them yourself :-)

A word of warning... this isn't foolproof.  The assumptions about Done() and WaitABit() returning reasonably promptly are very important.  If your intervals start getting large on the wrapping scale, things start getting very difficult... I personally recommend avoiding that situation by switching to another way of measuring time that can handle the kind of intervals you need.