Not all time is created equal
While attempting to schedule execution for an exact point in the future, using libdispatch
available on iOS and OSX, we came across a quirk when trying to compare two derived times. When most people think of time on a UNIX system, we talk in terms of seconds since the UNIX epoch, i.e. the number of seconds since 1st January, 1970. Indeed, as a UNIX-compliant system, OSX provides the helpful functions time
and gettimeofday
to do exactly that for us:
NAME
gettimeofday, settimeofday – get/set date and time
SYNOPSIS
#include <sys/time.h>
int
gettimeofday(struct timeval *restrict tp, void *restrict tzp);
int
settimeofday(const struct timeval *tp, const struct timezone *tzp);
DESCRIPTION
The system's notion of the current Greenwich time and the current time zone is obtained with the gettimeofday() call, and set
with the settimeofday() call. The time is expressed in seconds and microseconds since midnight (0 hour), January 1, 1970.
The functions available to us through libdispatch
, dispatch_time()
and dispatch_walltime()
, operate on an opaque type called dispatch_time_t
, ultimately defined as a uint64_t
. Let's assume that we start with a nicely defined UNIX epoch that we need to convert into a dispatch_time_t
.
The following code retrieves the current time in the relevant format, sleeps for a second, and then retrieves the time again. For the sake of brevity, all error-checking has been removed.
void get_initial_timespec(struct timespec* const __nonnull t)
{
struct timeval now;
gettimeofday(&now, NULL);
t->tv_sec = now.tv_sec;
t->tv_nsec = now.tv_usec * 1000;
}
int main()
{
struct timespec initialTimeSpec = { 0 };
get_initial_timespec(&initialTimeSpec);
sleep(1);
struct timespec laterTimeSpec = { 0 };
get_initial_timespec(&laterTimeSpec);
printf("Later UNIX time (%ld) %s initial UNIX time (%ld)\n",
laterTimeSpec.tv_sec,
laterTimeSpec.tv_sec < initialTimeSpec.tv_sec ? "<" : ">=",
initialTimeSpec.tv_sec);
}
The short program above gives the following output (at the time of writing):
Later UNIX time (1646764561) >= initial UNIX time (1646764560)
Seems legit. Now, if we convert those times to their dispatch_time_t
equivalents:
dispatch_time_t initialDispatchTime = dispatch_walltime(&initialTimeSpec, 0);
dispatch_time_t laterDispatchTime = dispatch_walltime(&laterTimeSpec, 0);
printf("Later dispatch time (%llu) %s initial dispatch time (%llu)\n",
laterDispatchTime,
laterDispatchTime < initialDispatchTime ? "<" : ">=",
initialDispatchTime);
And run it, we see some very curious output:
Later dispatch time (16799979511846996616) < initial dispatch time (16799979512851579616)
But how can that be? As is almost always the case, the answer is actually right there in the relevant man
page for dispatch_time
:
NAME
dispatch_time, dispatch_walltime – Calculate temporal milestones
SYNOPSIS
#include <dispatch/dispatch.h>
static const dispatch_time_t DISPATCH_TIME_NOW = 0ull;
static const dispatch_time_t DISPATCH_WALLTIME_NOW = ~1ull;
static const dispatch_time_t DISPATCH_TIME_FOREVER = ~0ull;
dispatch_time_t
dispatch_time(dispatch_time_t base, int64_t offset);
dispatch_time_t
dispatch_walltime(struct timespec *base, int64_t offset);
DESCRIPTION
The dispatch_time() and dispatch_walltime() functions provide a simple mechanism for expressing temporal milestones for use
with dispatch functions that need timeouts or operate on a schedule.
The dispatch_time_t type is a semi-opaque integer, with only the special values DISPATCH_TIME_NOW, DISPATCH_WALLTIME_NOW and
DISPATCH_TIME_FOREVER being externally defined. All other values are represented using an internal format that is not safe
for integer arithmetic or comparison. The internal format is subject to change.
[...]
The part of particular relevance to us here - values are represented using an internal format that is not safe for integer arithmetic or comparison
- is what scuppered us. Performing any arithmetic reasoning on the difference between two dispatch_time_t
values is fundamentally not possible. Use them for scheduling and timing, but do not attempt to reason about the difference between them.
Not all time is created equal!