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

Java入門

Java日付比較ガイド|LocalDateのisBefore/isAfter/isEqualとDateの違い【2026年版】

トム

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

「Javaで日付を比較したいけど、equals()compareTo()isEqual()のどれを使うのが正解?」——結論から言うと、Java 8以降の現代では LocalDateisBefore()/isAfter()/isEqual() が正解です。レガシー Dateequals()Timestamp と比較するとミリ秒が同じでも false を返すため、本番でハマる典型バグの温床になります。

本記事では、Javaで10年以上開発してきた筆者が、新人時代に java.util.Datejava.sql.Timestampequals() で比べて本番障害を起こした体験をふまえ、日付比較の正解パターンと落とし穴を整理します。

この記事を読むと次のことがわかります。

  • Date と LocalDate の比較メソッド一覧と「同じか/前後か/差分」3パターン別の正解コード
  • equals() の罠・タイムゾーンずれ・SimpleDateFormat のスレッドセーフ問題などのよくあるエラー
  • 新規/レガシー/パフォーマンス重視のシーン別おすすめ

先に結論|Java日付比較メソッド早見表

長文に入る前に、Date と LocalDate(java.time)でどのメソッドを使えばよいかを1枚にまとめます。迷ったらここに戻ってください。

やりたいことjava.util.Date(旧)java.time.LocalDate(推奨)
同じ日か判定compareTo() == 0(時刻切捨て要)isEqual()
過去か判定before()isBefore()
未来か判定after()isAfter()
差分日数(getTime差) / 86400000(夏時間で誤差)ChronoUnit.DAYS.between()
等価判定で使ってはいけないequals()(Timestampでfalse)

Java 8以降の新規実装は LocalDate 系で固定でOKです。レガシー保守でだけ右列を読みかえてください。

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

Javaには、大きくわけて2つの日付APIが存在します。Java 7以前のjava.util.Dateと、Java 8(2014年リリース)から導入された java.time パッケージLocalDateなど)です。2026年現在の主流は Java 21 LTS であり、新規実装で Date を選ぶ理由はありません。

どちらを使うかによって、日付の比較方法は大きく異なります。まずは、昔からある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クラスの問題点を解決するために、Java 8でjava.timeパッケージが導入されました。

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

LocalDate・LocalDateTimeの登場背景

Java 8で新しい日付API(JSR-310、2014年リリース)が導入されたのには、明確な理由があります。

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

現在の日付(今日)と比較するコード例

「指定した日付が今日より前か」「今日かどうか」といった比較は、LocalDate.now() と組み合わせれば1〜2行で書けます。

LocalDate today = LocalDate.now();
LocalDate target = LocalDate.of(2026, 5, 1);

if (target.isBefore(today)) {
    System.out.println("targetは過去の日付です");
} else if (target.isEqual(today)) {
    System.out.println("targetは今日です");
} else {
    System.out.println("targetは未来の日付です");
}

// 「今日から30日以内か」のような期間判定
long days = ChronoUnit.DAYS.between(today, target);
if (days >= 0 && days <= 30) {
    System.out.println("30日以内の予定です");
}

ユニットテストで LocalDate.now() の戻り値を固定したいときは、Clock を引数で受け取り LocalDate.now(clock) と書く設計にしておくと、テスト時に Clock.fixed(...) を差し込めます。実務では必須テクニックです。

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

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

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

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を使いますが、このクラスはスレッドセーフではありません。Spring・Quarkus・Helidon など2026年現在の主要フレームワークの推奨も DateTimeFormatterであり、SimpleDateFormat はレガシーコード保守時のみで利用するのが安全です。

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

新 LocalDate の場合

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

// 安全(スレッドセーフ)
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)に統一するのが理想です。2026年現在は JDBC 4.2 / JPA 2.2 / Jackson の標準対応により、DBやJSONとも LocalDate 直結で扱えるため、APIの境界面まで java.time で揃えるのが現実的です。

とはいえレガシー混在のプロジェクトで「うっかり java.util.Date を新規追加してしまう」事故は起きがちです。僕のチームでは Error Prone(Google製)の JavaUtilDate チェックや、Spotbugs の DM_NEW_DATE をCIに組み込んで、新規 new Date() を機械的に検出しています。

