ZonedDateTimeを使用する際の注意点 - Object.equals vs Assertions.isEqualTo
概要
Javaには時間を表現するためのオブジェクトがいくつか存在します。この記事では、その中でも最も情報量が多いZonedDateTimeを使用した時間の比較方法について説明します。
異なるが同じ時間?
まず、簡単なテストコードを書いて、何か特異な点がないか確認してみましょう。
ZonedDateTime seoulZonedTime = ZonedDateTime.parse("2021-10-10T10:00:00+09:00[Asia/Seoul]");
ZonedDateTime utcTime = ZonedDateTime.parse("2021-10-10T01:00:00Z[UTC]");
assertThat(seoulZonedTime.equals(utcTime)).isFalse();
assertThat(seoulZonedTime).isEqualTo(utcTime);
このコードはテストに合格します。equalsがfalseを返す一方で、isEqualToは合格します。なぜでしょうか?
実際には、上記のコードにおける2つのZonedDateTimeオブジェクトは同じ時間を表しています。しかし、ZonedDateTimeは内部的にLocalDateTime、ZoneOffset、およびZoneIdを含んでいるため、equalsを使用して比較すると、絶対時間ではなくオブジェクトの値が同じかどうかをチェックします。
そのため、equalsはfalseを返します。
ZonedDateTime#equals
しかし、isEqualToは時間オブジェクトの操作に関して異なる動作をするようです。
実際、ZonedDateTimeを比較する際、isEqualToはZonedDateTimeのequalsを呼び出すのではなく、ChronoZonedDateTimeByInstantComparator#compareを呼び出します。

Comparator#compareが呼び出される。
内部実装を見ると、toEpochSecond()を使用して秒に変換することで比較が行われていることがわかります。つまり、equalsを通じてオブジェクトを比較するのではなく、compareを通じて絶対時間を比較しています。
これに基づいて、ZonedDateTimeの比較を以下のようにまとめることができます:
equals
: オブジェクトを比較
isEqualTo
: 絶対時間を比較
したがって、ZonedDateTimeを間接的に含むオブジェクトを比較する場合、equalsが呼び出されるため、ZonedDateTimeの絶対値に基づいて比較したい場合は、オブジェクト内でequalsメソッドをオーバーライドする必要があります。
public record Event(
String name,
ZonedDateTime eventDateTime
) {
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Event event = (Event) o;
return Objects.equals(name, event.name)
&& Objects.equals(eventDateTime.toEpochSecond(), event.eventDateTime.toEpochSecond());
}
@Override
public int hashCode() {
return Objects.hash(name, eventDateTime.toEpochSecond());
}
}
@Test
void equals() {
ZonedDateTime time1 = ZonedDateTime.parse("2021-10-10T10:00:00+09:00[Asia/Seoul]");
ZonedDateTime time2 = ZonedDateTime.parse("2021-10-10T01:00:00Z[UTC]");
Event event1 = new Event("event", time1);
Event event2 = new Event("event", time2);
assertThat(event1).isEqualTo(event2); // pass
}
結論
ZonedDateTime間でequalsが呼び出される際に絶対時間を比較したい場合は、toEpochSecond()などを使用して変換する必要があります。- テストコードや類似のシナリオで
isEqualToを使用して直接ZonedDateTimeを比較する場合、equalsは呼び出されず、内部変換が行われるため、別途変換する必要はありません。 - オブジェクト内に
ZonedDateTimeが含まれている場合、必要に応じてオブジェクトのequalsメソッドをオーバーライドする必要があります。
