Daniel Centore Daniel Centore - 7 days ago 3
C++ Question

How to obtain current TAI time?

How can I obtain the current TAI time in milliseconds in Linux using either Java or C++?

The reason I need this is to be able to accurately take timestamps over a long period of time (on the order of years) and still be able to compare them, without worrying about leap seconds. It is possible for multiple measurements to take place during a leap second and all measurements need to be unambiguous, monotonically increasing, and linearly increasing. This will be a dedicated Linux server. This is for a scientific project which needs precision of about .5 seconds.

I do not currently wish to invest in a GPS timekeeper and hope to use NTP to pool.ntp.org in order to keep the system clock on track.

I have looked into the following solutions:

Java 8 or the ThreeTen Project
The only way to obtain a TAIInstant is to use an Instant and then convert it which, according to the specs, "Conversion from an Instant will not be completely accurate near a leap second in accordance with UTC-SLS." That in and of itself is not a big deal (in fact, using UTC-SLS would also be acceptable). However, using now() in the Instant class also seems to just be a wrapper for System.currentTimeMillis(), which makes me think that during the leap second, the time will still be ambiguous and the project will not actually give me TAI time. The Java 8 specifications also state:


Implementations of the Java time-scale using the JSR-310 API are not
required to provide any clock that is sub-second accurate, or that
progresses monotonically or smoothly
. Implementations are therefore
not required to actually perform the UTC-SLS slew or to otherwise be
aware of leap seconds.


Using a right/? timezone
This seems like it would work, however I am not sure if the implementation is smart enough to continue working during a leap second or if System.currentTimeMillis() would even give TAI time. In other words, would the underlying implementation still use UTC, thus giving an ambiguous time during the leap second which is then converted to TAI, or does using a right/ timezone actually work with TAI using System.currentTimeMillis() always (ie even during leap second)?

Using CLOCK_TAI
I tried using CLOCK_TAI in the Linux kernel but found it to be completely identical to CLOCK_REALTIME in my test:
Code:

#include <iostream>
#include <time.h>

long sec(int clock)
{
struct timespec gettime_now;
clock_gettime(clock, &gettime_now);
return gettime_now.tv_sec;
}

int main()
{
std::cout << sec(0) << std::endl; // CLOCK_REALTIME
std::cout << sec(1) << std::endl; // CLOCK_MONOTONIC
std::cout << sec(11) << std::endl; // CLOCK_TAI

return 0;
}


The output was simply:

1427744797
6896
1427744797


Using CLOCK_MONOTONIC
The problem with this is that the timestamps need to remain valid and comparable even if the computer restarts.

Answer

In addition to the correct accepted answer I would also mention the free Java library Time4J (min version v4.1) as possible solution because

  • I have written it to fill a gap in Java world (java.time cannot do all),
  • other answers given so far only talk about C++ (but you also asked for Java),
  • it works according to the same principles described by @user3427419.

It uses a monotonic clock based on System.nanoTime() but even allows custom implementations via the interface TickProvider. For the purpose of calibration, you can either use net.time4j.SystemClock.MONOTONIC, or you use an SNTP-clock named SntpConnector which just needs some simple configuration to connect to any NTP-time-server you want. And thanks to the built-in leap-second-table Time4J can even show you the announced leap second at the end of this month - in ISO-8601-notation or even as formatted local timestamp string in any timezone (using i18n-module).

A recalibration (in case of NTP - reconnect) of the clocks is possible meaning the clocks can be adapted to intermediate time adjustments (although I strongly recommend not to do it during your measurements or during a leap second). Although such a reconnect of an SNTP clock would normally cause the time stepping back in some cases Time4J tries to apply a smoothing algorithm (if activated in clock configuration) to ensure monotone behaviour. Detailed documentation is available online.

Example:

// Step 0: configure your clock
String ntpServer = "ptbtime1.ptb.de";
SntpConnector clock = new SntpConnector(ntpServer);

// Step 1: Timestamp start of the program and associate it with a counter
clock.connect(); 

// Step 2: Use the counter for sequential measurements at fixed intervals
Moment m = clock.currentTime();
System.out.println(m); // possible output = 2015-06-30T23:59:60,123456789Z

// Step 3: Timestamp new counter value(s) as necessary to keep your data adequately synced
clock.connect();

I doubt if any C++-based solution is more simple. More code demonstrations can also be studied on DZone.


Update (answer to question in comment):

A slightly simplified solution how to automatically download the given IETF-resource for new leap seconds and to translate it into a Time4J-specific format might look like this:

URL url = new URL("https://www.ietf.org/timezones/data/leap-seconds.list");
BufferedReader br =
    new BufferedReader(
        new InputStreamReader(url.openStream(), "US-ASCII"));
String line;
PlainDate expires = null;
Moment ntpEpoch = PlainTimestamp.of(1900, 1, 1, 0, 0).atUTC();
List<PlainDate> events = new ArrayList<PlainDate>();

try {
    while ((line = br.readLine()) != null) {
        if (line.startsWith("#@")) {
            long expraw = Long.parseLong(line.substring(2).trim());
            expires = ntpEpoch.plus(
              expraw, TimeUnit.SECONDS)
            .toZonalTimestamp(ZonalOffset.UTC).toDate();
            continue;
        } else if (line.startsWith("#")) {
            continue; // comment line
        }

        // this works for some foreseeable future
        long epoch = Long.parseLong(line.substring(0, 10)); 

        // this is no leap second 
        // but just the official introduction of modern UTC scale
        if (epoch == 2272060800L) {
            continue;
        }

        // -1 because we don't want to associate 
        // the leap second with the following day
        PlainDate event = 
          ntpEpoch.plus(epoch - 1, TimeUnit.SECONDS)
                  .toZonalTimestamp(ZonalOffset.UTC).toDate();
        events.add(event); // we don't assume any negative leap seconds here for simplicity
    }
} finally {
    br.close();
}

// now let's write the result into time4j-format
// use a location relative to class path of main program (see below)
String path = "C:/work/leapseconds.txt"; 
Writer writer = new FileWriter(new File(path));
String sep = System.getProperty("line.separator");

try {
    for (PlainDate event : events) {
        writer.write(event + ", +" + sep);
    }
    writer.write("@expires=" + expires + sep);
} finally {
    writer.close();
}

System.out.println(
  "Leap second file was successfully written from IETF-resource.");

// And finally, we can start the main program in a separate process
// with the system property "net.time4j.scale.leapseconds.path"
// set to our leapsecond file path (must be relative to class path)

Some notes:

I recommend to write this code as subprogram called by a simple batch program in order to avoid the main program being dependent on internet connectivity. This batch file would finally call the main program with the mentioned system property. If you set this property then the leap seconds will be read from the file specified there, and any eventually available tzdata-module would then stop to yield any concurrent leap second informations.