Javaで10年以上開発にたずさわっている筆者です。私が新人だったころ、JavaのDateクラスを使った日付比較で大きな失敗をした経験があります。
テストでは動いていたのに、本番環境でequals()による比較が期待通りに動かなかったのです。原因はjava.util.Dateとjava.sql.Timestampをequals()で比べていたからでした。この経験から、Javaの日付あつかいの難しさを痛感したのです。
Javaの日付比較は、昔ながらのDateクラスと、Java8から導入されたLocalDateなどで、あつかい方がまったく異なります。
この記事は、「Javaで日付をどうやって比べたらよいか分からない」「DateとLocalDateのどちらを使うべきか迷っている」という方に向けて書いています。
この記事を読み終えるころには、以下の点が明確になります。
- 古い
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つのオブジェクトが「等しい」かを判定します。
しかし、Dateのequals()は、比較対象が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これがよくハマる罠です。DateとTimestampをequals()で比較すると、ミリ秒が同じでもfalseになります。
compareTo() はミリ秒を比べる
compareTo(Date anotherDate)メソッドは、2つのDateオブジェクトの内部的なミリ秒(long値)を比較します。
date1<date2ならマイナスの値date1==date2なら 0date1>date2ならプラスの値
compareTo()はequals()と違い、Timestampのようなサブクラスであっても、ミリ秒の値だけをきちんと比較してくれます。
System.out.println(d1.compareTo(t1)); // 結果: 0 (ミリ秒が同じ)before() と after() を使うのが基本
Dateクラスで日付の前後を比較する場合は、before()とafter()メソッドを使うのが一般的です。
date1.before(date2):date1がdate2より過去ならtruedate1.after(date2):date1がdate2より未来なら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が導入されたのには、明確な理由があります。
- DateオブジェクトはsetTime()メソッドで内部のミリ秒をあとから変えられます。オブジェクトをメソッドにわたしたとき、中で値が変えられてしまう可能性があり、安全ではありません。java.timeのクラス(LocalDateなど)は不変です。plusDays()などで日付を操作しても、元のオブジェクトは変わらず、かならず新しいオブジェクトが返されます。これにより、安全に日付をあつかえます。
- 目的が明確である:Date常にタイムスタンプでした。Java8では、目的にあわせてクラスがわかれています。
LocalDate: 日付(YYYY-MM-DD)のみ。時刻やタイムゾーンをもちません。LocalTime: 時刻(HH:mm:ss)のみ。日付やタイムゾーンをもちません。LocalDateTime: 日付と時刻(YYYY-MM-DD-HH-mm-ss)をあつかいます。ZonedDateTime: タイムゾーンをふくめた日付と時刻をあつかいます。
- APIが直感的である:Dateでは日付の計算にCalendarクラスが必要で、月の指定が0からはじまる(1月が0)など、あつかいにくい点が多くありました。LocalDateはplusDays(1)(1日たす)のように、直感的なメソッドがそろっています。
旧Dateクラスと新APIの比較
DateとJava8のLocalDate / LocalDateTimeは、まったくの別物です。両者のちがいをまとめます。
| 比較項目 | java.util.Date | java.time.LocalDate / LocalDateTime |
| あつかう情報 | タイムスタンプ(ミリ秒) | LocalDateは日付のみ。LocalDateTimeは日付と時刻。 |
| 可変性 | 可変(Mutable) | 不変(Immutable) |
| スレッドセーフ | スレッドセーフではない | スレッドセーフ |
| API | Calendarと併用。直感的ではない。 | plusDays()など直感的。 |
| タイムゾーン | あいまいにあつかう | ZonedDateTimeで明確にあつかう。 |
とくに「不変(Immutable)」である点は、複雑な業務アプリケーションを開発するうえで、バグを防ぐために非常に重要です。
isBefore()・isAfter()・isEqual()の使い方
Java8のjava.timeパッケージでは、日付比較のメソッド名が統一され、非常にわかりやすくなりました。
Dateのbefore() after() equals() compareTo()の複雑さはありません。LocalDateもLocalDateTimeも、以下の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)); // 結果: trueLocalDateの場合、isEqual()は「年月日」がすべて一致すればtrueを返します。Dateのequals()のような型の問題はおきません。
LocalDateTimeのisEqual()は、ミリ秒のさらに下、「ナノ秒」までが一致しないと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 + " 日です"); // 結果: 9between(A, B)はBからAを引いた日数を返します。非常に安全で、一行で計算できます。
日付比較でよくあるエラーと注意点

最後に、日付比較のあつかいで、いまでもよく見かけるエラーや注意点をまとめます。
タイムゾーンの違いによるズレ
java.util.Dateは、内部てきにはUTC(協定世界時)のミリ秒をもちますが、toString()などで表示するときは、Javaが動いているサーバーのデフォルトタイムゾーンを使います。
日本のサーバー(JST)と、海外のサーバー(UTC)では、同じnew Date()でも、ちがう時刻をさしているように見えるため、混乱のもとになります。
Java8のLocalDateやLocalDateTimeは、タイムゾーン情報をもちません。タイムゾーンをあつかうときは、かならずZonedDateTimeを使います。
// タイムゾーンを明確に指定する (Java8)
ZoneId zoneTokyo = ZoneId.of("Asia/Tokyo");
ZonedDateTime zdtTokyo = ZonedDateTime.now(zoneTokyo);タイムゾーンを意識しない日付比較は、システム障害につながる可能性があります。
フォーマット変換時のParseException
文字列を日付オブジェクトに変換するとき、ParseExceptionがよく発生します。
旧 Date の場合
SimpleDateFormatを使いますが、このクラスはスレッドセーフではありません。
Webアプリケーションなどで、SimpleDateFormatをstatic変数(共通の変数)にして使いまわすと、同時にリクエストが来たときに内部状態がこわれ、予期せぬエラーや間違った日付変換がおこります。
新 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());コードが複雑になるため、できるかぎりLocalDate(java.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も直感的にあつかえます。