3

I wonder if Calendar.roll respects its javadoc contract:

Running the following snippet

    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());

yiels the following output:

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

The 13 of March 2016 was a Daylight Saving change at 2 am (from CST to CDT). The javadoc of roll states that roll "adds a single unit of time", and here no units of time is added. Is it the expected behavior of this method?

EDIT:

I reported this as a bug. For more information, here is the link to the corresponding OpenJDK ticket: https://bugs.openjdk.java.net/browse/JDK-8152077

2 Answers2

2

This appears to be an actual bug in the roll method. Nice find!

A couple of notes:

  • I had to use a SimpleDateFormat to get the exact results you showed, as just calling getTime will give a Date object that prints in the local time zone.

  • It would be better to use America/Chicago rather than CST, but that is not the cause of this.

  • For any regular date, it rolling the 23rd hour should go to hour 0 on the same day. If you wanted to just increment by an hour, then use add instead of roll. See add vs roll. The add method appears to be working correctly.

  • On the day of a spring-forward transition, there are only 23 hours in the day. The roll method appears to be taking this into account even though it did not cross over the actual transition (which occurs approaching 2:00 in this time zone, when the clocks jump ahead to 3:00). As you showed, it set the hour to 23, which is one hour before the 0 it should have set.

  • On the day of a fall-back transition, there are 25 hours in the day. Again, the roll method tries to take this into account, setting the hour to 1 instead of 0 even though it did not cross the actual transition (which again occurs approaching 2:00 in this time zone, when the the clocks move back to 1:00).

I did a quick search to see if this has been reported anywhere already and didn't find much. Perhaps you should report it.

I'll also add that you should probably consider using Joda Time for Java 7 or earlier, and java.time for Java 8 or newer.

Community
  • 1
  • 1
Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • Thanks Matt. About the `America/Chicago` thing, I used `CST` simply to reproduce the issue. It occurred on a server with its default timezone, using a simple `Calendar.getInstance()`. – Alexandre de Champeaux Mar 15 '16 at 14:39
1

As the Answer by Matt Johnson said:

  • Use a proper time zone name in the continent/region format. Avoid the 3-4 letter codes that are neither unique nor standardized.
  • Use the java.time framework built into Java 8 and later. See Tutorial. Avoid the old date-time classes, java.util.Date/.Calendar.

An 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 );

Dump to console.

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]

So, no such problem in java.time. Adding an hour moves across midnight as expected, from 11 PM on the 13th to just after midnight on the 14th.

Daylight Saving Time (DST)

DST is handled properly.

Crossing the DST change-over

Adding one hour from 1:59 AM jumps two hours on the clock to 3:59 AM, as expected.

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

Dump to console.

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]

Asking for 2 AM

Asking for 2 AM is not valid (no such time). So java.time automatically moves to a valid equivalent, 3 AM.

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

Dump to console.

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

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

Community
  • 1
  • 1
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154