【问题标题】:How does the (Oracle) Java JVM know a leap second is occurring?(Oracle) Java JVM 如何知道闰秒正在发生?
【发布时间】:2015-06-22 15:59:44
【问题描述】:

leap second will occur,2015 年 6 月 30 日。不同的操作系统似乎以不同的方式处理这种情况。在我的特殊情况下,我们正在运行一个 Red Hat 6.4 系统,该系统带有高度依赖时间的定制 Java (JDK 1.7) 软件。根据我发现的一些recent Red Hat released information 的说法,我们系统的 NTP 守护进程将确保操作系统通过重复 23:59:59 两次来自动处理闰秒。

我的问题是:如果我有一个长时间运行的 JDK 1.7 进程,它如何知道闰秒正在发生?我的意思是,Java 最终如何知道IERS 人们决定插入闰秒? Date 文档似乎表明知道闰秒,但似乎含糊不清。我可以假设 JDK,当构造适当的 Date 对象或调用 Calendar.getInstance() 时,它是传递到底层操作系统的日期时间处理以获得适当的“实时”时间值? (在我的情况下,听起来它会在 23:59:59 重复第二次,因为这就是操作系统将处理它的方式)。

【问题讨论】:

  • 好吧,cmiiw,因为我真的不记得我在哪里读到这个了。但是,我确定它是关于 UT1 和 UTC,如果这两个时区之间的距离超过 0.6 秒(左右),那么将添加闰秒。
  • @bayu.io:很有趣,所以至少对于 Java 8,甲骨文似乎决定在一天的最后 1000 秒内逐步延长秒数(出现至少不同于 RHEL 和 Windows 方法)。
  • 好吧,如果在那之前没有人给出明确的答案,我可能会在我们的 Java 1.7 进程中添加一些额外的调试日志记录,并在大约 9 天后报告我的发现。

标签: java datetime time utc


【解决方案1】:

assyliasThe Answer 是正确的。这个答案添加了一些由对该答案的评论引发的想法。该评论提到在计划闰秒时计算午夜经过的时间。

这个答案也确实解决了原始问题,指出对于实际使用,闰秒的问题没有实际意义,被日期时间框架忽略。

忽略闰秒

据我所知,所有常见的 Java 日期时间框架都会忽略闰秒。其中包括:

  • java.time
  • Joda-Time
  • java.util.Date/.Calendar(现在已被 java.time 和 Joda-Time 过时)

文档

这里是每个框架文档的摘录,表明它们实际上忽略了闰秒。粗体字的重点是我的。

Instant 类的 java.time 文档:

...鉴于上述精确计时的复杂性,此 Java API 定义了自己的时间标度,即 Java Time-Scale。

Java Time-Scale 将每个日历日精确地划分为 86400 个细分,称为秒。这些秒数可能与 SI 秒数不同。它与事实上的国际民事时间尺度非常吻合,其定义不时变化。

Java Time-Scale 对时间线的不同部分的定义略有不同,每一个都基于用作民用时间基础的共识国际时间尺度。每当国际商定的时间尺度被修改或替换时,必须为其定义一个新的 Java 时间尺度段。每个细分市场都必须满足以下要求:

  • Java 时标应与基础国际民用时标紧密匹配;
  • Java Time-Scale 应与每天中午的国际民用时标完全匹配;
  • Java 时标应与国际民用时标具有精确定义的关系。

截至 2013 年,Java 时间尺度目前有两个部分。

对于从 1972 年 11 月 3 日(具体边界在下文讨论)直至另行通知的这段时间,一致的国际时间尺度是 UTC(有闰秒)。在这一部分中,Java Time-Scale 与 UTC-SLS 相同。这与没有闰秒的日子的 UTC 相同。在确实有闰秒的日子里,闰秒平均分布在一天的最后 1000 秒内,保持每天 86400 秒的精确外观

对于 1972 年 11 月 3 日之前的部分,任意向后延伸,国际一致时间尺度定义为 UT1,应用于预测,相当于本初子午线(格林威治)上的(平均)太阳时.在这一部分中,Java 时间尺度与共识国际时间尺度相同。这两个段之间的确切边界是 1972-11-03T00:00 和 1972-11-04T12:00 之间 UT1 = UTC 的时刻。

