java 8 - ZonedDateTime not equal another ZonedDateTime
Asked Answered
F

4

17

I created two ZonedDateTime objects and I think they are should be equal:

public static void main(String[] args) {
    ZoneId zid = ZoneId.of("America/New_York");
    ZoneOffset offset = ZoneOffset.from(LocalDateTime.now().atZone(zid));
    ZonedDateTime zdt0 = ZonedDateTime.of(2014, 8, 24, 21, 10, 1, 777000002, offset);
    ZonedDateTime zdt1 = ZonedDateTime.of(2014, 8, 24, 21, 10, 1, 777000002, zid);
    boolean equals = Objects.equals(zdt0, zdt1);
    System.out.println("equals: " + equals);
}

In debugger I see that class of member of ZonedDateTime zone in first case is java.time.ZoneOffset and in second java.time.ZoneRegion and this is makes ZonedDateTime objects not equal. This is confusing... Any ideas?

Formate answered 11/9, 2014 at 17:28 Comment(0)
B
34

You are checking for object equality which evaluates to false as these objects are not equivalent. One is bound to a ZoneId, the other to a ZoneOffset. If you want to check whether they represent the same time, you can use the not very intuitively named method isEqual.

E.g.:

ZoneId zid = ZoneId.of("America/New_York");
ZoneOffset offset = ZoneOffset.from(LocalDateTime.now().atZone(zid));
ZonedDateTime zdt0 = ZonedDateTime.of(2014, 8, 24, 21, 10, 1, 777000002, offset);
ZonedDateTime zdt1 = ZonedDateTime.of(2014, 8, 24, 21, 10, 1, 777000002, zid);
System.out.println("isEqual:" + zdt0.isEqual(zdt1));
System.out.println("equals: " + zdt0.equals(zdt1));

prints:

isEqual:true
equals: false

Btw, note that you don’t need to use Objects.equals(a,b) for two objects you already know to be non-null. You can invoke a.equals(b) directly.

Bircher answered 11/9, 2014 at 18:9 Comment(1)
Thank you! This is helpful and you are right: isEqual is not not very intuitive...Formate
B
8

This played hell on me for hours too when using Jackson to serialize / deserialize instances of ZonedDateTime and then compare them against each other for equality to verify that my code was working correctly. I don't fully understand the implications but all I've learned is to use isEqual instead of equals. But this throws a big wrench in testing plans as most assertion utilities will just call the standard .equals().

Here's what I finally came up with after struggling for quite some time:

@Test
public void zonedDateTimeCorrectlyRestoresItself() {

    // construct a new instance of ZonedDateTime
    ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Z"));
    // offset = {ZoneOffset@3820} "Z"
    // zone   = {ZoneOffset@3820} "Z"

    String starting = now.toString();

    // restore an instance of ZonedDateTime from String
    ZonedDateTime restored = ZonedDateTime.parse(starting);
    // offset = {ZoneOffset@3820} "Z"
    // zone   = {ZoneOffset@3820} "Z"

    assertThat(now).isEqualTo(restored); // ALWAYS succeeds

    System.out.println("test");
}

@Test
public void jacksonIncorrectlyRestoresZonedDateTime() throws Exception {

    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.findAndRegisterModules();

    // construct a new instance of ZonedDateTime
    ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Z"));
    // offset = {ZoneOffset@3820} "Z"
    // zone   = {ZoneOffset@3820} "Z"


    String converted = objectMapper.writeValueAsString(now);

    // restore an instance of ZonedDateTime from String
    ZonedDateTime restored = objectMapper.readValue(converted, ZonedDateTime.class);
    // offset = {ZoneOffset@3820} "Z"
    // zone   = {ZoneOffset@3821} "UTC"

    assertThat(now).isEqualTo(restored); // NEVER succeeds

    System.out.println("break point me");
}
Berube answered 18/5, 2017 at 22:21 Comment(0)
F
2

The equals() method on ZonedDateTime requires that all component parts of the object are equal. Since a ZoneOffset is not equal to a ZoneRegion (even though both are subclasses of ZoneId), the method returns false. Read about VALJOs to understand more as to why value types are compared in this way.

The isEqual method only compares the instant on the time-line, which may or may not be what you want. You can also use the timeLineOrder() method to compare two ZoneDateTime only using the time-line.

Flatwise answered 11/9, 2014 at 18:59 Comment(0)
F
1

This caused us pain for a while too. The ZonedDateTime values actually represent identical times, however their zone "types" are different, which is why they are not equal.

The above answers are correct, however I thought I'd add a visual that might further help:

enter image description here

In the ZonedDateTime code, we find, which shows the zone comparison:

enter image description here

Fatso answered 3/3, 2021 at 15:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.