The tai library interface

The following functions are declared in the skalibs/tai.h header, and implemented in the libskarnet.a or library.

General information

tai is a set of data structures and primitives to represent and perform operations on time.

The point of tai is to handle time without ever having to deal with annoyances such as Y2K, Y2038, NTP limits, non-linear clocks, and the like. By using the tai interface, you ensure your program will behave properly no matter what.

What is the problem ?

The standard APIs for time management under Unix are broken in more or less subtle ways. The most obvious thing is that they do not pass year 2038. A less obvious problem is that they do not handle leap seconds correctly. Here are a few references you should read to understand what is going on:

The meat and potatoes of all this is that programmers cannot simply rely on standard Unix APIs such as gettimeofday() (which, by the way, is marked as obsolescent, but it's not going to disappear tomorrow) to measure time intervals or even to give precise absolute time, and in any case those APIs will become obsolete in 2038.

So what does tai do ?

tai implements - among other things - the TAI64 and TAI64N formats, which are used in all of skalibs. This gives a programmer access to precise linear absolute time, which is suitable for both timestamping (wallclock usage) and time interval measurements (stopwatch usage). Additionally, TAI64 passes Y2038 (it can represent dates exceeding the estimated lifespan of the universe).

tai has been inspired by Dan J. Bernstein's libtai library, but does not borrow any code from it.

Data structures

A tai structure holds an absolute date with a one-second precision. A tain structure holds an absolute date with a maximum of one-nanosecond precision, as permitted by the underlying system call. If flag-usert is clear, the system clock will be read via gettimeofday() system call, which has a one-microsecond precision; if it is set, the system clock will be read via the clock_gettime() system call, which has a one-nanosecond precision. In either case, a current tain will be unable to be more precise than the underlying implementation.

A tai, as well as a tain, can also hold a (possibly negative) relative time, i.e. a difference of absolute dates. It is up to the programmer to make sure that a relative time is never interpreted as an absolute TAI64 date, and vice-versa.


Wallclock operations

int tai_now (tai *t)
Writes the current time as a TAI value to *t, with a 1-second precision. The current time is based on the system clock. Make sure skalibs has been compiled with or without the --enable-tai-clock configure option according to your system clock synchronization method: skalibs supports a system clock set to TAI-10 or to UTC. The function returns 1 if it succeeds, or 0 (and sets errno) if it fails.

int sysclock_get (tain *a)
Reads the current value of the system clock (as in CLOCK_REALTIME) into *a. Returns 1 if it succeeds or 0 (and sets errno) if it fails. Note that despite being a tain, *a does not contain a TAI value - it only contains an internal, Y2038-safe representation of the value of the system clock, which should be either TAI-10 or UTC. You should not use this function directly unless you know exactly what you are doing.

int sysclock_set (tain const *a)
Sets the system clock to *a, provided *a has the correct internal representation. You should not use this function directly unless you know exactly what you are doing.

int tain_wallclock_read (tain *a)
Reads the current time into *a, as a TAI64N value. Returns 1 if it succeeds or 0 (and sets errno) if it fails. Here a contains a valid TAI64N stamp, no matter what the system clock is set to: arithmetic operations can be performed on it.

int tain_setnow (tain const *a)
Sets the current time to *a. Returns 1 if it succeeds or 0 (and sets errno) if it fails. a must contain a valid TAI64N stamp; proper operations will be automatically run to convert that stamp into the right format for the system clock.

int tain_now_set_wallclock (tain *a)
Tells skalibs that future invocations of tain_now() (see below) should use a wall clock, i.e. the system time as returned by clock_gettime(CLOCK_REALTIME) or gettimeofday(). Also reads the current time into *a. Returns 1 if it succeeds or 0 (and sets errno) if it fails. A wall clock is the default: it is not necessary to call this function before invoking tain_now() at the start of a program.

Stopwatch operations

The following two operations can only succeed if your system provides the clock_gettime() primitive with at least one of the CLOCK_MONOTONIC or CLOCK_BOOTTIME clocks. Otherwise, they will fail with errno set to ENOSYS.

int tain_stopwatch_init (tain *a, clock_t cl, tain *offset)
Initializes a stopwatch in *offset, using a clock named cl. Typically, cl is something like CLOCK_MONOTONIC, when it is defined by the system. The actual value of *offset is meaningless to the user; offset's only use is to be given as a second parameter to tain_stopwatch_read(). The function returns 1 if it succeeds or 0 (and sets errno) if it fails. On success, the current time, as given by the system clock (a wall clock), is returned in *a.

What tain_stopwatch_init() does is synchronize the "stopwatch clock" to the system clock. Right after tain_stopwatch_init() has been called, the absolute times given by tain_stopwatch_read() and tain_wallclock_read() are the same. Then, depending on the accuracy of the system clock, a drift may appear; calling tain_stopwatch_init() again resets that drift to zero.

int tain_stopwatch_read (tain *a, clock_t cl, tain const *offset)
Gives the absolute time, as a TAI64N value, in *a. This absolute time is computed as a linear increase (as measured with the cl clock, which should be a monotonic clock such as CLOCK_MONOTONIC) since the last time tain_stopwatch_init() was called with parameter offset. tain_stopwatch_read() guarantees precise time interval measurements; however, the time it gives can slightly differ from the result of tain_wallclock_read(). The function returns 1 if it succeeds or 0 (and sets errno) if it fails.

int tain_now_set_stopwatch (tain *a)
Tells skalibs that future invocations of tain_now() (see below) should use a stopwatch, i.e. the system time as returned by clock_gettime(CLOCK_MONOTONIC) or similar, if supported by the system. This is useful when it is more important for a program to compute unchanging time intervals no matter what the system clock does, than to display absolute time that is in sync with a human view of time (which is the cause and reason of most system clock jumps).
If no monotonic clock is supported by the system, this function does not change what tain_now() refers to (i.e. it will keep referring to the system clock).
Returns 1 on success and 0 (and sets errno) on failure. On success, the current time, as given by the system clock (a wall clock), is returned in *a.

All-purpose time reading

int tain_now (tain *a)
Writes the current time, as a TAI value, to *a. This is the function you should use by default. It returns 1 if it succeeds or 0 (and sets errno) if it fails.

tain_now() relies on the concept that there is One True Time Source for the process, and that is where it reads time from. By default, the One True Time Source is the system clock (a wall clock), and tain_now() is actually an alias to tain_wallclock_read(). At the start of a program, calling tain_now_set_stopwatch() will define a monotonic clock (if supported by the system) as the One True Time Source, which will make tain_now() resistant to system clock jumps, but will also make it unsuitable for timestamping.

In other words: the first time you need to read the clock, or at the start of your program, you should use tain_now_set_wallclock() or tain_now_set_stopwatch() depending on whether you want the One True Time Source to be the system clock (CLOCK_REALTIME or gettimeofday()) or a stopwatch (CLOCK_MONOTONIC, if supported). Afterwards, every time you need to read from that time source, use tain_now(). skalibs functions that may block, such as iopause_g, internally call tain_now() to update the timestamp they're using, so they will use the time source you have defined. (Functions ending in _g use the STAMP global variable to store the current timestamp.)

Converting to/from libc representations

The following functions only convert from a certain structure format to another; they do not make any assumption about the format of the time contained in those structures. For instance, for the tai_from_timeval function, if the struct timeval contains an absolute UTC time, then the tai will also contain the same UTC time. Despite being a tai, it may contain something else than TAI time.

If you need conversion from the native machine system clock format to TAI, see the next section.

int tai_from_time (tai *t, time_t u)
int tai_relative_from_time (tai *t, time_t u)

Those functions convert an absolute (resp. relative) time in a time_t to an absolute (resp. relative) time in a tai, with a 1-second precision. They return 1, unless the time_t value is invalid (in which case they return 0).

int time_from_tai (time_t *u, tai const *t)
int time_from_tai_relative (time_t *u, tai const *t)

The inverse functions of the previous ones. Be aware that time_t is 32 bits on some systems and cannot store all values of a tai (in which case the functions will return 0 EOVERFLOW).

int tain_from_timeval (tain *a, struct timeval const *tv)
int tain_relative_from_timeval (tain *a, struct timeval const *tv)
int tain_from_timespec (tain *a, struct timespec const *ts)
int tain_relative_from_timespec (tain *a, struct timespec const *ts)
int timeval_from_tain (struct timeval *tv, tain const *a)
int timeval_from_tain_relative (struct timeval *tv, tain const *a)
int timespec_from_tain (struct timespec *ts, tain const *a)
int timespec_from_tain_relative (struct timespec *ts, tain const *a)

Same conversion operations, between tain and a struct timeval or struct timespec. The 1-microsecond (for struct timeval) or 1-nanosecond (for struct timespec) precision is preserved.

Conversion between TAI and the system clock format

Unlike the previous functions, the functions listed here will always operate on valid absolute timestamps. Only TAI64 time is stored in tai structures, and only TAI64N time is stored in tain structures. These functions will convert to/from TAI, from/to the machine system clock format, i.e. TAI-10 or UTC depending on whether skalibs was compiled with the --enable-tai-clock configure option). This is useful to get valid TAI/TAI64N timestamps out of information exported by the system, for instance the time_t returned by time(), or in the st_atim, st_mtim or st_ctim fields of a struct stat.

