Is there a way of getting correct CLOCK_TAI on Linux?
I think you want clock_gettime
with CLOCK_TAI
to work properly. So did I.
The critical sentence in the referenced answer is: "Please note that the offset from CLOCK_REALTIME is initialized on boot to zero and neither ntpd nor chronyd set it by default to the correct value (currently 35)."
This may still be true, apart from the offset now being 37, but a recent ntpd can at least be configured to set the offset. I did the following on an openSUSE machine:
vi /etc/ntp.conf # Add the line: leapfile /var/lib/ntp/etc/ntp.leapseconds
update-leap
service ntpd restart
less /var/log/ntp # Check for errors
Then clock_gettime(CLOCK_TAI, &res)
seemed to work correctly.
I think that ntp sets the offset using ntp_adjtime
with MOD_TAI
. Searching the chrony source with grep -P '(ADJ|MOD)_TAI'
finds no matches, so it seems that chrony does not yet have this capability.
You can use libtai
from djb: https://cr.yp.to/libtai.html
What is it?
libtai is a library for storing and manipulating dates and times.
libtai supports two time scales: (1) TAI64, covering a few hundred billion years with 1-second precision; (2) TAI64NA, covering the same period with 1-attosecond precision. Both scales are defined in terms of TAI, the current international real time standard.
libtai provides an internal format for TAI64, struct tai, designed for fast time manipulations. The tai_pack() and tai_unpack() routines convert between struct tai and a portable 8-byte TAI64 storage format. libtai provides similar internal and external formats for TAI64NA.
libtai provides struct caldate to store dates in year-month-day form. It can convert struct caldate, under the Gregorian calendar, to a modified Julian day number for easy date arithmetic.
libtai provides struct caltime to store calendar dates and times along with UTC offsets. It can convert from struct tai to struct caltime in UTC, accounting for leap seconds, for accurate date and time display. It can also convert back from struct caltime to struct tai for user input. Its overall UTC-to-TAI conversion speed is 100x better than the usual UNIX mktime() implementation.
This version of libtai requires a UNIX system with gettimeofday(). It will be easy to port to other operating systems with compilers supporting 64-bit arithmetic.
The libtai source code is in the public domain.
As I am running chrony
instead of the old ntpd
, I didn't have an automated way of getting the kernel parameter right, so I looked into an alternative.
As the offset between TAI and UTC is relatively constant (changes < once per year) it is possible to statically set the kernel parameter, and then using the CLOCK_TAI clock in an application will give the correct value.
There is a test application for setting the kernel offset in the kernel sources, in tools/testing/selftests/timers/set-tai.c
. And, assuming you have the tzdata
package installed, there is a file with the offset between UTC and TAI in /usr/share/zoneinfo/leap-seconds.list
.
I chopped down the kernel test application so the main became:
int main(int argc, char **argv)
{
int i, ret;
ret = get_tai();
printf("tai offset started at %i\n", ret);
if (argc < 2)
{
printf("New offset not given, not setting\n");
}
else
{
i = strtol(argv[1],NULL,10);
printf("Attempting to set TAI offset to %d\n",i);
printf("Checking tai offsets can be properly set: ");
ret = set_tai(i);
ret = get_tai();
if (ret != i) {
printf("[FAILED] expected: %i got %i\n", i, ret);
return EXIT_FAILURE;
}
}
printf("[OK]\n");
return EXIT_SUCCESS;
}
Then, for my use case, it was just a matter of extracting the correct value from the leap-seconds.list
file and running set-tai
with this as a parameter (in /etc/rc.local
to get it to happen at boot time). An example way of doing this is:
TAI_OFFSET=$(grep -v '^#' /usr/share/zoneinfo/leap-seconds.list | tail -1 | awk '{ print $2 }')
if [ -x /usr/local/sbin/set-tai ]; then
/usr/local/sbin/set-tai $TAI_OFFSET
fi
Hope this is useful to someone else!