使用 JSR-310 API 实现 Java 时间刻度不需要提供任何亚秒级精度的时钟,或者单调或平稳地进行。因此,实现不需要实际执行 UTC-SLS 转换或以其他方式了解闰秒。然而,JSR-310 确实要求实现必须记录他们在定义表示当前时刻的时钟时使用的方法。有关可用时钟的详细信息,请参阅时钟。

Java 时间刻度用于所有日期时间类。这包括 Instant、LocalDate、LocalTime、OffsetDateTime、ZonedDateTime 和 Duration。

Joda-Time FAQ:

Joda-Time 不支持闰秒。闰秒可以通过编写一个新的、专门的年表来支持,或者对现有的 ZonedChronology 类进行一些增强。在任何一种情况下,未来版本的 Joda-Time 都不会默认启用闰秒。大多数应用程序都不需要它,而且它可能会产生额外的性能成本。

java.util.Date class doc:

一秒用0到61的整数表示;值 60 和 61 仅适用于闰秒,甚至仅出现在实际正确跟踪闰秒的 Java 实现中

据我所知,OpenJDK 和 Oracle 提供的实现跟踪闰秒。如果您找到它,请发布此类文档。

计算经过时间时没有闰秒

因此,这些框架在计算经过时间时不会报告额外的闰秒。

这是一些示例代码,用于计算从午夜前一分钟到午夜后一分钟的经过时间,时间为 2015 年 6 月 30 日至 7 月 1 日,当时安排了 Leap Second。此代码在java version "1.8.0_45" 中测试 Joda-Time 2.8.1 和 java-time。我忽略了 java.util.Date/.Calendar,因为我尽可能避免使用这些类;如果需要,请随时在此处为这种情况添加代码。

第一个Joda-Time

// Joda-Time 2.8.1
DateTime startJoda = new DateTime( 2015, 06, 30, 23, 59, 00, DateTimeZone.UTC );
DateTime stopJoda = new DateTime( 2015, 07, 01, 00, 01, 00, DateTimeZone.UTC );
long elapsedMillisJoda = ( stopJoda.getMillis( ) - startJoda.getMillis( ) );

System.out.println( "startJoda: " + startJoda + " stopJoda: " + stopJoda + " = elapsedMillisJoda: " + elapsedMillisJoda );

…和java.time

// java.time
ZonedDateTime startZdt = ZonedDateTime.of( 2015, 06, 30, 23, 59, 00, 00, ZoneOffset.UTC );
ZonedDateTime stopZdt = ZonedDateTime.of( 2015, 07, 01, 00, 01, 00, 00, ZoneOffset.UTC );
long elapsedMillisZdt = startZdt.until( stopZdt, ChronoUnit.MILLIS );

System.out.println( "startZdt: " + startZdt + " stopZdt: " + stopZdt + " = elapsedMillisZdt: " + elapsedMillisZdt );

运行时,我们会看到偶数的结果,正好是 2 分钟、120 秒或 120,000 毫秒。

startJoda: 2015-06-30T23:59:00.000Z stopJoda: 2015-07-01T00:01:00.000Z = elapsedMillisJoda: 120000
startZdt: 2015-06-30T23:59Z stopZdt: 2015-07-01T00:01Z = elapsedMillisZdt: 120000

关于java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

使用符合JDBC 4.2 或更高版本的JDBC driver,您可以直接与您的数据库交换java.time 对象。不需要字符串也不需要 java.sql.* 类。

从哪里获得 java.time 类?

ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

