1

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.

rotzbouw
  • 11
  • 1

2 Answers2

1

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();
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thank you for the suggestion on using the the ZonedDateTime to get the OffsetDateTime, I think that's a neat workaround to my issue. However, I disagree with the statement, that OffsetDateTime.now() is not associated with a zone. It's merely a convenience method calling another method using the system default zone. And it could well do that with anysubsequent plusX methods referring to the updated time, thus "fixing" the offset and LocalDateTime information. – rotzbouw Oct 19 '20 at 12:23
  • 1
    "And it could well do that" No it can't. `OffsetDateTime` does not have a `zone` field. It has no zone information. `plusX` doesn't know that you created the `OffsetDateTime` using `.now()`, and it shouldn't know either. I could very well create an `OffsetDateTime` with offset UTC+12, should `plusX` still use the system default zone? No. @rotzbouw – Sweeper Oct 19 '20 at 12:46
  • @Sweeper Thanks for your comment. I think I'm still missing a piece of the puzzle. If `OffsetDateTime` is instantiated with `now()`, it is assigned a `ZoneOffset` (containing offset in seconds and, as it extends `ZoneId` also the description, e.g. `Europe/Berlin`). As far as I can tell, every instantiation assigns a ZoneOffset to the object and each subsequent call, after modifying the UTC seconds, nanos, etc, could apply the same code as found in `ofInstant(Instant instant, ZoneId zone)` using the previously set ZoneId to set the ZoneOffset. – rotzbouw Oct 19 '20 at 13:17
  • @rotzbouw It can't, because it would need a `ZoneId`, in order to use `ofInstant`, and there is no `ZoneId` available. It can't always use the system zone, because it will not work for any `OffsetDateTime` that doesn't have the system zone. And no, there is no "previously set ZoneId" in a `OffsetDateTime`. There is only a `ZoneOffset`. – Sweeper Oct 19 '20 at 13:20
  • @Sweeper `OffsetDateTime` contains `ZoneOffset` which extends `ZoneId`. What do you mean by 'and there is no ZoneId available'? – rotzbouw Oct 19 '20 at 13:27
  • 1
    @rotzbouw Yes you can pass a `ZoneOffset` to a `ZoneId` parameter, but you will be passing a _constant offset_, and it would work exactly the same way as _not_ doing the extra `ofInstant` that you are proposing. Anyway, can `OffsetDateTime` be implemented to work as you described? Yes. Should it? No, because this class is meant to represent a _datetime with a constant offset_, not a changing one. – Sweeper Oct 19 '20 at 13:42
  • 1
    @rotzbouw: It's a pity that the Java API has *somewhat* conflated offsets and time zones - but `ZoneOffset` is *not* a time zone. As Sweeper says, it's a fixed offset. – Jon Skeet Oct 19 '20 at 14:07
0

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.

Sweeper
  • 213,210
  • 22
  • 193
  • 313