The functions return 1 in case of success, or 0 if the conversion could not be performed; in which case errno is set to EINVAL if the input argument was not a valid timestamp, to EOVERFLOW if the output could not be represented in the chosen format (which may happen on systems with a 32 bit time_t), or other error codes.

int tai_from_time_sysclock (tai *a, time_t t)
int time_sysclock_from_tai (time_t *t, tai const *a)
int tain_from_timeval_sysclock (tain *a, struct timeval const *tv)
int timeval_sysclock_from_tain (struct timeval *tv, tain const *a)
int tain_from_timespec_sysclock (tain *a, struct timespec const *ts)
int timespec_sysclock_from_tain (struct timespec *ts, tain const *a)

Conversions to/from basic types

int tain_uint (tain *a, unsigned int c)
Stores a relative time of c seconds into a. Normally returns 1, but may return 0 EINVAL on pathological numbers.

int tain_from_millisecs (tain *a, int ms)
This function makes a tain representing a relative time of ms milliseconds. ms must be non-negative. The function returns 1, unless ms is negative, in which case it returns 0 EINVAL.

int taino_millisecs (tain const *a)
If *a contains a non-negative relative time that fits into a 31-bit integer number of milliseconds, the function returns that number. Else it returns -1 EINVAL.

Time computations

int tai_add (tai *t, tai const *t1, tai const *t2)
Stores *t1 + *t2 into t. Of course, *t1 and *t2 must not both represent absolute times. The function normally returns 1, but will return 0 on bad inputs.

int tai_sub (tai *t, tai const *t1, tai const *t2)
Stores *t1 - *t2 into t. *t1 cannot be relative if *t2 is absolute. If they are both relative or both absolute, then *t is relative, else it's absolute. The function normally returns 1, but will return 0 on bad inputs.

int tain_add (tain *a, tain const *a1, tain const *a2)
int tain_sub (tain *a, tain const *a1, tain const *a2)

Same thing with tain.

int tain_addsec (tain *a, tain const *a1, int c)
Adds c seconds to *a1 and stores the result into a. c may be negative.

void tain_half (tain *a, tain const *b)
Stores *b/2 into a. *b must be relative.

Comparing dates or durations

int tai_less (tai const *t1, tai const *t2)
int tain_less (tain const *t1, tain const *t2)

Those functions return nonzero iff *t1 is lesser than *t2. *t1 and *t2 must be both relative, or both absolute.

Packing and unpacking

void tai_pack (char *s, tai const *t)
Marshals *t into the buffer s points to, which must be preallocated with at least TAI_PACK (8) characters. Afterwards, the buffer contains the external TAI64 format representation of *t.

void tai_unpack (char const *s, tai *t)
Unmarshals the external TAI64 format label pointed to by s (at least TAI_PACK characters) and stores the result into t.

void tain_pack (char *s, tain const *a)
void tain_unpack (char const *s, tain *a)

Same thing with external TAI64N format, using TAIN_PACK (12) characters.

Formatting and scanning

unsigned int tain_fmt (char *s, tain const *a)
Writes into s an ASCII representation of *a in external TAI64N format. s must point to a preallocated buffer of at least TAIN_PACK*2 (24) characters. The function returns the number of bytes that have been written to s (24).

unsigned int tain_scan (char const *s, tain *a)
Reads 24 characters from s; if those characters are a valid ASCII representation of the external TAI64N format of some time value, this value is stored into a, and 24 is returned. Else 0 is returned.


A TAI64N timestamp is a string of 25 characters: a single '@' character followed by the ASCII representation of the TAI64N external format of an absolute date.

unsigned int timestamp_fmt (char *s, tain const *a)
Writes a TAI64N timestamp representing the absolute date *a into the 25 characters pointed to by s. Returns 25.

unsigned int timestamp_scan (char const *s, tain *a)
Reads 25 characters at s. If those characters are a valid TAI64N timestamp, stores the absolute date in a and returns 25. Else, returns 0.

int timestamp (char *s)
Writes the current time (read from the system clock) as a TAI64N timestamp into s. Returns 1 if it succeeds or 0 (and sets errno) if it fails.

TAI64N timestamps are an efficient, robust, and easy-to-use way of timestampping log lines. They're easy to recognize in automatic data parsers. Log files where every line starts with a TAI64N timestamp can be merged and alphanumerically sorted: the resulting file will be chronologically sorted.

The s6 package provides tools to convert TAI64N timestamps into human-readable dates. Please do not embed human-readable dates in your log files, thus making parsing tools unnecessarily hard to write; use TAI64N timestamps instead, design tools that can parse them, and translate them to human-readable form at human analysis time.