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

Java入門

Java LocalDate比較|isBefore/isAfter解説

CATEGORY
REV.
DRAWN BY
TOM
SHEETS
8枚
LocalDate(isBefore・isAfterで比較)

トム

SESで約8年、環境を変えられないもどかしさの中にいました。32歳で自社開発企業に転職し、今はClaude Codeと自動化で、仕事も時間も組み直している途中です。
・Javaバックエンドエンジニア(Java歴12年以上)
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎
詳細なプロフィール

𝕏

「Javaで日付を比較したいけど、equals()とcompareTo()とisEqual()のどれを使うのが正解?」。

結論から言うと、Java 8以降の現代では LocalDate+isBefore()/isAfter()/isEqual() が正解です。JavaでLocalDateを比較するなら、この3メソッドで足ります。

「以上・以下」は !isBefore()/!isAfter()、期間内の判定はその組み合わせで書けます。LocalDateTimeの比較、Dateとの違い、今日との比較も後半で扱います。

旧 Date と Timestamp を equals() で比べると、呼び出す向きによって結果が変わります(timestamp.equals(date) はミリ秒が同じでも false)。新人時代にこれで本番障害を起こした体験(Java歴12年)から、落とし穴まで整理しました。

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

この記事でわかること

  • Date と LocalDate の比較メソッド一覧と「同じか/前後か/差分」3パターン別の正解コード
  • LocalDateで「以上・以下」「期間内か」を判定する書き方と、文字列(yyyyMMdd)の日付を比較する方法
  • equals() の罠・タイムゾーンずれ・SimpleDateFormat のスレッドセーフ問題などのよくあるエラー
  • 新規/レガシー/パフォーマンス重視のシーン別おすすめ

PARTS LIST /目次

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

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

やりたいことjava.util.Date(旧)java.time.LocalDate(推奨)
同じ日か判定compareTo() == 0(時刻切捨て要)isEqual()
過去か判定before()isBefore()
未来か判定after()isAfter()
以上(同日を含む)か判定!before()!isBefore()
期間内(両端を含む)か判定!before(start) && !after(end)!isBefore(start) && !isAfter(end)
差分日数(getTime差) / 86400000(夏時間で誤差)ChronoUnit.DAYS.between()
等価判定で使ってはいけないequals()(timestamp.equals(date) が 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つのオブジェクトが「等しい」かを判定します。

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

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

// 2025-11-02 00:00:00 UTC のDate
Date d1 = new Date(1762041600000L);

// 同じミリ秒のTimestamp (Dateを継承)
java.sql.Timestamp t1 = new java.sql.Timestamp(1762041600000L);

System.out.println(d1.equals(t1)); // 結果: true  (Date側のequals()はgetTime()だけを比べる)
System.out.println(t1.equals(d1)); // 結果: false (Timestamp側のequals()はTimestamp以外をfalseにする)

これがよくハマる罠です。Dateのequals()は、相手がDate(サブクラスのTimestampを含む)で、getTime()のミリ秒が同じならtrueを返します。ところが、データベースから取得したjava.sql.Timestamp側のequals()は、相手がTimestampでないと常にfalseを返します。つまりdate.equals(timestamp)はtrue、timestamp.equals(date)はfalseと、呼び出す向きによって結果が変わります(equals()の対称性が崩れています)。

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): date1がdate2より過去ならtrue
  • date1.after(date2): date1がdate2より未来ならtrue

これらのメソッドは、compareTo()と同様に内部でミリ秒値(サブクラスの場合はgetTime())を比べているため、Timestampとの比較も安全におこなえます。

// 2025-11-01 00:00 UTC と 2025-11-02 00:00 UTC
Date date1 = new Date(1761955200000L);
Date date2 = new Date(1762041600000L);

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分」は、日付は同じですが、ミリ秒の値はちがいます(ミリ秒の値と日時の対応はUnixタイムスタンプ変換ツールで確認できます)。

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

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

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パッケージが導入されました。

同じ日の8:00と10:00を比べると(Date と LocalDate)
DATE / ミリ秒のタイムスタンプA 8:00 と B 10:00 は「別の値」dateA.compareTo(dateB) → -1
dateA.equals(dateB) → false
時・分・秒・ミリ秒まで持つため、同じ2025年11月1日でも2時間分の差がある
LOCALDATE / 年月日だけA・B とも 2025-11-01 で「同じ値」localA.isEqual(localB) → true
時刻を持たないので、どちらも同じ日付になる

もしあなたが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-DDTHH:mm:ss)をあつかいます。
  • ZonedDateTime: タイムゾーンをふくめた日付と時刻をあつかいます。
  1. 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パッケージでは、日付比較のメソッド名が統一され、格段にわかりやすくなりました。

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)); // 結果: true

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

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

