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

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

A leap second will occur on June 30, 2015. Different Operating Systems seem to handle this situation differently. In my particular case, we are running a Red Hat 6.4 system with custom Java (JDK 1.7) software that is heavily time-dependent. According to some recent Red Hat released information 我发现,我们系统的 NTP 守护程序将通过重复 23:59:59 两次来确保 OS 自动处理闰秒。

我的问题是:如果我有一个很长的 运行 JDK 1.7 进程,它怎么知道发生了闰秒?我的意思是,Java 最终如何知道 IERS people have decided to insert a leap second? The Date 文档似乎表明知道闰秒,但似乎含糊不清。我可以假设 JDK,当构建适当的 Date 对象或调用 Calendar.getInstance() 时,它是对底层 OS 的日期时间处理的传递获得适当的 "real" 时间值? (在我的例子中,听起来它会重复第二个 23:59:59,因为这就是 OS 处理它的方式)。

这取决于您的 jdk 版本。例如,如果你是 运行 更新 80,你可以检查 the release notes:

JDK 7u80 contains IANA time zone data version 2015a. For more information, refer to Timezone Data Versions in the JRE Software.

然后按照link到timezone data versions and find 2015a. Then follow the link to TZ Updater version1.4.11

New leap second 2015-06-30 23:59:60 UTC as per IERS Bulletin C 49. (Thanks to Tim Parenti.)

之前似乎没有包含它,所以如果您 运行 是 JDK 7 的旧版本,您可能不会得到调整。有关其内部工作方式的更多信息,请参阅 here.

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

by assylias 是正确的。该答案添加了一些由对该答案的评论引发的想法。该评论提到在安排闰秒时计算午夜经过的时间。

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

忽略闰秒

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

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

文档

以下是每个框架文档的摘录,显示它们实际上忽略了闰秒。粗体强调的是我的。

Instant class 的 java.time 文档:

… Given the complexity of accurate timekeeping described above, this Java API defines its own time-scale, the Java Time-Scale.

The Java Time-Scale divides each calendar day into exactly 86400 subdivisions, known as seconds. These seconds may differ from the SI second. It closely matches the de facto international civil time scale, the definition of which changes from time to time.

The Java Time-Scale has slightly different definitions for different segments of the time-line, each based on the consensus international time scale that is used as the basis for civil time. Whenever the internationally-agreed time scale is modified or replaced, a new segment of the Java Time-Scale must be defined for it. Each segment must meet these requirements:

  • the Java Time-Scale shall closely match the underlying international civil time scale;
  • the Java Time-Scale shall exactly match the international civil time scale at noon each day;
  • the Java Time-Scale shall have a precisely-defined relationship to the international civil time scale.

There are currently, as of 2013, two segments in the Java time-scale.

For the segment from 1972-11-03 (exact boundary discussed below) until further notice, the consensus international time scale is UTC (with leap seconds). In this segment, the Java Time-Scale is identical to UTC-SLS. This is identical to UTC on days that do not have a leap second. On days that do have a leap second, the leap second is spread equally over the last 1000 seconds of the day, maintaining the appearance of exactly 86400 seconds per day.

For the segment prior to 1972-11-03, extending back arbitrarily far, the consensus international time scale is defined to be UT1, applied proleptically, which is equivalent to the (mean) solar time on the prime meridian (Greenwich). In this segment, the Java Time-Scale is identical to the consensus international time scale. The exact boundary between the two segments is the instant where UT1 = UTC between 1972-11-03T00:00 and 1972-11-04T12:00.

Implementations of the Java time-scale using the JSR-310 API are not required to provide any clock that is sub-second accurate, or that progresses monotonically or smoothly. Implementations are therefore not required to actually perform the UTC-SLS slew or to otherwise be aware of leap seconds. JSR-310 does, however, require that implementations must document the approach they use when defining a clock representing the current instant. See Clock for details on the available clocks.

The Java time-scale is used for all date-time classes. This includes Instant, LocalDate, LocalTime, OffsetDateTime, ZonedDateTime and Duration.

Joda-Time FAQ:

Joda-Time does not support leap seconds. Leap seconds can be supported by writing a new, specialized chronology, or by making a few enhancements to the existing ZonedChronology class. In either case, future versions of Joda-Time will not enable leap seconds by default. Most applications have no need for it, and it might have additional performance costs.

java.util.Date class doc:

A second is represented by an integer from 0 to 61; the values 60 and 61 occur only for leap seconds and even then only in Java implementations that actually track leap seconds correctly.

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

计算经过时间时没有闰秒

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

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

第一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 framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

要了解更多信息,请参阅 Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310

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

从哪里获得java.time classes?

  • Java SE 8, Java SE 9,及以后
    • 内置。
    • 标准 Java API 的一部分,带有捆绑实施。
    • Java 9 添加了一些小功能和修复。
  • Java SE 6 and Java SE 7
  • Android
    • Android java.time classes.
    • 捆绑实施的更高版本
    • 对于较早的 Android,ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See

ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

这些是我的实际观察结果,运行使用 Oracle Java JDK 1.7.0_55-b13 在 RHEL 6.3 上安装实时系统(即:一个输出-日期 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()' 能够深入到底层的 OS(RHEL 6.3,据称它正确地解释了闰秒)来询问当前时间。好像不是这样。

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

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

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

由于 'daylight saving' 而导致的任何本地时间更改都将被忽略。

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

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