Dan*_*ore 6 c++ java linux leap-second tai-time
如何使用Java或C++获取Linux中当前的TAI时间(以毫秒为单位)?
我需要这个的原因是能够准确地获取很长一段时间(大约数年)的时间戳,并且仍然可以比较它们,而不必担心闰秒.在闰秒期间可以进行多次测量,并且所有测量需要是明确的,单调增加的和线性增加的.这将是一个专用的Linux服务器.这是一个需要大约0.5秒精度的科学项目.
我目前不想投资GPS计时器,并希望将NTP用于pool.ntp.org,以保持系统时钟正常运行.
我研究了以下解决方案:
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但发现它与我的测试中的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;
}
Run Code Online (Sandbox Code Playgroud)
输出只是:
1427744797
6896
1427744797
Run Code Online (Sandbox Code Playgroud)
使用CLOCK_MONOTONIC 这个问题是,即使计算机重新启动,时间戳也需要保持有效和可比较.
小智 5
CLOCK_REALTIME并CLOCK_TAI返回相同的值,因为内核参数tai_offset为零。
使用adjtimex(timex tmx)并读取值进行检查。我认为ntpd如果它足够新(>4.2.6)并且有闰秒文件,就会设置它。它也可能能够从上游服务器获取它,但我无法验证。adjtimex()当以 root 身份运行时,可以手动设置该调用tai_offset。您将需要一个新的man页面来adjtimex查看要设置的参数。我的 debianman页面太旧,但命令有效。
除了正确接受的答案之外,我还会提到免费的Java 库 Time4J(最小版本 v4.1)作为可能的解决方案,因为
java.time不能完成全部),它使用基于单调时钟System.nanoTime(),但甚至允许通过接口进行自定义实现TickProvider。为了校准的目的,您可以使用net.time4j.SystemClock.MONOTONIC,或者使用名为 的 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();
Run Code Online (Sandbox Code Playgroud)
我怀疑是否有任何基于 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)
Run Code Online (Sandbox Code Playgroud)
一些注意事项:
我建议将此代码编写为由简单批处理程序调用的子程序,以避免主程序依赖于互联网连接。该批处理文件最终将使用上述系统属性调用主程序。如果设置此属性,则将从此处指定的文件中读取闰秒,并且任何最终可用的 tzdata 模块将停止生成任何并发的闰秒信息。