除了正确接受的答案之外,我还会提到免费的Java 库 Time4J(最低版本 v4.1)作为可能的解决方案,因为
- 我写它是为了填补 Java 世界的空白(
java.time
不能做所有事情),
- 到目前为止给出的其他答案只谈论 C++(但你也要求 Java),
- 它的工作原理与@user3427419 描述的相同。
它使用基于System.nanoTime()
但甚至允许通过接口自定义实现的单调时钟TickProvider
。出于校准的目的,您可以使用net.time4j.SystemClock.MONOTONIC
,或者使用名为的 SNTP-clock SntpConnector
,它只需要一些简单的配置即可连接到您想要的任何 NTP-time-server。多亏了内置的闰秒表 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-module 都会停止以产生任何并发的闰秒信息。