Time is annoyingly complicated. The mess of different civil definitions makes dealing with time in computers unreasonably difficult. And creating useful, portable interfaces is much harder than one would hope.
Unix has a variety of different representations of time. There’s good, old-fashioned time(2) with its time_t — a simple count of seconds since 1970-01-01T00:00:00UTC. There’s struct timeval (from gettimeofday(2) and select(2)), which is just a time_t and a microsecond count. There’s the POSIX invention struct timespec (from pselect(2)), which is just the same, only with nanoseconds, in the optimistic hope that any implementation will care about the difference. And there’s poll(2), which just takes a count of milliseconds. Finally, there are the various timers which deal with things like clock_t, such as clock(3), which returns the amount of CPU time the current process has used, and the somewhat more useful times(2), which fills a structure with the user and system time used by the process and its children (uninteresting) and then returns the actual elapsed time since some arbitrary epoch.
Did you notice the sleight-of-hand? UTC was a very strange beast in 1970, with variable-length seconds and all sorts of horrors. This got fixed in 1972: from then on, UTC seconds are the same length as TAI seconds, but the two drift apart because leap seconds are occasionally (and unpredictably) thrown into UTC to make it consistent with the Earth’s actual rotation. So 1970-01-01T00:00:00UTC isn’t particularly well-defined, though it’s usually assumed that the system is applied retrospectively. The official definition of time_t doesn’t count leap seconds, so this isn’t a disaster; precisely which second has the time_t label 0 is unclear, but it doesn’t actually matter very much.
This means that gettimeofday is useless for measuring time intervals. A leap second means that time labels can be repeated, and intervals which include the leap second (or even its start) are measured incorrectly.
Windows is, of course, a crawling horror; a vile, putrescent sore on our collective and metaphorical backsides. It has a SYSTEMTIME (sorry about the shouting), which is actually broken down into months, days, hours, minutes, etc. (The idea that a ‘system time’ contains the day of the week is wonderfully absurd in itself, but that’s a different rant.) The SYSTEMTIME is, at least, expressed in UTC, but of course a broken-down time is almost useless for doing anything other than formatting. For actual computation, you need to convert it to a FILETIME, which is a 64-bit count of 100 nanosecond intervals since 1601-01-01T00:00:00UTC. Finally, there’s GetTickCount, which just returns a count of milliseconds since boot time.
This is extremely unsatisfactory. Does a FILETIME count leap seconds? (Presumably so, if we take the specification at face value; so we need a database of leap seconds to convert between FILETIME and time_t.) And, perhaps more significantly, what year is it talking about? (1 January hasn’t always been the time of the new year, and certainly wasn’t in 1601. Of course, UTC wasn’t invented then either, so I’ve no idea how many leap seconds ought to have been inserted.) And the SYSTEMTIME is also unclear: the specification clearly states that it returns the time in UTC, but it also constrains the wSecond slot to be an integer between 0 and 59 inclusive, which means that it can’t represent leap seconds and therefore can’t always give the correct UTC time.
Microsoft’s documentation contains stern warnings about not using SYSTEMTIMEs and FILETIMEs for measuring time intervals, since (presumably) they can be changed by capricious or ignorant users. So, if the ticker count wraps, and you can’t trust the real time, what do you do?
And the tick count returned by GetTickCount? It’s 32 bits, so it wraps around every 50 days or so. There’s a 64-bit variant, which won’t wrap for half a gigayear… but it’s only in Vista. (See forthcoming rant about I/O.)
Just in case you thought Unix was any more useful: times returns a clock_t, which is some integer type counting clock ticks. If you want to know how frequently the clock ticks, well, you call sysconf(_SC_CLK_TCK) and that will tell you how many ticks there are per second. If you want to know how long it takes to wrap, then you lose because nothing tells you. (The best you can manage is to compare sizeof(clock_t) with the sizeof various integer types, see whether −(clock_t)1 > 0, and try to correlate this with the various MUMBLE_MIN and MUMBLE_MAX constants from limits.h. But you’ll lose if two different integer types have the same sizeof and signedness but different limits (due to hidden bits or whatever), or if clock_t just isn’t one of the standard integer types — a horrific situation introduced in C99.
Linux provides its own special braindamage as well. Linux syscalls indicate errors by returning a negative errno value. The C library detects this, unnegates the return value, stashes it in errno, and then returns $−1$. Unfortunately, these negative values are perfectly reasonable return values from times(2), so an application has to assume that times(2) doesn’t in fact fail (POSIX at least doesn’t specify any reasons why it might, but doesn’t forbid it from failing), and try to reconstruct the result from errno.
At least on Unix, the problems involved with messing with real time are well known, and the gettimeofday time tends to slew gradually towards reality rather than leaping about.
My actual objective is to keep track of pending timer events and then make them happen at the right times. I’m going to use a double to represent a number of seconds since the Unix epoch. This will give me millisecond precision for about 285 thousand years either side of the epoch, and microsecond precision for about 500 years around it. I’m happy with that.
I think that the right thing to do is to assume that the real time is vaguely accurate — say, to within a few days — and use this to detect wraparound in the monotonic timer which is my actual time reference. But it seems a really complicated way of going about something which, I’d have thought, was relatively routine.
Leave a comment