Java - Result when compare two ZonedDateTime is not as expected

Hai Hoang picture Hai Hoang · Apr 6, 2018 · Viewed 11.6k times · Source

I have this code :

ZonedDateTime t1 = ZonedDateTime.parse("2018-04-06T10:01:00.000+03:00");
ZonedDateTime t2 = ZonedDateTime.parse("2018-04-06T10:01:00.000-03:00");
System.out.println(t1.compareTo(t2));

The result is -1. I guess it will convert both t1 and t2 to the same timezone then compare it. E.g in the first case it will (maybe) compare

t1 = 2018-04-06T07:01:00.000+00:00 to t2 = 2018-04-06T13:01:00.000+00:00

This case return 1 as I expected

ZonedDateTime t1 = ZonedDateTime.parse("2018-04-06T17:01:00.000+03:00");
ZonedDateTime t2 = ZonedDateTime.parse("2018-04-06T10:01:00.000-03:00");
System.out.println(t1.compareTo(t2));

But this one (*) doesn't return 0, it return 1 :

ZonedDateTime t1 = ZonedDateTime.parse("2018-04-06T16:01:00.000+03:00");
ZonedDateTime t2 = ZonedDateTime.parse("2018-04-06T10:01:00.000-03:00");
System.out.println(t1.compareTo(t2));

I even try to use truncatedTo(ChronoUnit.MINUTES) after parse the string to ignore the seconds part but nothing change.

The most interested thing is if I decrease / increase t1 / t2 by one second, the result will be -1

e.g :

ZonedDateTime t1 = ZonedDateTime.parse("2018-04-06T16:01:00.000+03:00");
ZonedDateTime t2 = ZonedDateTime.parse("2018-04-06T10:01:01.000-03:00");
System.out.println(t1.compareTo(t2));

In my use case, all user input time will be saved in the above format in many ZoneOffset and I have some query where I need to compare it with server time (ignore the seconds of the time). How can I get t1 equals t2 in case (*) above?

Answer

Ole V.V. picture Ole V.V. · Apr 6, 2018

The comparison is based first on the instant, then on the local date-time, then on the zone ID, then on the chronology. It is "consistent with equals", as defined by Comparable.

compareTo is implemented in the superclass ChronoZonedDateTime, a class that we usually don’t care about, but where we need to look for the documentation of some of the methods. So the above quote comes from there.

So in the case where both ZonedDateTime objects denote the same instant, t1’s local time is 16:01, which is greater than the t2’s 10:01. So 1 is the correct and expected result. On the other hand, if the instants are just one second apart — or just 1 nanosecond — that takes precedence, and the local times are not compared.

I believe this explains the behaviour you observed.

The quote furthermore gives a hint as to why this must be so: Suppose that comparing two ZonedDateTime objects with same instant but different time zones returned 0. Then the comparison would no longer be consistent with equals. Which is considered a pretty nice property (though not required).

The natural ordering for a class C is said to be consistent with equals if and only if e1.compareTo(e2) == 0 has the same boolean value as e1.equals(e2) for every e1 and e2 of class C.

(Quoted from Comparable interface documentation)

So if you wanted to compare just the instants, there are two options depending on what type you prefer for your result:

  1. Use isBefore, isAfter or isEqual:

    ZonedDateTime t1 = ZonedDateTime.parse("2018-04-06T16:01:00.000+03:00");
    ZonedDateTime t2 = ZonedDateTime.parse("2018-04-06T10:01:00.000-03:00");
    System.out.println(t1.isBefore(t2));
    System.out.println(t1.isAfter(t2));
    System.out.println(t1.isEqual(t2));
    

    Output:

false
false
true
  1. Use the answer by Yoav Gur: explicitly compare the instants.