isBefore・isAfter・isEqual の判定(a:9/1、b:9/10)
CALL / 呼び出し
結果
理由
a.isBefore(b)
true
a は b より「前」
a.isAfter(b)
false
a は b より後ではない
a.isEqual(b)
false
同じ日付なら true になる

LocalDateで「以上・以下」「期間内」を判定する書き方

isBefore()とisAfter()は、どちらも同じ日付のときはfalseを返します。つまり「より前」「より後」の判定であり、同日を含みません。

target の日付ごとの判定結果(start = 11/1、end = 11/30)
TARGET
!isBefore(start)
start 以上
!isAfter(end)
end 以下
期間内
(両端を含む)
10/31前日
falseisBefore(start) が true
trueisAfter(end) は false
false
11/1start当日
true同じ日なので isBefore(start) は false
trueisAfter(end) は false
true
11/10期間の中
trueisBefore(start) は false
trueisAfter(end) は false
true
11/30end当日
trueisBefore(start) は false
true同じ日なので isAfter(end) は false
true
12/1翌日
trueisBefore(start) は false
falseisAfter(end) が true
false
NOTEisBefore・isAfter は同じ日だと false。否定の ! を付けると、start 当日・end 当日も期間内に入ります。

「以上(同日を含む)」「以下(同日を含む)」を判定したいときは、反対側のメソッドを!で否定するのが定番です。期間内(開始日〜終了日、両端を含む)の判定も、この2つを&&でつなぐだけで書けます。

判定したいこと書き方
a が b より前(a < b)a.isBefore(b)
a が b より後(a > b)a.isAfter(b)
a が b 以上(a >= b)!a.isBefore(b)
a が b 以下(a <= b)!a.isAfter(b)
a が start〜end の期間内!a.isBefore(start) && !a.isAfter(end)
LocalDate target = LocalDate.of(2025, 11, 10);
LocalDate start  = LocalDate.of(2025, 11, 1);
LocalDate end    = LocalDate.of(2025, 11, 30);

// target が start 以上(同日を含む)か
boolean onOrAfter = !target.isBefore(start);   // 結果: true

// target が end 以下(同日を含む)か
boolean onOrBefore = !target.isAfter(end);     // 結果: true

// 期間内(start〜end、両端を含む)か
boolean inRange = !target.isBefore(start) && !target.isAfter(end); // 結果: true

// compareTo() は「符号」だけを見る(値は-1/0/1とは限らない)
System.out.println(target.compareTo(start) > 0); // 結果: true

// 文字列(yyyyMMdd)は LocalDate に変換してから比較する
LocalDate fromString = LocalDate.parse("20251115", DateTimeFormatter.BASIC_ISO_DATE);
System.out.println(fromString.isAfter(target)); // 結果: true

LocalDate.compareTo()は、年・月・日の順に差を返す実装のため、戻り値が-1/0/1になるとは限りません。== 1のように値そのものを比べず、> 0/< 0と符号だけで判定してください。可読性の面でも、条件分岐にはisBefore()/isAfter()を使うほうが意図が伝わります。

「20251115」のようなyyyyMMdd形式の文字列どうしも、桁数がそろっていれば文字列比較で大小は判定できます。ただし形式の崩れた値が混ざっても気づけないため、DateTimeFormatter.BASIC_ISO_DATEでLocalDateに変換してから比較するのが安全です。

LocalDateTimeの比較(時刻を含む場合・日付だけ比べたい場合)

LocalDateTimeもisBefore()/isAfter()/isEqual()で比較できます。ただし時刻・ナノ秒まで見るため、「同じ日か」を知りたいときはtoLocalDate()で日付だけに変換してから比べます。秒以下の違いを無視したいときはtruncatedTo()で精度をそろえます。

LocalDateTime a = LocalDateTime.of(2025, 11, 1, 8, 0);
LocalDateTime b = LocalDateTime.of(2025, 11, 1, 10, 0);

// 時刻まで含めて比べる
System.out.println(a.isBefore(b));  // true
System.out.println(a.isEqual(b));   // false

// 日付だけ比べる(toLocalDate()で時刻を捨てる)
System.out.println(a.toLocalDate().isEqual(b.toLocalDate())); // true

// 秒以下の違いを無視して比べる(分までに丸める)
LocalDateTime c = LocalDateTime.of(2025, 11, 1, 8, 0, 30);
LocalDateTime d = LocalDateTime.of(2025, 11, 1, 8, 0, 45);
System.out.println(c.truncatedTo(ChronoUnit.MINUTES)
        .isEqual(d.truncatedTo(ChronoUnit.MINUTES))); // true

