本ページはプロモーションが含まれています

Java入門

Java日付比較の3つの方法とLocalDateの使い方

トム

・都内自社開発IT企業勤務/javaのバックエンドエンジニア
/java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎

Javaで10年以上開発にたずさわっている筆者です。私が新人だったころ、JavaのDateクラスを使った日付比較で大きな失敗をした経験があります。

テストでは動いていたのに、本番環境でequals()による比較が期待通りに動かなかったのです。原因はjava.util.Datejava.sql.Timestampequals()で比べていたからでした。この経験から、Javaの日付あつかいの難しさを痛感したのです。

Javaの日付比較は、昔ながらのDateクラスと、Java8から導入されたLocalDateなどで、あつかい方がまったく異なります。

この記事は、「Javaで日付をどうやって比べたらよいか分からない」「DateLocalDateのどちらを使うべきか迷っている」という方に向けて書いています。

この記事を読み終えるころには、以下の点が明確になります。

  • 古いDateクラスでの比較の危険性
  • Java8のLocalDateを使った安全な比較方法
  • 日付比較でよくあるエラーとその解決策

Javaでの日付比較のあやふやな知識をなくし、自信をもってコードが書けるようになります。

Javaで日付を比較する基本方法

Javaには、大きくわけて2つの日付APIが存在します。Java 7以前のjava.util.Dateと、Java8 から導入されたjava.timeパッケージ(LocalDateなど)です。

どちらを使うかによって、日付の比較方法は大きく異なります。まずは、昔からあるDateクラスでの比較の基本をみていきましょう。

Dateクラスを使った日付比較の仕組み

java.util.Dateクラスは、その名前とはうらはらに「日付」だけをあつかうものではありません。

Dateオブジェクトの実体は、「エポック」(1970年1月1日 0時0分0秒)からの経過時間をミリ秒であらわした数値(long型)です。

long time = System.currentTimeMillis();
Date date = new Date(time);

つまり、Dateオブジェクトは常に「時・分・秒・ミリ秒」までの情報をもっています。「2025年11月1日」というDateオブジェクトを作ったつもりでも、内部的には「2025年11月1日 0時0分0秒0ミリ秒」のようなタイムスタンプとしてあつかわれます。

この「ミリ秒までのタイムスタンプである」という点が、日付比較を行ううえで最初の注意点となります。

compareTo()とequals()の違いを理解しよう

Dateオブジェクトを比較するメソッドとして、equals()compareTo()があります。この2つは、まったくちがう動きをします。

equals() は使ってはいけない

equals(Object obj)メソッドは、2つのオブジェクトが「等しい」かを判定します。

しかし、Dateequals()は、比較対象がDate型であり、かつ内部のミリ秒がまったく同じである場合のみtrueを返します。

ここで大きな問題になるのが、データベースから取得したjava.sql.Timestampなど、Dateのサブクラスとの比較です。

// 2025年11月1日 のDate
Date d1 = new Date(1762041600000L); 

// 2025年11月1日 のTimestamp (Dateを継承)
java.sql.Timestamp t1 = new java.sql.Timestamp(1762041600000L); 

// ミリ秒は同じでも、型がちがうため false になる!
System.out.println(d1.equals(t1)); // 結果: false

これがよくハマる罠です。DateTimestampequals()で比較すると、ミリ秒が同じでもfalseになります。

compareTo() はミリ秒を比べる

compareTo(Date anotherDate)メソッドは、2つのDateオブジェクトの内部的なミリ秒(long値)を比較します。

  • date1 < date2 ならマイナスの値
  • date1 == date2 なら 0
  • date1 > date2 ならプラスの値

compareTo()equals()と違い、Timestampのようなサブクラスであっても、ミリ秒の値だけをきちんと比較してくれます。

System.out.println(d1.compareTo(t1)); // 結果: 0 (ミリ秒が同じ)

before() と after() を使うのが基本

Dateクラスで日付の前後を比較する場合は、before()after()メソッドを使うのが一般的です。

  • date1.before(date2): date1date2より過去ならtrue
  • date1.after(date2): date1date2より未来ならtrue

これらのメソッドは、内部でcompareTo()を呼びだしているため、Timestampとの比較も安全におこなえます。

