メインコンテンツにスキップ

ZonedDateTimeを使用する際の注意点 - Object.equals vs Assertions.isEqualTo

· 4分の読み時間
Haril Song
Owner, Software Engineer at 42dot

概要

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);

このコードはテストに合格します。equalsfalseを返す一方で、isEqualToは合格します。なぜでしょうか?

実際には、上記のコードにおける2つのZonedDateTimeオブジェクトは同じ時間を表しています。しかし、ZonedDateTimeは内部的にLocalDateTimeZoneOffset、およびZoneIdを含んでいるため、equalsを使用して比較すると、絶対時間ではなくオブジェクトの値が同じかどうかをチェックします。

そのため、equalsfalseを返します。

image1 ZonedDateTime#equals

しかし、isEqualToは時間オブジェクトの操作に関して異なる動作をするようです。

実際、ZonedDateTimeを比較する際、isEqualToZonedDateTimeequalsを呼び出すのではなく、ChronoZonedDateTimeByInstantComparator#compareを呼び出します。

image2

image3 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メソッドをオーバーライドする必要があります。