2つの値の型が違う場合(LocalDateとLocalDateTime)は、isBefore()にそのまま渡せません。LocalDate側をatStartOfDay()で0時のLocalDateTimeにするか、LocalDateTime側をtoLocalDate()で日付にそろえてから比較してください。

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

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(1761955200000L); // 2025-11-01 UTC
Date date2 = new Date(1762041600000L); // 2025-11-02 UTC

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のLocalDateやLocalDateTimeは、タイムゾーン情報をもちません。

タイムゾーンをあつかうときは、ZonedDateTime(または時点だけならInstant・UTCオフセット付きならOffsetDateTime)を使います。

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

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

フォーマット変換時のParseException

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

旧 Date の場合

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

Java公式のAPIドキュメントでも、SimpleDateFormatは「同期されていない」、DateTimeFormatterは「不変でスレッドセーフ」と明記されています。SimpleDateFormat はレガシーコード保守時のみで利用するのが安全です。

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

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

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

JDBC 4.2 / JPA 2.2 の対応に加え、Jackson も jackson-datatype-jsr310(JavaTimeModule)を使えば(Spring Boot は自動で登録)、DBやJSONとも LocalDate 直結で扱えるため、APIの境界面まで java.time で揃えるのが現実的です。

とはいえレガシー混在のプロジェクトで「うっかり java.util.Date を新規追加してしまう」事故は起きがちです。

僕のチームでは Error Prone(Google製)の JavaUtilDate チェック をCIに組み込んで、新規の java.util.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 has a bad API that leads to bugs; prefer java.time.Instant or LocalDate.
//     Date d = new Date();
//     ^

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

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

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

まず、やりたい判定から使うメソッドを選ぶ流れを図にすると次のようになります。

やりたい判定から日付比較のメソッドを選ぶ
Java 8以降で LocalDate を使える?
いいえ(旧Date)before() / after() / compareTo()✕ equals() は使わない
はい何を判定したいかで選ぶ下の表
やりたい判定
LOCALDATE のメソッド
同じ日か
isEqual()
より前・より後か
isBefore() / isAfter()
以上・以下同日を含む
!isBefore() / !isAfter()
期間内か
!isBefore(start) && !isAfter(end)
何日離れているか
ChronoUnit.DAYS.between()

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

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以前のレガシー環境での比較の選び方

もし、どうしても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()も内部でミリ秒値を取り出して比べているだけなので、速度の差はほとんどありません。
  • LocalDateの比較:isEqual()やcompareTo()は十分に高速です。

参考までに、僕の手元(Apple M4 / Java 21 / JMH 1.37)で計測した1回あたりの時間と、それを1億回分に換算した値は次のとおりです。JITの最適化が効くため、実際の処理の中での値は条件によって変わります。

方式1億回の所要時間1回あたり
date1.compareTo(date2)約 0.07 秒約 0.7 ns
date1.getTime() == date2.getTime()約 0.07 秒約 0.7 ns
localDate1.isEqual(localDate2)約 0.09 秒約 0.9 ns

LocalDate.isEqual() がわずかに遅いものの、1ナノ秒に満たない差なので業務アプリでは無視できる範囲です。

読みやすさと安全性のメリットの方が圧倒的に大きいので、迷わず LocalDate 系を使ってください。

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

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

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

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

  1. Date と Timestamp を equals() 比較(冒頭の体験談)— timestamp.equals(date) の向きだと、ミリ秒が同じでも常に false。compareTo() または 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.Timestamp と java.util.Date を equals() で比較すると、呼び出す向きで結果が変わる典型バグを踏まないよう、新規は最初から LocalDate/LocalDateTime で受けるのが鉄則です。

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

Java 7以前なら、Joda-Timeライブラリを導入するのが定石です。

Joda-Timeは java.time の元になったAPIで、DateTime ・LocalDate ・isBefore()/isAfter()/isEqual() がそのまま使えます。

Java 8にアップグレード可能なら、まずアップグレードを優先してください。

REVISIONS / 改訂履歴
REV.DATE内容
52026-10-16図解を描き直し(図面のデザインにそろえた)
42026-10-04記載内容の事実確認と修正
32026-09-042026年9月時点の情報で全体を見直し
22026-04-262026年4月時点の情報で全体を見直し
12025-11-02初版
NEXT READ本棚から1本

AIで開発を組み直すなら、まずこの5冊から

入門・実践・設計まで、Claude Codeの本をレベル別に5冊選んで比べました。

→ おすすめ5冊を見る

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

トム

SESで約8年、環境を変えられないもどかしさの中にいました。32歳で自社開発企業に転職し、今はClaude Codeと自動化で、仕事も時間も組み直している途中です。
・Javaバックエンドエンジニア(Java歴12年以上)
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎
詳細なプロフィール

𝕏

Java入門