8

如何在 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 转换或以其他方式了解闰秒。

使用对/?timezone 这似乎可行,但是我不确定实现是否足够聪明以在闰秒期间继续工作,或者 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这样做 的问题是,即使计算机重新启动,时间戳也需要保持有效和可比较。

4

4 回答 4

4

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

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

于 2015-05-07T23:01:55.907 回答
3

我需要这个的原因是能够在很长一段时间内(以年为单位)准确地获取时间戳,并且仍然能够比较它们,而不必担心闰秒。在闰秒期间可能会发生多次测量,并且所有测量都需要是明确的、单调递增的和线性递增的。

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

  1. 程序的时间戳启动并将其与计数器相关联
  2. 使用计数器以固定间隔进行连续测量
  3. 根据需要为新的计数器值加上时间戳,以保持数据充分同步

如果您在可能发生闰秒的 1 秒内避免时间戳记(午夜!),您就可以自由自在,因为这些可以稍后进行调整。

现在,如果您坚持使用不带计数器的 TAI,您所需要的只是一张需要考虑闰秒的表格。然后只需使用单调时间。还有一些库可以为您执行此操作,但它们可能已过时,因此您必须自己维护它们,

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

于 2015-03-30T21:05:57.127 回答
3

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

  • 我写它是为了填补 Java 世界的空白(java.time不能做所有事情),
  • 到目前为止给出的其他答案只谈论 C++(但你也要求 Java),
  • 它的工作原理与@user3427419 描述的相同。

它使用基于System.nanoTime()但甚至允许通过接口自定义实现的单调时钟TickProvider。出于校准的目的,您可以使用net.time4j.SystemClock.MONOTONIC,或者使用名为的 SNTP-clock SntpConnector,它只需要一些简单的配置即可连接到您想要的任何 NTP-time-server。多亏了内置的闰秒表 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-module 都会停止以产生任何并发的闰秒信息。

于 2015-06-12T20:05:33.337 回答
0

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

来自 NTP 的选项 TAI:您的 TAI 实施需要了解闰秒。可能 NTP 协议或引用的资源是当前和未来闰秒最可靠的来源。

来自 GPS 的选项 TAI:GPS 时钟与 TAI 有固定的偏移量,您不必弄乱闰秒

于 2015-03-30T22:53:17.097 回答