繁体   English   中英

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

[英]How does the (Oracle) Java JVM know a leap second is occurring?

闰秒将在2015年6月30日发生 。不同的操作系统似乎以不同的方式处理这种情况。 在我的特定情况下,我们运行的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,因为这就是操作系统将如何处理它)。

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类doc

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

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

计算经过时间时没有闰秒

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

以下是一些示例代码,用于计算2015年6月30日至7月1日安排闰秒时从午夜到分钟之间的经过时间。 此代码在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 );

运行时,我们会看到偶数的结果,恰好是两分钟,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及更高版本中。 这些类取代了麻烦的旧遗留日期时间类,如java.util.DateCalendarSimpleDateFormat

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

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

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

从哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展了java.time。 该项目是未来可能添加到java.time的试验场。 您可以在这里找到一些有用的类,比如IntervalYearWeekYearQuarter ,和更多

这取决于你的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,您可能无法进行调整。 有关内部工作原理的更多信息,请点击此处

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

这些是我在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)

不幸的是,这并没有告诉我多少,除了它没有在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秒运行,我们需要重新启动我们的服务以便回退。

不要忘记闰秒发生在UT0的23:59:59,这是在世界各地的所有时区的同一时刻应用,所以如果你在区域时间加8,它将发生在23:59:59 - 8 = 15:59:59

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

以下是23:59:60然后00:00:00。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM