如何获取当前TAI时间



如何在Linux中使用Java或C++获得当前TAI时间(以毫秒为单位)?

我需要这一点的原因是能够在很长一段时间内(以年为单位)准确地获取时间戳,并且仍然能够对它们进行比较,而不必担心闰秒。在一闰秒内可以进行多次测量,并且所有测量都必须是明确的、单调递增的和线性递增的。这将是一个专用的Linux服务器。这是一个科学项目,需要大约0.5秒的精度。

我目前不想投资GPS计时器,希望使用NTP来pool.NTP.org,以保持系统时钟正常运行。

我研究了以下解决方案:

Java 8或ThreeTen项目获得TAIInstant的唯一方法是使用Instant,然后对其进行转换,根据规范,"根据UTC-SLS,从Instant的转换在闰秒附近不会完全准确。"这本身并不是什么大不了的事(事实上,使用UTC-SLS也是可以接受的)。然而,在Instant类中使用now()似乎只是System.currentTimeMillis()的包装器,这让我认为在闰秒期间,时间仍然是模糊的,项目实际上不会给我TAI时间。Java 8规范还规定:

使用JSR-310 API实现Java时间尺度不是需要提供亚秒精度的任何时钟,或者进展单调或平稳。因此,实施不需要实际执行UTC-SLS转换或以其他方式注意闰秒。

使用右/?时区这看起来是可行的,但我不确定实现是否足够智能,可以在闰秒内继续工作,或者System.currentTimeMillis()是否会给TAI时间。换言之,底层实现是否仍然使用UTC,从而在闰秒期间给出一个不明确的时间,然后将其转换为TAI,或者使用右侧/时区是否总是使用System.currentTimeMillis()与TAI一起工作(即即使在闰秒)?

使用CLOCK_TAI我尝试在Linux内核中使用CLOCK_TAI,但在测试中发现它与CLOCK_REALTIME完全相同:代码:

#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;
}

结果很简单:

1427744797
6896
1427744797

使用CLOCK_MONOTONIC这样做的问题是,即使计算机重新启动,时间戳也需要保持有效和可比较。

CLOCK_REALTIMECLOCK_TAI返回相同的值,因为内核参数tai_offset为零。

使用adjtimex(timex tmx)进行检查并读取值。我认为如果它足够新(>4.2.6)并且有一个闰秒文件,ntpd会设置它。它也可以从上游服务器获得,但我还无法验证。调用adjtimex()可以在以root身份运行时手动设置tai_offsetadjtimex需要一个新的ish man页面来查看要设置的参数。我的debian man页面太旧了,但命令有效。

除了正确接受的答案外,我还提到免费的Java库Time4J(最低版本v4.1)作为可能的解决方案,因为

  • 我写它是为了填补Java世界中的一个空白(java.time不能做到所有)
  • 到目前为止给出的其他答案只涉及C++(但您也要求使用Java)
  • 它的工作原理与@user3427419描述的原理相同

它使用基于System.nanoTime()的单调时钟,但甚至允许通过接口TickProvider进行自定义实现。为了进行校准,您可以使用net.time4j.SystemClock.MONOTONIC,也可以使用名为SntpConnector的SNTP时钟,它只需要一些简单的配置即可连接到您想要的任何NTP时间服务器。由于内置的闰秒表,Time4J甚至可以在本月底以ISO-8601表示法或任何时区的格式化本地时间戳字符串(使用i18n模块)向您显示宣布的闰秒。

时钟的重新校准(在NTP的情况下-重新连接)是可能的,这意味着时钟可以适应中间时间调整(尽管我强烈建议在测量期间或闰秒期间不要这样做)。尽管在某些情况下,SNTP时钟的这种重新连接通常会导致时间倒退,但Time4J试图应用平滑算法(如果在时钟配置中激活)以确保单调行为。详细文档可在线获取。

示例:

// 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();

我怀疑是否有任何基于C++的解决方案更简单。还可以在DZone上研究更多的代码演示。


更新(回答评论中的问题):

如何自动下载给定IETF资源以获得新的闰秒并将其转换为Time4J特定格式的稍微简化的解决方案可能如下所示:

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)

一些注意事项:

我建议将此代码作为简单批处理程序调用的子程序编写,以避免主程序依赖于互联网连接。这个批处理文件最终将调用具有上述系统属性的主程序。如果设置此属性,则闰秒将从指定的文件中读取,并且任何最终可用的tzdata模块都将停止以产生任何并发的闰秒信息。

我需要它的原因是能够准确地获取时间戳在很长一段时间内(大约几年)仍然能够比较它们,而不用担心闰秒。这是可能的在一个闰秒内进行多次测量测量需要是明确的、单调递增的,并且线性增加。

那么你的设计是次优的。你不能利用时间,然后以某种方式干预闰秒。事实上,这种情况经常出现,人们也陷入了使用挂钟进行时间戳测量的陷阱。

  1. 时间戳程序的开始并将其与计数器关联
  2. 使用计数器以固定间隔进行顺序测量
  3. 根据需要对新计数器值进行时间戳,以保持数据充分同步

如果你在第二秒可能发生的1秒(午夜!)内避免了时间戳,你就可以自由回家了,因为这些时间可以稍后调整。

现在,如果你坚持使用没有计数器的TAI,你所需要的只是一个需要考虑闰秒的表。然后只使用单调时间。还有一些库可以为你做这件事,但它们可能已经过时了,所以你必须自己维护它们,

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

您必须实现基于C++std::steady_clock或类似功能的TAI时钟。要同步您的TAI时钟,您可以依靠GPS或NTP。

NTP的选项TAI:您的TAI实现需要有关闰秒的知识。NTP协议或参考资源可能是当前和未来闰秒的最可靠来源。

选项TAI从GPS:GPS时钟有一个固定的偏移到TAI,你不必干扰闰秒

相关内容

  • 没有找到相关文章

最新更新