Time and Time Agagin

Pop quiz: what does the UNIX time(2) API return?

crazy_clock If you're like most programmers, you probably answered "the number of seconds since the Epoch." The UNIX epoch began on January 1, 1970. time(2) simply returns the number of seconds since then. What could be simpler?

Unfortunately, it's not quite that simple. What time(2) returns is not the number of seconds since the Epoch. It's a representation of the UTC time since the start of the Epoch. That may sound like a distinction without a difference. Are we arguing about the number of angels that can dance on the head of a pin?

The Earth's day is not exactly 24 hours long. It's slightly shorter than that. To make up the difference, leap seconds are periodically injected into UTC time. The next leap second is scheduled to be injected on June 30th, 2012.

Unix time is pegged to UTC. Wikipedia explains how:

The Unix time number is zero at the Unix epoch, and increases by exactly 86400 per day since the epoch...

When a leap second occurs, so that the UTC day is not exactly 86400 s long, a discontinuity occurs in the Unix time number. The Unix time number increases by exactly 86400 each day, regardless of how long the day is. When a leap second is deleted (which has never occurred as of 2010), the Unix time number jumps up by 1 where the leap second was deleted, which is the end of the day. When a leap second is inserted (which has occurred on average once every year and a half), the Unix time number increases continuously during the leap second, during which time it is more than 86 400 s since the start of the current day, and then jumps down by 1 at the end of the leap second, which is the start of the next day.

The article goes on to note that on strictly conforming POSIX.1 systems, the time_t value 915148800 repeated twice in a row at the end of 1998. The same oddity happens after every leap second is injected. time_t repeats itself, like a broken record. For two seconds in a row, time() returns the same value!

The implications are clear. time_t isn't an interval. You can't subtract two time_t values and get the difference in time between them. You can't wait until time() + 5 and be sure that you are waiting exactly 5 seconds.

The Importance of being Punctual

The problems surrounding leap seconds may seem relatively insignificant. After all, most applications are hardly concerned if they waited 5 seconds or 6. The real question you have to ask yourself is, am I feeling lucky? (Well, are you, punk?) However, there are other, bigger problems with the way many programmers use time.

What time(2) returns is often referred to by programmers as wall-clock time. Wall clock time is a representation of what the rest of the world thinks the time is. For example, 6:00pm is a wall clock time.

The problem with wall-clock time is that it can jump all over the place. If the user opens up the system control panel and changes the time, the wall clock time experienced by your program will change abruptly and without warning. If you are running a time synchronization daemon like ntpd, it will also modify the wall-clock time periodically.

To see why this could be a problem, imagine the following sequence of events:

  • Program Foo decides to wait 5 seconds. The programmer tells the computer to sleep until 6:05pm
  • The user changes the time to 5:00pm
  • Program Foo ends up waiting more than an hour, when in fact all it really wanted to do was wait for 5 seconds.
To avoid these and similar problems, you should use monotonic time whenever possible. Unlike wall-clock time, monotonic time is simply the number of seconds since some arbitrary starting point. There are no leap seconds or timezone games, and it never decreases.

The POSIX clock_gettime interface, when used with CLOCK_MONOTONIC, will give you the monotonic time. You can sleep until a time in the future using clock_nanosleep. Condition variables can be configured to use the monotonic time using pthread_cond_attr_setclock.

However, as far as I am aware, there is no way to make pthread_mutex_timedlock or sem_timedwait use the monotonic clock.

Most of the time, programmers aren't interested in sleeping until a specific wall-clock time; they simply want to wait for a certain interval to elapse. Unless you're writing a calendar program, you probably don't want your condition variable timeouts to change when somebody changes the system time. However, with the APIs we have today, that's often the behavior you get.

The future

Hopefully in the future programmers will be more aware of the need for monotonic time, and POSIX will give us more opportunities to use it.

The International Telecommunications Union is considering eliminating leap seconds. If this happens, it will mean an end to the time_t anomaly.