【讨论】:

  • 这是一个非常好的通用分析,特别适用于较新(8+)的JDK。
  • 假设闰秒的“扩散”也延伸到毫秒和纳秒是否正确?
  • @Harald 对不起,我没有听懂你的问题。当某个科学机构在当前 UTC 时间跟踪和天文时间跟踪之间测量到足够的漂移时,即宣布闰秒,一旦增量增长到一整秒。所以飞跃总是一整秒,而不是一小部分。也许您指的是某些人(例如 Google 公司)将飞跃“涂抹”为在几分钟内完成的一系列微小的小数秒飞跃,而不是在时钟停止的一整秒内。
  • 是的:“闰秒在过去 1000 秒内平均分布”是我想知道的。这些秒可能没有更多的毫秒,所以毫秒和纳秒也必须分散开。我想知道是否有一些带有时间增量的棘手代码可以显示存在闰秒。
  • @Harald 我仍然没有关注,抱歉。也许你不明白我们试图吸收/吞下/忽略额外的一秒。我们将时钟暂停一秒钟,让它们赶上地球自转缓慢和其他天文定位。一种方法是在任意选择的分钟上设置第 61 秒。另一种方法是每千秒增加一毫秒。
【解决方案2】:

这取决于您的 jdk 版本。例如,如果您正在运行更新 80,您可以查看the release notes

JDK 7u80 包含 IANA 时区数据版本 2015a。有关详细信息,请参阅 JRE 软件中的时区数据版本。

然后点击timezone data versions的链接,找到2015a。那就关注the link to TZ Updater version1.4.11

根据 IERS Bulletin C 49,新闰秒 2015 年 6 月 30 日 23:59:60 UTC。(感谢 Tim Parenti。)

以前似乎没有包含它,因此如果您运行的是旧版本的 JDK 7,您可能无法进行调整。有关其内部工作原理的更多信息,请访问here

说实话,我从来没有测试过它在实践中是如何工作的。

【讨论】:

  • 我相信我们的生产系统正在运行 jdk-7u55,因此可以很好地猜测,如果 JRE 在内部进行自己的计时,jdk 7u55 将忽略 2015-06-30闰秒。
  • @OgrePsalm33 看起来是的 - 根据我给出的最后一个链接:“Java SE 平台的时区数据不是从本地或主机操作系统 (OS) 读取的,因此 OS时区补丁不会更新 JRE 软件的时区数据”。但是,如果您在午夜之后创建new Date(),它可能会获取操作系统执行的时钟调整 - 但是在闰秒上的日期计算,比如date2.getMillis() - date1.getMillis(),可能会给出错误一秒的结果。实际上,最后一件事很容易检查。
  • 这个答案最符合我对所发生事情的观察。
  • 必须使用 TZupdater 工具手动完成更新(如果您不想更新 JRE),但即使这样也需要您停止它“您必须停止任何正在运行的在已安装的 JDK/JRE 软件映像上运行 TZUpdater 工具之前要操作的 JDK/JRE 软件。"
  • 错误答案。 Oracle JVM 永远无法知道它,即使在最现代的发行版中也不知道,因为原始 tz 数据中的闰秒表尚未导入。唯一导入的是 tz 分布文档中的 NEWS 部分,其中有时包含有关闰秒的信息。但是 Oracle 只是将文档复制到 tz updater 工具中,对 JVM 代码没有任何影响。
【解决方案3】:

这些是我的实际观察,在 RHEL 6.3 上运行实时系统,使用 Oracle Java JDK 1.7.0_55-b13(即:一个过时的 JDK)。我添加了启动的调试代码,在 2015 年 6 月 30 日 23:59 UTC 闰秒前每 0.5 秒记录一个新的 DateTime(),然后在闰秒结束后启动。日志如下(出于好奇,打印的双数是自J2000 epoch 以来的秒数)

2015-06-30 23:59:59.316 18349217 [main] INFO  - Leaper's time is 488980799.316000 (Tue Jun 30 23:59:59 GMT-00:00 2015)
2015-06-30 23:59:59.817 18349718 [main] INFO  - Leaper's time is 488980799.816000 (Tue Jun 30 23:59:59 GMT-00:00 2015)
2015-07-01 00:00:00.317 18350218 [main] INFO  - Leaper's time is 488980800.317000 (Wed Jul 01 00:00:00 GMT-00:00 2015)
2015-07-01 00:00:00.817 18350718 [main] INFO  - Leaper's time is 488980800.817000 (Wed Jul 01 00:00:00 GMT-00:00 2015)
2015-07-01 00:00:01.318 18351219 [main] INFO  - Leaper's time is 488980801.318000 (Wed Jul 01 00:00:01 GMT-00:00 2015)

