如何获得当前的TAI时间?

如何在Linux中使用Java或C ++获取当前TAI时间(毫秒)?

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

我现在不想投资GPS计时器,并希望使用NTP到pool.ntp.org以保持系统时钟的正常运行。

我已经看了下面的解决scheme:

Java 8或ThreeTen项目获得TAIInstant的唯一方法是使用Instant,然后根据规范将其转换为“根据UTC-SLS,即时转换在闰秒附近不会完全准确。 “ 这本身并不是什么大问题(事实上,使用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,但发现它与我testing中的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问题是,即使计算机重新启动,时间戳也需要保持有效和可比较。

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

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

它使用基于System.nanoTime()的单调时钟,但是甚至允许通过接口TickProvider自定义实现。 为了进行校准,您可以使用net.time4j.SystemClock.MONOTONIC ,或者使用一个名为SntpConnector的SNTP时钟, SntpConnector需要一些简单的配置即可连接到任何所需的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

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

使用adjtimex(timex tmx)并读取值。 我认为ntpd会设置它是否足够新( >4.2.6 ),并有闰秒文件。 它也可能能够从上游服务器得到它,但我还没有能够验证。 调用adjtimex()可以在以root tai_offset运行时手动设置tai_offset 。 您将需要一个新的ish man页来查看要设置的参数。 我的debian man页太老了,但命令奏效。

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

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

来自GPS的选项TAI:GPS时钟对TAI具有固定的偏移量,您不必揉搓闰秒