如何获得当前的TAI时间?

时间:2015-03-30 20:12:59

标签: java c++ linux leap-second tai-time

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

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

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

我已经研究过以下解决方案:

Java 8或ThreeTen项目 获得TAIInstant的唯一方法是使用Instant然后转换它,根据规格,"根据UTC-SLS,瞬间转换在闰秒附近不会完全准确。&#34 ;这本身并不重要(事实上,使用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 这样做的问题是,即使计算机重新启动,时间戳也需要保持有效和可比较。

4 个答案:

答案 0 :(得分:4)

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

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

答案 1 :(得分:3)

  

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

然后你的设计不是最理想的。你不能使用时间然后以某种方式插入闰秒。这实际上经常出现,人们陷入了使用挂钟进行时间戳测量的同一陷阱。

  1. 程序的时间戳开始并将其与计数器相关联
  2. 使用计数器以固定间隔进行连续测量
  3. 为保持数据充分同步所需的时间戳新计数器值
  4. 如果你在1秒内避免时间戳,那么可以发生闰秒(午夜!),你可以在家中自由,因为这些可以在以后调整。

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

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

答案 2 :(得分:2)

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

  • 我写这篇文章是为了填补Java世界的空白(java.time无法做到全部),
  • 到目前为止给出的其他答案只讨论C ++(但你也要求Java),
  • 它的工作原理与@ user3427419描述的原理相同。

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

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

示例:

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

一些说明:

我建议将此代码编写为简单批处理程序调用的子程序,以避免主程序依赖于Internet连接。该批处理文件最终将使用提到的系统属性调用主程序。如果你设置这个property,那么将从那里指定的文件中读取闰秒,然后任何最终可用的tzdata-module将停止以产生任何并发的闰秒信息。

答案 3 :(得分:0)

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

来自NTP的选项TAI:您的TAI实施需要有关闰秒的知识。可能NTP协议或referenced resources是当前和未来闰秒的最可靠来源。

来自GPS的选项TAI:GPS时钟对TAI有固定的偏移,你不必乱用闰秒