Java Calendar.roll 和夏令时更改期间的 CST

Java Calendar.roll and CST during daylight saving changes

我想知道 Calendar.roll 是否尊重它的 javadoc 契约:

运行 以下片段

    final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("CST"));
    cal.setTimeInMillis(1457928024812l);

    System.out.println(cal.getTime());
    cal.roll(Calendar.HOUR_OF_DAY, true);
    System.out.println(cal.getTime());

产生以下输出:

Sun Mar 13 23:00:24 CDT 2016
Sun Mar 13 23:00:24 CDT 2016

2016 年 3 月 13 日是凌晨 2 点的夏令时更改(从 CST 到 CDT)。 roll 的 javadoc 指出 roll “ 添加单个时间单位 ”,这里没有添加任何时间单位。 这是此方法的预期行为吗?

编辑:

我将此报告为错误。有关详细信息,请参阅相应 OpenJDK 票证的 link:https://bugs.openjdk.java.net/browse/JDK-8152077

这似乎是 roll 方法中的实际错误。不错的发现!

一些注意事项:

  • 我必须使用 SimpleDateFormat 才能获得您显示的确切结果,因为只需调用 getTime 就会得到一个 Date 对象,该对象在本地打印时区。

  • 最好用America/Chicago而不是CST,但这不是原因。

  • 对于任何常规日期,滚动第 23 小时的日期应该在同一天转到第 0 小时。如果您只想增加一个小时,请使用 add 而不是 roll。参见 add vs rolladd 方法似乎工作正常。

  • 在spring-向前过渡的那一天,一天只有23个小时。 roll 方法似乎考虑到了这一点,即使它没有跨越实际的转换(在这个时区接近 2:00 时,时钟跳转到 3:00) .如您所示,它将小时设置为 23,比它应该设置的 0 早一小时。

  • 在回退过渡的那天,一天有 25 个小时。同样,roll 方法试图将这一点考虑在内,将小时设置为 1 而不是 0,即使它没有跨越实际转换(再次发生接近 2:00 在这个时区,当时钟回到 1:00).

我快速搜索了一下,看看是否已经在任何地方报告过这种情况,但没有找到太多。也许你应该 report it.

我还要补充一点,您可能应该考虑对 Java 8 或更高版本使用 Joda Time for Java 7 or earlier, and java.time

正如所说:

  • 使用 continent/region 格式的 proper time zone name。避免使用既不唯一也不规范的 3-4 字母代码。
  • 使用java.time framework built into Java 8 and later. See Tutorial。避免使用旧的日期时间 类、java.util.Date/.Calendar.

一个Instant is a moment on the timeline in UTC with nanosecond resolution. Apply a time zone (ZoneId) to get a ZonedDateTime.

Long input = 1457928024812L;
Instant instant = Instant.ofEpochMilli ( input );

ZoneId zoneId = ZoneId.of ( "America/Chicago" );
ZonedDateTime zdt = ZonedDateTime.ofInstant ( instant, zoneId );

ZonedDateTime zdtHourLater = zdt.plusHours ( 1 );

转储到控制台。

System.out.println ( "input: " + input + " |  instant: " + instant + " | zoneId: " + zoneId + " | zdt: " + zdt + " | zdtHourLater: " + zdtHourLater );

input: 1457928024812 | instant: 2016-03-14T04:00:24.812Z | zoneId: America/Chicago | zdt: 2016-03-13T23:00:24.812-05:00[America/Chicago] | zdtHourLater: 2016-03-14T00:00:24.812-05:00[America/Chicago]

所以,在 java.time 中没有这样的问题。如预期的那样,从 13 日晚上 11 点到 14 日午夜刚过,增加一个小时。

夏令时 (DST)

正确处理夏令时。

跨夏令时转换

从 1:59 上午增加一小时,时钟上的时间跳到 3:59 上午,如预期的那样。

ZonedDateTime zdtBeforeTwoAm = ZonedDateTime.of ( 2016, Month.MARCH.getValue ( ), 13, 1, 59, 0, 0, zoneId );
ZonedDateTime zdtBeforeTwoAmPlus = zdtBeforeTwoAm.plusHours ( 1 );

转储到控制台。

System.out.println ( "zdtBeforeTwoAm: " + zdtBeforeTwoAm + " |  zdtBeforeTwoAmPlus: " + zdtBeforeTwoAmPlus );

zdtBeforeTwoAm: 2016-03-13T01:59-06:00[America/Chicago] | zdtBeforeTwoAmPlus: 2016-03-13T03:59-05:00[America/Chicago]

要求凌晨 2 点

要求凌晨 2 点无效(没有这样的时间)。所以 java.time 自动移动到有效的等价物,凌晨 3 点。

ZonedDateTime zdtTwoAm = ZonedDateTime.of ( 2016, Month.MARCH.getValue ( ), 13, 2, 0, 0, 0, zoneId );

转储到控制台。

System.out.println ("zdtTwoAm: " + zdtTwoAm );

zdtTwoAm: 2016-03-13T03:00-05:00[America/Chicago]