// 2025年11月1日 と 2025年11月2日
Date date1 = new Date(1762041600000L);
Date date2 = new Date(1762128000000L);

System.out.println(date1.before(date2)); // 結果: true
System.out.println(date1.after(date2)); // 結果: false

ミリ秒単位の比較に注意すべき理由

before() after() compareTo() は、ミリ秒までを厳密に比較します。

これが問題になるのは、「日付(年月日)だけ」を比べたいときです。

たとえば、「2025年11月1日 8時0分」と「2025年11月1日 10時0分」は、日付は同じですが、ミリ秒の値はちがいます。

// 2025-11-01 08:00:00
Date dateA = new Date(1762070400000L); 

// 2025-11-01 10:00:00
Date dateB = new Date(1762077600000L); 

System.out.println(dateA.compareTo(dateB)); // 結果: -1 (dateAのほうが過去)
System.out.println(dateA.equals(dateB));    // 結果: false

もし「同じ日付か」を判定したい場合、Dateオブジェクトの「時・分・秒・ミリ秒」をすべて0にそろえる処理が必要でした。

// 昔の方法:Calendarをつかって時刻を切りすてる
Calendar cal = Calendar.getInstance();
cal.setTime(dateA);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
Date truncatedDateA = cal.getTime();

このように、Dateクラスで「日付だけ」をあつかうのは、非常に手間でバグを生みやすいものでした。

Java8以降のDateとLocalDateの違い

このようなDateクラスの問題点をかいけつするために、Java8でjava.timeパッケージが導入されました。

もしあなたがJava8以降のバージョンを使っているなら、Dateクラスではなく、java.timeパッケージのクラス(LocalDateなど)を使うべきです。

LocalDate・LocalDateTimeの登場背景

Java8で新しい日付APIが導入されたのには、明確な理由があります。

  1. DateオブジェクトはsetTime()メソッドで内部のミリ秒をあとから変えられます。オブジェクトをメソッドにわたしたとき、中で値が変えられてしまう可能性があり、安全ではありません。java.timeのクラス(LocalDateなど)は不変です。plusDays()などで日付を操作しても、元のオブジェクトは変わらず、かならず新しいオブジェクトが返されます。これにより、安全に日付をあつかえます。
  2. 目的が明確である:Date常にタイムスタンプでした。Java8では、目的にあわせてクラスがわかれています。
    • LocalDate: 日付(YYYY-MM-DD)のみ。時刻やタイムゾーンをもちません。
    • LocalTime: 時刻(HH:mm:ss)のみ。日付やタイムゾーンをもちません。
    • LocalDateTime: 日付と時刻(YYYY-MM-DD-HH-mm-ss)をあつかいます。
    • ZonedDateTime: タイムゾーンをふくめた日付と時刻をあつかいます。
  3. APIが直感的である:Dateでは日付の計算にCalendarクラスが必要で、月の指定が0からはじまる(1月が0)など、あつかいにくい点が多くありました。LocalDateはplusDays(1)(1日たす)のように、直感的なメソッドがそろっています。

旧Dateクラスと新APIの比較

DateとJava8のLocalDate / LocalDateTimeは、まったくの別物です。両者のちがいをまとめます。

比較項目java.util.Datejava.time.LocalDate / LocalDateTime
あつかう情報タイムスタンプ(ミリ秒)LocalDateは日付のみ。LocalDateTimeは日付と時刻。
可変性可変(Mutable)不変(Immutable)
スレッドセーフスレッドセーフではないスレッドセーフ
APICalendarと併用。直感的ではない。plusDays()など直感的。
タイムゾーンあいまいにあつかうZonedDateTimeで明確にあつかう。

とくに「不変(Immutable)」である点は、複雑な業務アプリケーションを開発するうえで、バグを防ぐために非常に重要です。

isBefore()・isAfter()・isEqual()の使い方

Java8のjava.timeパッケージでは、日付比較のメソッド名が統一され、非常にわかりやすくなりました。

Datebefore() after() equals() compareTo()の複雑さはありません。LocalDateLocalDateTimeも、以下の3つのメソッドで比較します。

  • isBefore(): 過去か
  • isAfter(): 未来か
  • isEqual(): 同じか
