(Oracle)Java JVM如何知道闰秒发生了什么?

Ogr*_*m33 34 java time datetime utc

一个闰秒会发生在6月30日,2015年不同的操作系统似乎以不同的方式处理这种情况.在我的特定情况下,我们运行的Red Hat 6.4系统具有严重依赖于时间的自定义Java(JDK 1.7)软件.根据最近我发现的一些Red Hat发布的信息,我们系统的NTP守护进程将确保操作系统通过重复23:59:59两次自动处理闰秒.

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

Bas*_*que 24

答案由assylias是正确的.本答案增加了一些由该答案评论引发的想法.该评论涉及计算在安排闰秒的午夜时间的经过时间.

本答案也解决了原始问题,指出在实际应用中,闰秒问题没有实际意义,被日期时间框架所忽略.

闰秒被忽略了

正如我所理解的,所有常见的Java日期时间框架都忽略了闰秒.这些包括:

  • java.time
  • 乔达时间
  • java.util.Date/.Calendar(现在已经过时了java.time和Joda-Time)

文件

以下是每个框架文档的摘录,显示它们实际上忽略了Leap Seconds.大胆的重点是我的.

Instant类的java.time文档:

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

Java Time-Scale将每个日历日划分为86400个细分,称为秒.这些秒可能与SI秒不同.它与事实上的国际民事时间表紧密匹配,其定义随时变化.

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

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

目前,截至2013年,Java时间范围内有两个部分.

对于从1972-11-03(下面讨论的确切边界)直到另行通知的段,共识国际时间尺度是UTC(具有闰秒).在此段中,Java Time-Scale与UTC-SLS相同.这与没有闰秒的天数相同.在具有闰秒的日子里,闰秒在一天的最后1000秒内平均分布,每天保持正好86400秒的外观.

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

使用JSR-310 API实现Java时标不需要提供亚秒精确或单调或平滑进展的任何时钟.因此,实现不需要实际执行UTC-SLS转换或以其他方式了解闰秒.但是,JSR-310要求实现必须记录它们在定义表示当前时刻的时钟时使用的方法.有关可用时钟的详细信息,请参见时钟.

Java时标用于所有日期时间类.这包括Instant,LocalDate,LocalTime,OffsetDateTime,ZonedDateTime和Duration.

Joda-Time常见问题:

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

该java.util.Date类文档:

第二个由0到61的整数表示; 值60和61仅在闰秒发生,甚至仅在实际正确跟踪闰秒的Java实现中发生.

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

计算经过时间时没有闰秒

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

以下是一些示例代码,用于计算2015年6月30日至7月1日安排闰秒时从午夜到分钟之间的经过时间.此代码测试Joda-Time 2.8.1和java-time in java version "1.8.0_45".我忽略了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 );
Run Code Online (Sandbox Code Playgroud)

...和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 );
Run Code Online (Sandbox Code Playgroud)

运行时,我们会看到偶数的结果,恰好是两分钟,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
Run Code Online (Sandbox Code Playgroud)

关于java.time

该java.time框架是建立在Java 8和更高版本.这些类取代麻烦的老传统日期时间类,如java.util.Date,Calendar,和SimpleDateFormat.

现在处于维护模式的Joda-Time项目建议迁移到java.time类.

要了解更多信息,请参阅Oracle教程.并搜索Stack Overflow以获取许多示例和解释.规范是JSR 310.

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

从哪里获取java.time类?

该ThreeTen-额外项目与其他类扩展java.time.该项目是未来可能添加到java.time的试验场.您可以在此比如找到一些有用的类Interval,YearWeek,YearQuarter,和更多.


ass*_*ias 11

这取决于你的jdk版本.例如,如果您正在运行更新80,则可以查看发行说明:

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

然后点击时区数据版本的链接,找到2015a.然后点击TZ Updater version1.4.11的链接:

新的闰秒2015-06-30 23:59:60 UTC根据IERS Bulletin C 49.(感谢Tim Parenti.)

它似乎之前没有被包括在内,所以如果您运行的是旧版本的JDK 7,您可能无法进行调整.有关内部工作原理的更多信息,请点击此处.

说实话,我从未测试过它在实践中是如何运作的.

  • @ OgrePsalm33看起来是的 - 根据我给出的最后一个链接:"*Java SE平台的时区数据不是从本地或主机操作系统(OS)读取的,因此操作系统时区补丁不会更新JRE软件的时区数据*".但是如果你在午夜之后创建一个`new Date()`它可能会接受操作系统执行的时钟调整 - 但日期计算,比如说`date2.getMillis() - date1.getMillis()`第二个可能会导致结果错误一秒钟.实际上,最后一件事很容易检查. (2认同)
  • 错误的答案。Oracle JVM 永远无法知道这一点,即使在最现代的发行版中也是如此,因为原始 tz 数据中的闰秒表尚未导入。唯一导入的是 tz 发行版文档中的新闻部分,其中有时包含有关闰秒的信息。但 Oracle 只是将文档复制到 tz updater 工具,而不会对 JVM 代码产生任何影响。 (2认同)

Ogr*_*m33 5

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

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)
Run Code Online (Sandbox Code Playgroud)

不幸的是,这并没有告诉我多少,除了它没有在23:59:60插入规范的闰秒(因为assylias和Basil Bourque的答案都表明会发生).我希望'新的Date()'可以达到底层操作系统(RHEL 6.3,据称可以正确计算闰秒)来询问当前时间.情况似乎并非如此.

我没有资源运行JDK版本和RHEL版本的任何其他组合来测试效果.我目前最好的猜测是,Basil Bourque在当天的最后1000秒内发现闰秒的文档适用于JDK 1.8及更新版本(因为它是Instant 8文档的一部分,这是Java 8的一项功能).对于我的特殊情况,由于我的旧JDK 1.7.0_55没有2015年的闰秒调整,我认为assylias的观察适用.也就是说,我们的实时流程现在提前1秒运行,不知道闰秒.

在这里学到的经验?如果您想确保它们考虑即将到来的闰秒,请确保您的实时系统已完全修补最新更新.我将不得不做一些观察和日志分析,但我们的特定系统现在可能比实时提前1秒运行,我们需要重新启动我们的服务以便回退.