Neither SimpleDateFormat
nor the underlying Calendar
specifies what happens when parsing a datetime string without timezone for a time in the overlapping hour between daylight savings time and standard time.
You have observed that it will return the later time, i.e. it seems to prefer standard over daylight savings time. But, the behavior is undefined, so...
The new java.time
classes do however specify exactly what happens, and how to choose the other "hour" of the overlap.
In the new API, since your datetime string is without timezone, you'd likely first parse using LocalDateTime
, then apply time zone to get a ZonedDateTime
, e.g.
LocalDateTime ldtEnd = LocalDateTime.parse("2016-11-06T01:56:01");
ZonedDateTime zdtEnd = ldtEnd.atZone(ZoneId.of("America/Los_Angeles"));
// zdtEnd is now: 2016-11-06T01:56:01-07:00[America/Los_Angeles]
To see the overlap, you can try adding an hour:
ZonedDateTime zdtEnd2 = zdtEnd.plusHours(1);
// zdtEnd2 is now: 2016-11-06T01:56:01-08:00[America/Los_Angeles]
The behavior is well-defined, see javadoc of atZone()
:
In most cases, there is only one valid offset for a local date-time. In the case of an overlap, where clocks are set back, there are two valid offsets. This method uses the earlier offset typically corresponding to "summer".
In the case of a gap, where clocks jump forward, there is no valid offset. Instead, the local date-time is adjusted to be later by the length of the gap. For a typical one hour daylight savings change, the local date-time will be moved one hour later into the offset typically corresponding to "summer".
To obtain the later offset during an overlap, call ZonedDateTime.withLaterOffsetAtOverlap()
on the result of this method. To throw an exception when there is a gap or overlap, use ZonedDateTime.ofStrict(LocalDateTime, ZoneOffset, ZoneId)
.
As you can see, it will always return the earlier time in an overlap, which is opposite of the observed behavior of SimpleDateFormat
. If you want the later time in an overlap, call withLaterOffsetAtOverlap()
.
If you don't want to rely on documented default, you can always be explicit:
ZoneId PT = ZoneId.of("America/Los_Angeles");
LocalDateTime ldtStart = LocalDateTime.parse("2016-11-06T00:57:01");
ZonedDateTime zdtStartEarly = ldtStart.atZone(PT).withEarlierOffsetAtOverlap();
ZonedDateTime zdtStartLater = ldtStart.atZone(PT).withLaterOffsetAtOverlap();
System.out.println(zdtStartEarly); // 2016-11-06T00:57:01-07:00[America/Los_Angeles]
System.out.println(zdtStartLater); // 2016-11-06T00:57:01-07:00[America/Los_Angeles]
LocalDateTime ldtEnd = LocalDateTime.parse("2016-11-06T01:56:01");
ZonedDateTime zdtEndEarly = ldtEnd.atZone(PT).withEarlierOffsetAtOverlap();
ZonedDateTime zdtEndLater = ldtEnd.atZone(PT).withLaterOffsetAtOverlap();
System.out.println(zdtEndEarly); // 2016-11-06T01:56:01-07:00[America/Los_Angeles]
System.out.println(zdtEndLater); // 2016-11-06T01:56:01-08:00[America/Los_Angeles]
As you can see, for the 00:57
time, it makes no difference, because that time is not in the overlap hour.