8

My Linux machine supports CLOCK_TAI, but its offset from CLOCK_REALTIME is incorrectly zero (this is the default). Is there software or other solution that will maintain CLOCK_TAI as TAI?

From answers here and here, it seems neither ntpd nor chronyd do this.

I'd be happy to set my hardware clock to TAI to achieve this, provided CLOCK_REALTIME and gettimeofday etc. return POSIX time (essentially UTC).

3 Answers 3

4

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.

1
3

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.

3
  • I think this is just a library for converting time formats. I'm looking for a program that will adjust the Linux clocks. Dec 20, 2016 at 9:16
  • Your question title says "getting TAI" it does not say "use TAI for my clock" or "set system to TAI." When you mention setting the clock to TAI in the body it seemed like you were amenable to this option if it let you "get correct TAI from system."
    – dfc
    Dec 20, 2016 at 9:22
  • I'm asking about the Linux clock CLOCK_TAI. Dec 20, 2016 at 22:29
3

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

Please be aware that for the above snippet to work correctly the leap-seconds.list file is assumed to have the currently valid TAI_OFFSET at the last non-comment type line. If ever more current lines are in the file or other than current date offsets are in question then some more advanced parsing method needs to be used.

Hope this is useful to someone else!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.