LocalDate date1 = LocalDate.of(2025, 11, 1);
LocalDate date2 = LocalDate.of(2025, 11, 5);
LocalDate date3 = LocalDate.of(2025, 11, 1);

// date1 は date2 より過去か
System.out.println(date1.isBefore(date2)); // 結果: true

// date1 は date2 より未来か
System.out.println(date1.isAfter(date2)); // 結果: false

// date1 は date3 と同じか
System.out.println(date1.isEqual(date3)); // 結果: true

LocalDateの場合、isEqual()は「年月日」がすべて一致すればtrueを返します。Dateequals()のような型の問題はおきません。

LocalDateTimeisEqual()は、ミリ秒のさらに下、「ナノ秒」までが一致しないとtrueにならない点には注意が必要です。

日付比較の実装パターン別サンプルコード

Java8のLocalDateを使ったほうが、コードがどれだけスッキリするか、パターン別にみてみましょう。

2つの日付が同じか判定するコード例

旧 Date の場合(非推奨)

equals()は危険なため、compareTo()が0か、または日付をまるめて(時刻を0にして)比較する必要がありました。

// 時刻を切りすてるユーティリティ (例: Apache Commons Lang)
// DateUtils.truncate(date, Calendar.DAY_OF_MONTH) などが必要

// もし時刻が0であることが保証されているなら...
if (date1.compareTo(date2) == 0) {
    System.out.println("同じ日付です");
}

新 LocalDate の場合 (推奨)

LocalDateは時刻情報をもたないため、isEqual()だけで安全に比較できます。

LocalDate date1 = LocalDate.of(2025, 11, 1);
LocalDate date2 = LocalDate.of(2025, 11, 1);

if (date1.isEqual(date2)) {
    System.out.println("同じ日付です");
}

コードのシンプルさと安全性がまったくちがいます。

指定日より前後かを判定する方法

旧 Date の場合(非推奨)

before()after() を使います。

Date date1 = new Date(1762041600000L); // 2025-11-01
Date date2 = new Date(1762128000000L); // 2025-11-02

if (date1.before(date2)) {
    System.out.println("date1 は date2 より過去です");
}

新 LocalDate の場合 (推奨)

isBefore()isAfter() を使います。

LocalDate date1 = LocalDate.of(2025, 11, 1);
LocalDate date2 = LocalDate.of(2025, 11, 2);

if (date1.isBefore(date2)) {
    System.out.println("date1 は date2 より過去です");
}

どちらも書き方は似ていますが、LocalDateは時刻情報にまどわされる心配がありません。

日付の差分(日数)を求めるサンプル

旧 Date の場合(非推奨)

ミリ秒の差をもとめ、1日のミリ秒(1000 * 60 * 60 * 24)で割る必要がありました。

long diffInMillis = date2.getTime() - date1.getTime();
long diffInDays = diffInMillis / (1000 * 60 * 60 * 24);

System.out.println("差は " + diffInDays + " 日です");

この方法は、サマータイム(夏時間)がある地域では、日付の変わり目で計算がずれる危険性があります。

新 LocalDate の場合 (推奨)

ChronoUnitという便利なクラスを使います。

LocalDate date1 = LocalDate.of(2025, 11, 1);
LocalDate date2 = LocalDate.of(2025, 11, 10);

long diffInDays = ChronoUnit.DAYS.between(date1, date2);

System.out.println("差は " + diffInDays + " 日です"); // 結果: 9

between(A, B)はBからAを引いた日数を返します。非常に安全で、一行で計算できます。

日付比較でよくあるエラーと注意点

最後に、日付比較のあつかいで、いまでもよく見かけるエラーや注意点をまとめます。

タイムゾーンの違いによるズレ

java.util.Dateは、内部てきにはUTC(協定世界時)のミリ秒をもちますが、toString()などで表示するときは、Javaが動いているサーバーのデフォルトタイムゾーンを使います。

日本のサーバー(JST)と、海外のサーバー(UTC)では、同じnew Date()でも、ちがう時刻をさしているように見えるため、混乱のもとになります。

Java8のLocalDateLocalDateTimeは、タイムゾーン情報をもちません。タイムゾーンをあつかうときは、かならずZonedDateTimeを使います。

// タイムゾーンを明確に指定する (Java8)
ZoneId zoneTokyo = ZoneId.of("Asia/Tokyo");
ZonedDateTime zdtTokyo = ZonedDateTime.now(zoneTokyo);

タイムゾーンを意識しない日付比較は、システム障害につながる可能性があります。

フォーマット変換時のParseException

文字列を日付オブジェクトに変換するとき、ParseExceptionがよく発生します。

旧 Date の場合

SimpleDateFormatを使いますが、このクラスはスレッドセーフではありません。

Webアプリケーションなどで、SimpleDateFormatstatic変数(共通の変数)にして使いまわすと、同時にリクエストが来たときに内部状態がこわれ、予期せぬエラーや間違った日付変換がおこります。

新 LocalDate の場合

DateTimeFormatterを使います。こちらはスレッドセーフです。static変数にして安全に使いまわせます。

// 安全(スレッドセーフ)
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd");

public LocalDate parseDate(String text) {
    return LocalDate.parse(text, FORMATTER);
}

DateとLocalDateの混在による型エラー

古いシステムを改修していると、古いDateと新しいLocalDateが混ざってしまうことがあります。

この2つはまったく別の型のため、直接比較できません。どちらかに変換するひつようがあります。

Date から LocalDate へ

Date date = new Date();
ZoneId zone = ZoneId.systemDefault();
LocalDate localDate = date.toInstant().atZone(zone).toLocalDate();

LocalDate から Date へ

LocalDate localDate = LocalDate.now();
ZoneId zone = ZoneId.systemDefault();
Date date = Date.from(localDate.atStartOfDay(zone).toInstant());

コードが複雑になるため、できるかぎりLocalDatejava.time)に統一するのが理想です。

シーン別おすすめの日付比較方法まとめ

ここまで解説した内容をふまえ、どのような場合にどの比較方法を選ぶべきかをまとめます。

業務アプリ開発で安全な比較方法

java.time (LocalDate, LocalDateTime) が推奨されます。

Java8以降の環境であれば、DateやCalendarを使う理由はありません。

不変(Immutable)でスレッドセーフなjava.timeパッケージを使い、isBefore() isAfter() isEqual() で比較するのが、もっとも安全でバグのない方法です。

軽量スクリプトでの簡易比較の選び方

もし、どうしてもJava 7以前の古い環境で動かす必要がある場合、Dateクラスを使うしかありません。

その場合は、equals()は絶対に使わず、before() after() compareTo() を使って比較します。

また、日付(年月日)だけを比べたいときは、時刻情報を切りすてる処理(Calendarを使うか、ライブラリのDateUtils.truncateなど)を忘れないようにします。

パフォーマンスを意識した比較のコツ

数百万件のデータをループで処理するなど、パフォーマンスがもとめられる場合、比較のコストが気になるかもしれません。

  • Dateの比較:date1.getTime() == date2.getTime() のように、long値どうしで比較するのが最速です。compareTo()before()は、内部でこのgetTime()を呼びだしているため、わずかにオーバーヘッドがあります。
  • LocalDateの比較:isEqual()compareTo()は十分に高速です。

ただし、パフォーマンスのボトルネックになるのは、日付の「比較」よりも、「文字列からの変換(パース)」や「オブジェクトの生成」であることがほとんどです。

ループの中でnew Date()LocalDate.parse()をくりかえすのはさけ、比較そのものはjava.timeのメソッドをそのまま使うのが、コードの読みやすさと性能のバランスがとれたよい方法です。

まとめ:Java日付比較はLocalDateで安全に

Javaの日付比較は、Dateクラスの「ミリ秒のタイムスタンプである」という性質と、equals()のワナを理解していないと、手ごわいバグにつながります。

  • Date: before() after() compareTo() で比較する。equals()は使わない。
  • LocalDate (Java8以降): isBefore() isAfter() isEqual() で比較する。

これから新しく書くコードでは、Java8のjava.timeパッケージ(LocalDate LocalDateTime)を使いましょう。

日付のみ、時刻のみ、といった目的が明確で、不変で安全、APIも直感的にあつかえます。

  • この記事を書いた人
  • 最新記事

トム

・都内自社開発IT企業勤務/javaのバックエンドエンジニア
/java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎

-Java入門