ZonedDateTime comparison: expected: [Etc/UTC] but was: [UTC]

Andremoniy picture Andremoniy · Jan 26, 2018 · Viewed 8.6k times · Source

I was comparing two dates which seem to be equal, but they contain a different name of zones: one is Etc/UTC, another is UTC.

According to this question: Is there a difference between the UTC and Etc/UTC time zones? - this two zones are the same. But my tests fail:

import org.junit.Test;
import java.sql.Timestamp;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import static org.junit.Assert.assertEquals;

public class TestZoneDateTime {

    @Test
    public void compareEtcUtcWithUtc() {
        ZonedDateTime now = ZonedDateTime.now();
        ZonedDateTime zoneDateTimeEtcUtc = now.withZoneSameInstant(ZoneId.of("Etc/UTC"));
        ZonedDateTime zoneDateTimeUtc = now.withZoneSameInstant(ZoneId.of("UTC"));

        // This is okay
        assertEquals(Timestamp.from(zoneDateTimeEtcUtc.toInstant()), Timestamp.from(zoneDateTimeUtc.toInstant()));
        // This one fails
        assertEquals(zoneDateTimeEtcUtc,zoneDateTimeUtc);

        // This fails as well (of course previous line should be commented!)
        assertEquals(0, zoneDateTimeEtcUtc.compareTo(zoneDateTimeUtc));
    }
}

The result:

java.lang.AssertionError: 
Expected :2018-01-26T13:55:57.087Z[Etc/UTC]
Actual   :2018-01-26T13:55:57.087Z[UTC]

More specifically, I would expect, that ZoneId.of("UTC") would be equal to ZoneId.of("Etc/UTC"), but they aren't!

As @NicolasHenneaux suggested, I should probably use compareTo(...) method. That's good idea, but zoneDateTimeEtcUtc.compareTo(zoneDateTimeUtc) returns -16 value, because of this implementation inside ZoneDateTime:

cmp = getZone().getId().compareTo(other.getZone().getId());

Assertion result:

java.lang.AssertionError: 
Expected :0
Actual   :-16

So the problem lies somewhere in ZoneId implementation. But I still would expect that if both zone ids are valid and both designate the same zone, then they should be equal.

My question is: is it a library bug, or I am doing something wrong?

UPDATE

Several people tried to convince me that it is a normal behaviour, and it is normal that the implementation of comparison methods uses String id representation of the ZoneId. In this case I should ask, why does the following test runs okay?

    @Test
    public void compareUtc0WithUtc() {
        ZonedDateTime now = ZonedDateTime.now();
        ZoneId utcZone = ZoneId.of("UTC");
        ZonedDateTime zonedDateTimeUtc = now.withZoneSameInstant(utcZone);
        ZoneId utc0Zone = ZoneId.of("UTC+0");
        ZonedDateTime zonedDateTimeUtc0 = now.withZoneSameInstant(utc0Zone);

        // This is okay
        assertEquals(Timestamp.from(zonedDateTimeUtc.toInstant()), Timestamp.from(zonedDateTimeUtc0.toInstant()));
        assertEquals(0, zonedDateTimeUtc.compareTo(zonedDateTimeUtc0));
        assertEquals(zonedDateTimeUtc,zonedDateTimeUtc0);
    }

If Etc/UTC is the same as UTC, then I see two options:

  • compareTo/equals method shouldn't use ZoneId id, but should compare their rules
  • Zone.of(...) is broken and should treat Etc/UTC and UTC as the same time zones.

Otherwise I don't see why UTC+0 and UTC work fine.

UPDATE-2 I have reported a bug, ID : 9052414. Will see what Oracle team will decide.

UPDATE-3 The bug report accepted (don't know will they close it as "won't fix" or not): https://bugs.openjdk.java.net/browse/JDK-8196398

Answer

javaguest picture javaguest · Jan 29, 2018

You can convert the ZonedDateTime objects to Instant, as the other answers/comments already told.

ZonedDateTime::isEqual

Or you can use the isEqual method, which compares if both ZonedDateTime instances correspond to the same Instant:

ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime zoneDateTimeEtcUtc = now.withZoneSameInstant(ZoneId.of("Etc/UTC"));
ZonedDateTime zoneDateTimeUtc = now.withZoneSameInstant(ZoneId.of("UTC"));

Assert.assertTrue(zoneDateTimeEtcUtc.isEqual(zoneDateTimeUtc));