FULL PRODUCT VERSION :
java version "1.8.0_152"
Java(TM) SE Runtime Environment (build 1.8.0_152-b16)
Java HotSpot(TM) 64-Bit Server VM (build 25.152-b16, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Darwin Kernel Version 17.2.0
A DESCRIPTION OF THE PROBLEM :
According to TZ specification, "Etc/UTC" represents "UTC", so these two time zones are essentially the same:
ftp://ftp.iana.org/tz/data/etcetera
https://en.wikipedia.org/wiki/Tz_database#Area
So it is expectable, that the two ZoneId's created as:
ZoneId etcUtcZone = ZoneId.of("Etc/UTC");
ZoneId utcZone = ZoneId.of("UTC");
will be equal in all comparison related operations.
In fact these two objects has the same ZoneRules, what is correct. However, because ZomeId equality is based on ZoneId:
return getId().equals(other.getId());
and ChronoZonedDateTime.compareTo is based on comparing zone ids:
cmp = getZone().getId().compareTo(other.getZone().getId());
these all breaks a quite expectable logic, that these two ZonedDateTimes (zoneDateTimeEtcUtc and zoneDateTimeUtc):
ZonedDateTime now = ZonedDateTime.now();
ZoneId etcUtcZone = ZoneId.of("Etc/UTC");
ZonedDateTime zoneDateTimeEtcUtc = now.withZoneSameInstant(etcUtcZone);
ZoneId utcZone = ZoneId.of("UTC");
ZonedDateTime zoneDateTimeUtc = now.withZoneSameInstant(utcZone);
Should be effectively equal:
zoneDateTimeEtcUtc.compareTo(zoneDateTimeUtc) should be 0 and
zoneDateTimeEtcUtc.equals(zoneDateTimeUtc) should be true.
However they aren't due to the reasons described above.
The technical reason for this behaviour are clear, but it seems to be bug in the design of implementation compareTo and equals methods. Otherwise this is a bug in documentation, because it is not expectable that these two dates build in the same timezone would be different.
From the other hand when using "UTC" and "UTC+0" timezones dates become equal:
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));
// This one fails
assertEquals(zonedDateTimeUtc,zonedDateTimeUtc0);
It is clear why it is so, but the general behaviour still seems not proper.
See also this question on StackOverflow: https://stackoverflow.com/questions/48462758/zoneddatetime-comparison-expected-etc-utc-but-was-utc
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
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();
ZoneId etcUtcZone = ZoneId.of("Etc/UTC");
ZonedDateTime zoneDateTimeEtcUtc = now.withZoneSameInstant(etcUtcZone);
ZoneId utcZone = ZoneId.of("UTC");
ZonedDateTime zoneDateTimeUtc = now.withZoneSameInstant(utcZone);
// This is okay
assertEquals(Timestamp.from(zoneDateTimeEtcUtc.toInstant()), Timestamp.from(zoneDateTimeUtc.toInstant()));
assertEquals(0, zoneDateTimeEtcUtc.compareTo(zoneDateTimeUtc));
// This one fails
assertEquals(zoneDateTimeEtcUtc, zoneDateTimeUtc);
}
}
Compile and run this code.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The test should run without fails.
ACTUAL -
The test fails.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
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();
ZoneId etcUtcZone = ZoneId.of("Etc/UTC");
ZonedDateTime zoneDateTimeEtcUtc = now.withZoneSameInstant(etcUtcZone);
ZoneId utcZone = ZoneId.of("UTC");
ZonedDateTime zoneDateTimeUtc = now.withZoneSameInstant(utcZone);
// This is okay
assertEquals(Timestamp.from(zoneDateTimeEtcUtc.toInstant()), Timestamp.from(zoneDateTimeUtc.toInstant()));
assertEquals(0, zoneDateTimeEtcUtc.compareTo(zoneDateTimeUtc));
// This one fails
assertEquals(zoneDateTimeEtcUtc, zoneDateTimeUtc);
}
}
---------- END SOURCE ----------
java version "1.8.0_152"
Java(TM) SE Runtime Environment (build 1.8.0_152-b16)
Java HotSpot(TM) 64-Bit Server VM (build 25.152-b16, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Darwin Kernel Version 17.2.0
A DESCRIPTION OF THE PROBLEM :
According to TZ specification, "Etc/UTC" represents "UTC", so these two time zones are essentially the same:
ftp://ftp.iana.org/tz/data/etcetera
https://en.wikipedia.org/wiki/Tz_database#Area
So it is expectable, that the two ZoneId's created as:
ZoneId etcUtcZone = ZoneId.of("Etc/UTC");
ZoneId utcZone = ZoneId.of("UTC");
will be equal in all comparison related operations.
In fact these two objects has the same ZoneRules, what is correct. However, because ZomeId equality is based on ZoneId:
return getId().equals(other.getId());
and ChronoZonedDateTime.compareTo is based on comparing zone ids:
cmp = getZone().getId().compareTo(other.getZone().getId());
these all breaks a quite expectable logic, that these two ZonedDateTimes (zoneDateTimeEtcUtc and zoneDateTimeUtc):
ZonedDateTime now = ZonedDateTime.now();
ZoneId etcUtcZone = ZoneId.of("Etc/UTC");
ZonedDateTime zoneDateTimeEtcUtc = now.withZoneSameInstant(etcUtcZone);
ZoneId utcZone = ZoneId.of("UTC");
ZonedDateTime zoneDateTimeUtc = now.withZoneSameInstant(utcZone);
Should be effectively equal:
zoneDateTimeEtcUtc.compareTo(zoneDateTimeUtc) should be 0 and
zoneDateTimeEtcUtc.equals(zoneDateTimeUtc) should be true.
However they aren't due to the reasons described above.
The technical reason for this behaviour are clear, but it seems to be bug in the design of implementation compareTo and equals methods. Otherwise this is a bug in documentation, because it is not expectable that these two dates build in the same timezone would be different.
From the other hand when using "UTC" and "UTC+0" timezones dates become equal:
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));
// This one fails
assertEquals(zonedDateTimeUtc,zonedDateTimeUtc0);
It is clear why it is so, but the general behaviour still seems not proper.
See also this question on StackOverflow: https://stackoverflow.com/questions/48462758/zoneddatetime-comparison-expected-etc-utc-but-was-utc
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
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();
ZoneId etcUtcZone = ZoneId.of("Etc/UTC");
ZonedDateTime zoneDateTimeEtcUtc = now.withZoneSameInstant(etcUtcZone);
ZoneId utcZone = ZoneId.of("UTC");
ZonedDateTime zoneDateTimeUtc = now.withZoneSameInstant(utcZone);
// This is okay
assertEquals(Timestamp.from(zoneDateTimeEtcUtc.toInstant()), Timestamp.from(zoneDateTimeUtc.toInstant()));
assertEquals(0, zoneDateTimeEtcUtc.compareTo(zoneDateTimeUtc));
// This one fails
assertEquals(zoneDateTimeEtcUtc, zoneDateTimeUtc);
}
}
Compile and run this code.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The test should run without fails.
ACTUAL -
The test fails.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
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();
ZoneId etcUtcZone = ZoneId.of("Etc/UTC");
ZonedDateTime zoneDateTimeEtcUtc = now.withZoneSameInstant(etcUtcZone);
ZoneId utcZone = ZoneId.of("UTC");
ZonedDateTime zoneDateTimeUtc = now.withZoneSameInstant(utcZone);
// This is okay
assertEquals(Timestamp.from(zoneDateTimeEtcUtc.toInstant()), Timestamp.from(zoneDateTimeUtc.toInstant()));
assertEquals(0, zoneDateTimeEtcUtc.compareTo(zoneDateTimeUtc));
// This one fails
assertEquals(zoneDateTimeEtcUtc, zoneDateTimeUtc);
}
}
---------- END SOURCE ----------