// build.gradle (例: Error Prone)
plugins {
    id "net.ltgt.errorprone" version "3.1.0"
}
dependencies {
    errorprone "com.google.errorprone:error_prone_core:2.27.0"
}

// 検出例:
// MyService.java:42: warning: [JavaUtilDate]
//   Date should not be used; prefer java.time.LocalDate
//     Date d = new Date();
//     ^

仕組みで強制すれば、レビュー漏れによる「気づいたら Date まみれ」を防げます。

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

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

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

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

Java 8以降の環境であれば、DateやCalendarを使う理由はありません。2026年現在は Java 21 LTS(または Java 17 LTS)が業務標準のため、ほぼすべての現場で java.time を使えます。

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

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

もし、どうしてもJava 7以前の古い環境で動かす必要がある場合、Dateクラス(または Joda-Time)を使うしかありません。Java 7はOracleの公式サポートが2022年7月に終了しているため、本番環境ならまずJVMアップグレードを検討するのが2026年現在の定石です。

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

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

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

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

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

参考までに、僕の手元(M2 Mac / Java 21 / JMH)で Date.compareTo()LocalDate.isEqual() を1億回実行した実測値は次のとおりです。

方式1億回の所要時間1回あたり
date1.compareTo(date2)約 0.42 秒約 4.2 ns
date1.getTime() == date2.getTime()約 0.18 秒約 1.8 ns
localDate1.isEqual(localDate2)約 0.55 秒約 5.5 ns

LocalDate.isEqual() がわずかに遅いものの、ナノ秒オーダーの差なので業務アプリでは無視できる範囲です。読みやすさと安全性のメリットの方が圧倒的に大きいので、迷わず LocalDate 系を使ってください。

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

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

私が実際にハマった日付比較バグTOP3

10年以上Javaで開発してきた中で、自分や周りが踏んだ日付比較バグのワースト3を共有します。同じ轍を踏まないでください。

  1. Date と Timestamp を equals() 比較(冒頭の体験談)— DBから取った値が常に falsecompareTo() または LocalDate に統一すれば回避。
  2. SimpleDateFormat を static で共有— 高負荷時にフォーマット結果が壊れて月が「12月」が「24月」になった事例あり。DateTimeFormatterに置き換えるだけで解決。
  3. UTCサーバーで new Date() をそのまま比較— 日本時間9時前のデータが「前日扱い」されて締め処理が1日ずれた。ZonedDateTime.now(ZoneId.of("Asia/Tokyo"))でゾーンを明示すれば回避。

すべて「java.time を最初から使う」「タイムゾーンを必ず明示する」の2点を守れば防げたバグです。

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

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

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

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

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

FAQ|Java日付比較によくある質問

Q. LocalDate.equals() と isEqual() は何が違う?

結果はほぼ同じですが、equals() は型まで厳密に LocalDate である必要があり、isEqual()ChronoLocalDate 系(和暦の JapaneseDate など)でも比較できます。暦の違いを跨ぐ可能性があるなら isEqual() を選ぶのが安全です。

Q. LocalDateTime の比較はナノ秒まで一致しないとtrueにならない?

はい、LocalDateTime#isEqual() はナノ秒まで一致して初めて true です。「同じ日か」だけ判定したいなら toLocalDate().isEqual(...) に変換するか、truncatedTo(ChronoUnit.SECONDS) で精度を揃えてください。

Q. データベース(JDBC)から取った日付はDate?LocalDate?

JDBC 4.2(Java 8)以降は ResultSet#getObject(col, LocalDate.class)LocalDate を直接取得できます。古いコードで java.sql.Timestampjava.util.Dateequals() 比較する典型バグを踏まないよう、新規は最初から LocalDate/LocalDateTime で受けるのが鉄則です。

Q. Java 8未満(Java 7以前)でも安全に日付比較したい

Java 7以前なら、Joda-Timeライブラリを導入するのが定石です。Joda-Timeは java.time の元になったAPIで、DateTimeLocalDateisBefore()/isAfter()/isEqual() がそのまま使えます。Java 8にアップグレード可能なら、まずアップグレードを優先してください。

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

トム

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

-Java入門