OffsetDateTime object's LocalDateTime and ZoneOffset values differ when using differing instantiation methods

599 views Asked by At

Creating OffsetDateTime objects using

  1. ofInstant(instant, zoneid) or via
  2. fluent interface

can lead to non-equal objects (by using compareTo assertions or comparing the ZoneOffset and LocalDateTime fields) if the instantiation via the fluent interface crosses a Daylight Saving Time boundary. Consider the following example:

OffsetDateTime inAMonth = OffsetDateTime.now().plusMonths(1);
OffsetDateTime inAMonth2 = OffsetDateTime.ofInstant(inAMonth.toInstant(), ZoneId.systemDefault());

In central Europe (ZoneId 'Europe/Berlin') in mid-October this will yield two non-equal objects due to plusMonths() re-using the offset of the initial call (now()).

Does anyone know why the offset is not recalculated?

I ran into this issue during a unit test and the only workarounds I could come up with were a) not using the fluent interface or b) refrain from using cross-DST jumps while using fluent interface. Using something other than OffsetDateTime is not an option, unfortunately.

2

There are 2 answers

7
Jon Skeet On

Does anyone know why the offset is not recalculated?

Because the OffsetDateTime value returned by OffsetDateTime.now() isn't associated with any particular time zone, only an offset. The offset is determined as "the current offset in the system default time zone", but after that there's no association with the system default time zone.

If you want a value that is associated with a time zone, use ZonedDateTime.now() instead. You can convert the result of ZonedDateTime.now().plusMonths(1) into an OffsetDateTime afterwards:

OffsetDateTime inAMonth = ZonedDateTime.now().plusMonths(1).toOffsetDateTime();
0
Sweeper On

OffsetDateTime.plusMonth never changes the offset, since it's an OffsetDateTime - a date, a time, with a constant offset. If you want the offset to change, use a ZonedDateTime, because only a zone's offset can change.

When creating an OffsetDateTime using an instant and zone, however, it obviously needs to get the offset of the specified zone at the specified instant. Well, at the specified instant, the DST transition has already happened, so inAMonth2 has the post-DST transition offset.