8

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.

Daniel Centore
  • 2,968
  • 1
  • 15
  • 35

4 Answers4

4

CLOCK_REALTIME and CLOCK_TAI return the same because the kernel parameter tai_offset is zero.

Check by using adjtimex(timex tmx) and read the value. I think that ntpd will set it if it is new enough (>4.2.6) and has a leap second file. It may also be able to get it from upstream servers but I haven't been able to verify. The call adjtimex() can set tai_offset manually when run as root. You will need a new-ish man page for adjtimex to see the parameters to set. My debian man page was too old but the command worked.

ptierno
  • 7,870
  • 2
  • 19
  • 33
3

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.

Then your design is suboptimal. You cannot use time and then somehow meddle through leap seconds. This actually comes up often enough and people fall into the same trap of timestamping measurements using wall clock.

  1. Timestamp start of the program and associate it with a counter
  2. Use the counter for sequential measurements at fixed intervals
  3. Timestamp new counter value(s) as necessary to keep your data adequately synced

If you avoid timestamping for the 1 second that leapsecond can occur (midnight!), you are home free because those can be adjusted later.

Now if you insist on using TAI without counter, all you need is a table with leap seconds that need to be accounted for. Then just use monotonic time. There is also libraries that can do this for you, but they may be out of date so you'll have to maintain them yourself,

http://skarnet.org/software/skalibs/libstddjb/tai.html

user3427419
  • 1,540
  • 8
  • 13
3

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.

Meno Hochschild
  • 38,305
  • 7
  • 88
  • 115
  • How does your library handle leap seconds over the long term? In other words, if an application is restarted several years in the future, how can I make sure it has knowledge of leap seconds that have occurred over the past few years? – Daniel Centore Oct 13 '15 at 01:46
  • @DanielCentore New leap seconds can be imported by updating your leap second data yourself - either via a new version of the library or via updating the tzdata-module (in fact importing a new tzdb-version from iana.org/tz). Time4J also manages an [expiration flag](http://time4j.net/javadoc-en/net/time4j/scale/LeapSeconds.html#getDateOfExpiration--) which could be used for programming alerts to the sysadmin if the leap second data has become old. Remember the fact that it is not possible to keep the data more than 6 months in advance in a valid state. – Meno Hochschild Oct 13 '15 at 11:54
  • Is there a recommended way to simply handle this automatically, by automatically downloading the resources from the internet? – Daniel Centore Oct 13 '15 at 17:34
  • @DanielCentore I am not aware of any Java library which automatically downloads and installs the latest tzdb-version. But I can imagine that it is possible to write a specialized `net.time4j.tz.ZoneProvider` which does exactly that and will logically replace the tzdata-module of Time4J. This is not a small programming task and a little bit out of scope of any time library since it would include a lot of specialized internet connection code, unpacking tar-files and possibly break if IANA decides to move or change its download resources in the future. – Meno Hochschild Oct 13 '15 at 18:58
  • What if I were to download the IETF file ( https://www.ietf.org/timezones/data/leap-seconds.list ) and parse for leap seconds at program start? I'm just not sure what the recommended way to simply add leap second knowledge would be. Updating other time zone info is not particularly important to me. – Daniel Centore Oct 14 '15 at 02:22
0

You have to implement a TAI clock based on C++ std::steady_clock or similar. To synchronize your TAI clock you could rely on GPS or NTP.

Option TAI from NTP: Your TAI implementation would need knowledge about leap seconds. Probably NTP protocol or referenced resources are the most reliable sources of current and future leap seconds.

Option TAI from GPS: GPS clock has a fixed offset to TAI, you do not have to mess with leap seconds

stefan
  • 3,383
  • 13
  • 25