如何使用Java或C++在Linux中以毫秒为单位获得当前的TAI时间?
我需要这样做的原因是能够在很长一段时间内(按年数计算)准确地获取时间戳,并且仍然能够比较它们,而不必担心闰秒。有可能在一个闰秒内进行多个测量,所有测量都需要明确、单调地增加和线性增加。这将是一个专用的Linux服务器。这是一个需要精确到.5秒左右的科学项目。
我目前不希望投资于全球定位系统计时员,并希望使用NTP的pool.ntp.org,以保持系统时钟在轨道上。
我研究了以下解决办法:
Java 8或ThreeTen项目获得TAIInstant的唯一方法是使用一个即时,然后转换它,根据规范,“根据UTC,从即时到每秒的转换将不完全准确。”这本身并不是什么大事(事实上,使用UTC也是可以接受的)。然而,在Instant类中使用now()似乎也只是System.currentTimeMillis()的包装,这使我认为,在闰秒期间,时间仍然不明确,项目实际上不会给我太大的时间。Java 8规范还声明:
使用JSR-310API实现Java时间刻度是不需要的,不需要提供任何亚秒精度的时钟,也不需要提供单调或平滑的。因此,实现不需要实际执行UTC,或者以其他方式了解闰秒。
使用右/?时区--这似乎是可以工作的,但是我不确定实现是否足够聪明,可以在闰秒内继续工作,或者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这方面的问题是,即使计算机重新启动,时间戳也需要保持有效和可比较。
发布于 2015-06-12 20:05:33
除了正确接受的答案之外,我还要提到免费的Java库Time4J (最小版本v4.1)作为可能的解决方案,因为
java.time不可能做到所有),它使用基于System.nanoTime()的单调时钟,但甚至允许通过接口TickProvider实现自定义。为了进行校准,您可以使用net.time4j.SystemClock.MONOTONIC,也可以使用名为SntpConnector的SNTP时钟,它只需要一些简单的配置就可以连接到任何您想要的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)一些注意事项:
我建议将此代码编写为由简单批处理程序调用的子程序,以避免主程序依赖于internet连接。该批处理文件将最终调用具有上述系统属性的主程序。如果您设置了这个属性,那么闰秒将从指定的文件中读取,然后任何最终可用的tzdata模块都会停止以产生任何并发的闰秒信息。
发布于 2015-05-07 23:01:55
CLOCK_REALTIME和CLOCK_TAI返回相同的值,因为内核参数tai_offset为零。
使用adjtimex(timex tmx)进行检查并读取值。我认为,如果ntpd是足够新的(>4.2.6)并且有一个闰秒文件,它就会设置它。它也可以从上游服务器获得它,但我还无法验证。调用adjtimex()可以在以根用户身份运行时手动设置tai_offset。您将需要一个新的ish man页面,以便adjtimex查看要设置的参数。我的debian man页面太旧了,但是命令有效。
发布于 2015-03-30 21:05:57
我需要这样做的原因是能够在很长一段时间内(按年数计算)准确地获取时间戳,并且仍然能够比较它们,而不必担心闰秒。有可能在一个闰秒内进行多个测量,所有测量都需要明确、单调地增加和线性增加。
那么你的设计就不太理想了。你不能利用时间,然后以某种方式干预闰秒。这实际上经常出现,人们也陷入了使用挂钟计时测量的相同陷阱中。
如果您避免时间戳的1秒,跳跃秒可能发生(午夜!),您是免费的,因为这些可以在稍后调整。
现在,如果您坚持使用TAI而不使用计数器,那么您所需要的只是一张表,表中有闰秒,需要计算。那就用单调的时间。也有一些库可以帮你做到这一点,但是它们可能已经过时了,所以你必须自己维护它们,
http://skarnet.org/software/skalibs/libstddjb/tai.html
https://stackoverflow.com/questions/29355406
复制相似问题