不幸的是,这并没有告诉我太多,除了它没有在 23:59:60 插入规范的闰秒(因为 assylias 和 Basil Bourque 的答案都表明会发生)。我的希望是“new Date()”能够深入到底层操作系统(RHEL 6.3,它应该正确解释闰秒)来询问当前时间。情况似乎并非如此。

我没有资源来运行 JDK 版本和 RHEL 版本的任何其他组合来测试效果。我目前最好的猜测是,Basil Bourque 发现的关于在一天的最后 1000 秒内传播闰秒的文档适用于 JDK 1.8 和更高版本(因为它是 Instant 文档的一部分,这是 Java 8 的一个特性)。对于我的特殊情况,由于我的旧 JDK 1.7.0_55 没有 2015 年的闰秒调整,我认为 assylias 的观察适用。也就是说,我们的实时进程现在提前 1 秒运行,忽略了闰秒。

这里学到了什么?如果您想确保它们考虑到即将到来的闰秒,请确保您的实时系统已完全使用最新更新进行修补。我必须进行一些观察和日志分析,但我们的特定系统现在可能比实时运行时间提前 1 秒,我们需要重新启动服务以回退到队列中。

【讨论】:

  • 你确定是这样吗?假设 'new DateTime()' 引用 joda 时间,或者您的意思是 'new Date()',这两者都使用 System.currentTimeMillis(),它使用操作系统的时钟:blogs.oracle.com/dholmes/entry/inside_the_hotspot_vm_clocks
  • 为了我自己的利益,我创建了一个 docker 镜像,运行时演示了当插入闰秒时,java 会做出相应的响应 (hub.docker.com/r/tolbertam/leap-java-demo)。在这种情况下使用 Instant.of,但应适用于使用 System.currentTimeMillis() 的任何构造函数
  • 啊!我明白这里发生了什么。我只是在较旧的 JDK 上尝试过这个并且有相同的经验。原因是因为旧版本的 JDK
  • @AndyTolbert 我应该澄清一下,new DateTime() 是我们自己的包装 Java Date() 的类,而不是 Joda Time 类(我们的具体实现基于 J2000 的日期,这是一个要求对于我们的系统)。所以你是说实际的 Thread.sleep() 占闰秒,即使 System.currentTimeMillis() 没有?
  • 在 7u60 之前,存在一个错误,即当闰秒发生时(或一般情况下将时钟送回),它会抛出 JVM 中的计时器,所以如果你执行 Thread.sleep (10);在闰秒期间,代码最终会在闰秒 + 10 毫秒期间休眠,而不是休眠 10 毫秒。这就是为什么如果您打印日期、睡眠然后打印日期,您将看不到使用较旧的 JVM 打印的闰秒,但由于新的 Date/System.currentTimeMillis 使用来自操作系统。
【解决方案4】:

不要忘记闰秒发生在 UT0 的 23:59:59,这将在世界上所有时区的同一时刻应用,因此如果您处于时区时间加 8,它将发生在 23:59 :59 - 8 = 15:59:59。

由于“夏令时”而导致的任何本地时间更改都将被忽略。

下一秒是 23:59:60,然后是 00:00:00。

请注意,闰秒规范允许在 12 月底或 6 月插入 + - 1 或 2 秒。

【讨论】:

  • 嗨,欢迎来到 StackOverflow!我们当然感谢像您这样的新用户的贡献。不幸的是,您刚刚发布的答案并没有真正直接回答问题。如果您觉得这样的附加信息很重要,请发表评论。您只需要赚取更多的声望点即可发布 cmets,因此请从提出问题或以有意义的方式回答一些问题开始。祝你好运!
  • 2 秒???不允许“双”闰秒,因为 UTC 的定义是它与 UT 的偏差不得超过 0.9 秒。另请参阅Wikipedia 因此,逻辑结果是在季度末(最好在 6 月底或 12 月底)最多一个闰秒。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-11-30
  • 2015-09-18
  • 2013-04-07
  • 1970-01-01
  • 2014-08-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多