如何获取当前的TAI时间?

如何获取当前的TAI时间?

问题描述:

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

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

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

我已经调查了以下解决方案:

的Java 8或ThreeTen项目 获得TAIInstant是使用即时然后将其转换其中,根据规范,“转换的唯一途径根据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却发现它是完全一致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库Time4Jmin version 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会尝试应用平滑算法(如果在时钟配置中激活)以确保单调行为。详细的文件可用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) 

一些注意事项:

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

+0

你的图书馆如何处理长期的闰秒?换句话说,如果一个应用程序在未来几年内重新启动,我如何确保它具有过去几年发生的闰秒知识? – 2015-10-13 01:46:49

+0

@DanielCentore可以通过自己更新您的闰秒数据导入新的闰秒 - 无论是通过新版本的库还是通过更新tzdata模块(事实上从iana.org/tz导入新的tzdb版本)。 Time4J还管理[过期标志](http://time4j.net/javadoc-en/net/time4j/scale/LeapSeconds.html#getDateOfExpiration--),如果闰秒数据可用于将警报编程给系统管理员已经变老了。请记住,无法将数据提前6个月保持有效状态。 – 2015-10-13 11:54:14

+0

是否有推荐的方法来自动从互联网上下载资源来自动处理这个问题? – 2015-10-13 17:34:48

我之所以需要这个是能够准确地采取时间戳 过的很长一段时间(两年左右),仍然能够 对它们进行比较,而不必担心闰秒。可能有 用于在闰秒期间进行多次测量,并且所有测量结果都必须是明确的,单调递增的,并且线性增加。

那么你的设计是不理想的。你不能使用时间,然后以某种方式干涉闰秒。这实际上经常出现,人们陷入使用挂钟进行时间戳测量的陷阱。程序和其关联为必要时带计数器

  • 使用以固定的间隔计数器连续测量
  • 时间戳新的计数器值(一个或多个),以确保您的数据能够充分同步的

    1. 时间戳的开始

    如果您避免在1秒内发生超时(午夜!)的时间戳,您可以免费入住,因为可以稍后再调整。

    现在,如果您坚持使用TAI而不使用计数器,您所需要的只是一张需要考虑闰秒的表格。然后使用单调时间。还有,可以为你做这个库,但他们可能会过时的,所以你必须自己维护它们,

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

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

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

    选项TAI:GPS时钟的固定偏移至大,你不必惹闰秒

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

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