Javaで日付を比較する方法を完全解説|LocalDate・LocalDateTime・Dateの使い分け

目次

1. この記事で分かること

Javaで「日付を比較したい」と思ったとき、最初に押さえるべき結論はシンプルです。

  • 基本は java.time(LocalDate / LocalDateTime / Instant / ZonedDateTime)を使う
  • 「日付だけ」なのか「日時」なのかで選ぶ型が変わる
  • 比較は isBefore / isAfter / isEqual が最も分かりやすい
  • Date(java.util.Date)はレガシー。触るなら早めに java.time に変換して扱う

ここを外すと、初心者がよく踏む「ズレる」「一致しない」「思った判定にならない」というトラブルに繋がりやすいです。

1.1 まず最短で理解:日付比較の正解ルート

「日付比較」という言葉は一見同じでも、実際には次の4パターンがあります。

  • 前後を判定したい(締切より前か?/未来か?)
  • 同じかどうかを判定したい(同日か?/同一時刻か?)
  • 期間内かを判定したい(開始〜終了に含まれるか?)
  • 差分を出したい(何日後?何分後?)

そして、これらを安全に書くためには「型選び」が重要です。

1.2 「日付」なのか「日時」なのかで型が変わる

初心者が一番混乱しやすいのがここです。あなたが比較したいのはどちらでしょうか?

日付だけ(カレンダーの日付)を比較したい場合

例:

  • 2026-01-09 が 2026-01-10 より前か?
  • 誕生日が今日と同じか?

この場合は LocalDate が最適です。

  • 時刻を持たない(= 00:00 などの概念でぶれない)
  • 「同日判定」が直感的に書ける

日時(時刻まで含める)を比較したい場合

例:

  • 2026-01-09 18:30 が 19:00 より前か?
  • 予約時間を過ぎたか?

この場合は LocalDateTime が基本になります。

  • 日付+時刻を持つ
  • 「同じ日だけど時刻が違う」を区別できる

タイムゾーンが絡む(世界対応・サーバとユーザーが違う)場合

例:

  • 日本時間での「今日」と、海外ユーザーの「今日」が違う
  • サマータイムで1日が24時間じゃない日がある

この場合は ZonedDateTime または Instant を使うのが安全です。

1.3 比較メソッドはこれだけ覚えればOK(java.time)

java.time での比較は、まずこの3つが基本です。

  • isBefore():前か?
  • isAfter():後か?
  • isEqual():同じか?

たとえば LocalDate なら次のように書けます。

import java.time.LocalDate;

LocalDate a = LocalDate.of(2026, 1, 9);
LocalDate b = LocalDate.of(2026, 1, 10);

System.out.println(a.isBefore(b)); // true
System.out.println(a.isAfter(b));  // false
System.out.println(a.isEqual(b));  // false

この書き方は「読みやすさ」が強いです。初心者でも、英語の意味のまま理解できます。

1.4 compareTo は「並び替え」や「大小判定」に向いている

日付を比較して「どっちが先か」を数値で扱いたい場合は compareTo() が便利です。

  • 戻り値が 負数:左が小さい(=前)
  • 戻り値が 0:同じ
  • 戻り値が 正数:左が大きい(=後)
int result = a.compareTo(b);

if (result < 0) {
    System.out.println("aはbより前");
} else if (result == 0) {
    System.out.println("aとbは同じ");
} else {
    System.out.println("aはbより後");
}

ただし、初心者向けに「前後判定」を書くなら、基本は isBefore/isAfter の方が直感的です。
compareTo は、ソート(並び替え)や、結果を数値として扱いたいときに真価を発揮します。

1.5 旧APIの Date は「比較できるけど、落とし穴が多い」

java.util.Date でも比較はできます。たとえば before() / after() があります。

ですが、初心者がつまずきやすい点があります。

  • ミリ秒まで含めた「日時」として扱われる
  • 「同日判定」のつもりでも、時刻が違えば一致しない
  • タイムゾーンや変換が絡むと解釈ミスが起きやすい

そのため、この記事では次の方針で進めます。

  • まず java.time を中心に日付比較を説明する
  • Date は現場で避けられない場面に絞って、安全な扱い方(変換)も含めて説明する

1.6 この記事の読み方(あなたの目的別ガイド)

あなたが知りたいことに合わせて、読みどころを先に案内しておきます。

  • とにかく日付の前後判定をしたい
    → 3章(LocalDate / LocalDateTime の比較)が最優先
  • Dateしか使えない古いコードに苦しんでいる
    → 4章(Date比較と変換)を重点的に
  • 文字列(String)の”2026-01-09″を比較したい
    → 5章(パースして比較)が必読
  • 開始日〜終了日で範囲チェックしたい
    → 6章(期間内判定)のテンプレを使う
  • 日数差、経過時間を出したい
    → 7章(Period/Duration)
  • タイムゾーンでズレて困ったことがある
    → 8章(Instant/ZonedDateTime)

2. Javaの日付・日時型を整理(比較の前提)

日付比較で混乱が起きる最大の原因は、「どの型が、何を表しているのかを曖昧なまま使ってしまうこと」です。
ここでは比較の前提として、Javaに存在する主な日付・日時型を整理します。

2.1 Javaには日付・日時の型が複数存在する

Javaで使われる日付・日時関連の型は、大きく分けて次の2系統があります。

  • 旧API(レガシー)
    • java.util.Date
    • java.util.Calendar
  • 新API(java.time パッケージ)
    • LocalDate
    • LocalDateTime
    • ZonedDateTime
    • Instant

現在のJava開発では、原則として java.time を使うのが正解です。
ただし、既存システムや外部ライブラリの都合で旧APIが残っている現場も多いため、両方を理解しておく必要があります。

2.2 「日付だけ」か「日時」かを最初に決める

まず重要なのは、比較したい対象が何かを明確にすることです。

カレンダー上の日付だけを扱いたい場合

例:

  • 今日が締切日より前か
  • 誕生日が同じ日か

この場合は LocalDate を使います。

LocalDate today = LocalDate.now();
LocalDate deadline = LocalDate.of(2026, 1, 31);

特徴は次の通りです。

  • 年・月・日のみを保持
  • 時刻の概念を持たない
  • 「同日判定」が直感的にできる

「日付比較」と言われたら、まず LocalDate を疑う
これを覚えておくと失敗が減ります。

2.3 日時(時刻まで)を比較したい場合

例:

  • 予約時刻を過ぎたか
  • ログイン時間の前後関係

この場合は LocalDateTime を使います。

LocalDateTime start = LocalDateTime.of(2026, 1, 9, 18, 30);
LocalDateTime end   = LocalDateTime.of(2026, 1, 9, 19, 0);

特徴:

  • 年・月・日・時・分・秒を保持
  • タイムゾーンの情報は含まない
  • 同じ「日」でも時刻が違えば別物として扱える

「同日だけど時間が違う」という区別が必要な場合に必須です。

2.4 タイムゾーンが絡む場合は要注意

次のようなケースでは、LocalDateTime だけでは不十分です。

  • サーバーは日本、ユーザーは海外
  • 世界共通の締切時刻を扱う
  • サマータイムが存在する地域を考慮する

このような場合は ZonedDateTime または Instant を使います。

ZonedDateTime:どこの日時かを含める

ZonedDateTime tokyoTime =
    ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
  • 日時+タイムゾーンを保持
  • 表示用・ユーザー向け処理に向いている

Instant:世界共通の「瞬間」

Instant now = Instant.now();
  • タイムゾーンを持たない
  • UTC基準の「一点の時刻」
  • 比較や保存(DB・ログ)に非常に強い

保存や比較は Instant、表示は ZonedDateTime
これは実務でよく使われる安全な設計パターンです。

2.5 java.util.Date がややこしい理由

java.util.Date は一見シンプルに見えますが、初心者には扱いづらい特徴があります。

Date date = new Date();

問題点は次の通りです。

  • 中身は「日時(ミリ秒)」なのに、クラス名が Date
  • 日付だけを扱っているつもりでも、時刻が必ず含まれる
  • 同日判定をしたいときに失敗しやすい

たとえば「2026-01-09」のつもりでも、内部的には
「2026-01-09 00:00:00.000」や「2026-01-09 12:34:56.789」
として扱われます。

このため、Date同士の equals / compareTo は直感とズレやすいのです。

2.6 現代Javaでのおすすめ指針(まとめ)

ここまでを踏まえた、実務向けの指針を整理します。

  • 日付だけ → LocalDate
  • 日時(TZ不要) → LocalDateTime
  • 世界共通の比較・保存 → Instant
  • 表示・ユーザー向け → ZonedDateTime
  • Dateを受け取ったら → すぐ java.time に変換

この前提を押さえておけば、次章の「具体的な日付比較」はスムーズに理解できます。

3. java.time(LocalDate / LocalDateTime)で日付を比較する【最重要】

ここからが、この記事の核心です。
現在のJava開発において、日付比較はほぼ java.time で完結します。
まずは LocalDateLocalDateTime の比較方法を確実に押さえましょう。

3.1 前後判定の基本:isBefore / isAfter / isEqual

最もよく使うのが「前か後か」「同じか」の判定です。

LocalDate の前後比較

LocalDate d1 = LocalDate.of(2026, 1, 9);
LocalDate d2 = LocalDate.of(2026, 1, 10);

System.out.println(d1.isBefore(d2)); // true
System.out.println(d1.isAfter(d2));  // false
System.out.println(d1.isEqual(d2));  // false

ポイントは次の通りです。

  • isBefore:左が右より「前」
  • isAfter:左が右より「後」
  • isEqual:同じ日付

メソッド名がそのまま意味を表しているため、初心者でも読みやすく、安全です。

3.2 LocalDateTime でも比較方法は同じ

日時を扱う LocalDateTime でも、使い方は変わりません。

LocalDateTime t1 = LocalDateTime.of(2026, 1, 9, 18, 30);
LocalDateTime t2 = LocalDateTime.of(2026, 1, 9, 19, 0);

System.out.println(t1.isBefore(t2)); // true
System.out.println(t1.isAfter(t2));  // false
System.out.println(t1.isEqual(t2));  // false

ただし注意点があります。

  • 同じ日でも、時刻が違えば isEqual は false
  • 「同日かどうか」を見たい場合は、次の方法を使います

3.3 「同日か?」を判定したいときの正しい書き方

LocalDateTime で「同じ日かどうか」を判定したい場合、
日付部分だけを切り出すのが安全です。

boolean sameDay =
    t1.toLocalDate().isEqual(t2.toLocalDate());

この書き方のメリットは、

  • 時刻の違いに影響されない
  • 意図がコードから明確に読み取れる

「同日判定 = LocalDate に落としてから比較」
これは実務で非常によく使われるパターンです。

3.4 compareTo を使った大小比較と並び替え

compareTo() は、比較結果を数値として扱いたい場合に便利です。

int result = d1.compareTo(d2);

if (result < 0) {
    System.out.println("d1はd2より前");
} else if (result == 0) {
    System.out.println("同じ日付");
} else {
    System.out.println("d1はd2より後");
}

この特性を利用すると、コレクションの並び替えが簡単に書けます。

List<LocalDate> dates = List.of(
    LocalDate.of(2026, 1, 10),
    LocalDate.of(2026, 1, 8),
    LocalDate.of(2026, 1, 9)
);

dates.stream()
     .sorted()
     .forEach(System.out::println);

LocalDateLocalDateTime自然順序(昇順)を持っているため、特別な Comparator を書かなくても並び替えできます。

3.5 equals と isEqual の違いを理解する

LocalDate では equals()isEqual() の結果は基本的に同じです。

d1.equals(d2);
d1.isEqual(d2);

しかし、読みやすさの観点では次のように使い分けると良いです。

  • 条件判定 → isEqual
  • コレクション操作・キー比較 → equals

意図が明確になることで、後から読む人が理解しやすくなります。

3.6 よくある失敗例:null と比較してしまう

日付比較で意外と多いのが null による例外です。

LocalDate date = null;
date.isBefore(LocalDate.now()); // NullPointerException

対策としては、

  • 事前に null チェックを行う
  • Optional を使う
  • 「未設定=無期限」など、仕様を明確に決める

比較ロジックを書く前に、null の意味を設計で決めておくことが重要です。

3.7 まとめ:java.time での比較はこれだけ覚えればOK

  • 前後判定 → isBefore / isAfter
  • 同一判定 → isEqual
  • 並び替え → compareTo
  • 同日判定 → toLocalDate() して比較

これらを押さえれば、日付比較の8割以上は対応可能です。

4. java.util.Date で日付を比較する(レガシー対応)

現場によっては、今でも java.util.Date を避けられないケースがあります。
古いシステム、既存ライブラリ、外部APIとの連携などが理由です。
ここでは 「Dateをどう安全に扱うか」 に絞って解説します。

4.1 Date 同士の前後比較:before / after

Date には、前後を判定するためのメソッドが用意されています。

Date d1 = new Date(126, 0, 9);  // 2026-01-09
Date d2 = new Date(126, 0, 10); // 2026-01-10

System.out.println(d1.before(d2)); // true
System.out.println(d1.after(d2));  // false

一見すると問題なさそうですが、ここには注意点があります。

  • 内部的には 日時(ミリ秒単位)で比較される
  • 「同日かどうか」の比較には向かない

4.2 compareTo / equals の落とし穴

DateComparable を実装しているため、compareTo() も使えます。

int result = d1.compareTo(d2);

また、equals() も存在します。

boolean same = d1.equals(d2);

しかし初心者がよく勘違いするのがここです。

  • 同じ日付でも、時刻が違えば equals は false
  • compareTo も「日」ではなく「日時」で比較される

つまり、次のようなケースでは一致しません。

  • 2026-01-09 00:00
  • 2026-01-09 12:00

人間の感覚では「同じ日」ですが、Date では別物です。

4.3 Date をそのまま使い続けない方がいい理由

Date を使い続けると、次のような問題が起きやすくなります。

  • 同日判定が直感通りに書けない
  • タイムゾーンの影響がコードから見えない
  • バグの原因が分かりにくい

そのため、比較や判定を行う前に java.time に変換するのが安全です。

4.4 Date → java.time への安全な変換方法

DateInstant に変換できます。

Date date = new Date();

Instant instant = date.toInstant();

そこから目的に応じて型を選びます。

日付として扱いたい場合

LocalDate localDate =
    instant.atZone(ZoneId.systemDefault()).toLocalDate();

日時として扱いたい場合

LocalDateTime localDateTime =
    instant.atZone(ZoneId.systemDefault()).toLocalDateTime();

ここで重要なのは、

  • どのタイムゾーンで解釈するかを明示している点

これにより、「いつの間にかズレていた」という事故を防げます。

4.5 変換後は java.time の比較メソッドを使う

一度 LocalDateLocalDateTime に変換してしまえば、
比較はこれまで説明してきた方法で安全に行えます。

LocalDate a = localDate;
LocalDate b = LocalDate.now();

if (a.isBefore(b)) {
    System.out.println("aは今日より前");
}

Dateは入口だけ、ロジックはjava.time
これが現代Javaのベストプラクティスです。

4.6 どうしても Date 同士で比較する場合の指針

やむを得ず Date のまま扱う場合は、次を守ると事故が減ります。

  • 「同日判定」はしない(日時として割り切る)
  • 比較は before / after のみに限定する
  • 仕様書やコメントで「日時比較である」ことを明示する

4.7 まとめ:Date を使うなら最短距離で抜ける

  • DateはレガシーAPI
  • 比較はできるが、直感とズレやすい
  • 早めに java.time に変換するのが最善

5. 文字列(String)の日付を比較するなら「必ずパースしてから」

日付比較で非常に多い失敗が、「文字列のまま比較してしまう」ことです。
見た目が日付でも、Stringは日付ではありません。ここを誤ると、静かにバグが混入します。

5.1 文字列のまま比較してはいけない理由

たとえば、次の2つの文字列を比較するとどうなるでしょうか。

String a = "2026-1-9";
String b = "2026-01-10";

System.out.println(a.compareTo(b));

一見すると「aの方が前」になりそうですが、文字列比較は辞書順です。

  • "2026-1-9"
  • "2026-01-10"

この2つは フォーマットが揃っていないため、結果は直感通りになりません。

さらに、

  • "2026-10-01"
  • "2026-2-01"

のようなケースでは、ほぼ確実に破綻します。

結論:日付の比較を、Stringの比較で行ってはいけない。

5.2 正しい手順は「String → 日付型 → 比較」

文字列の日付を比較したい場合、正しい手順は常に同じです。

  1. 文字列を 日付型に変換(パース)
  2. 日付型同士で比較

ISO形式(yyyy-MM-dd)の場合

String s1 = "2026-01-09";
String s2 = "2026-01-10";

LocalDate d1 = LocalDate.parse(s1);
LocalDate d2 = LocalDate.parse(s2);

System.out.println(d1.isBefore(d2)); // true

ISO形式であれば、LocalDate.parse() だけで安全に変換できます。

5.3 フォーマットが異なる場合は DateTimeFormatter を使う

実務では、次のような形式をよく見かけます。

  • 2026/01/09
  • 2026-01-09 18:30
  • 09-01-2026

この場合は、フォーマットを明示してパースします。

DateTimeFormatter formatter =
    DateTimeFormatter.ofPattern("yyyy/MM/dd");

LocalDate date =
    LocalDate.parse("2026/01/09", formatter);

日時を含む場合は LocalDateTime を使います。

DateTimeFormatter formatter =
    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");

LocalDateTime dateTime =
    LocalDateTime.parse("2026-01-09 18:30", formatter);

5.4 フォーマットが複数ある場合の考え方

外部入力やCSV、API連携では、日付フォーマットが揺れることがあります。

この場合の基本方針は次のいずれかです。

  • 事前にフォーマットを正規化する
  • 複数の DateTimeFormatter を用意して順に試す
  • 入力仕様としてフォーマットを固定する(最善)

例(順に試す簡易例):

List<DateTimeFormatter> formatters = List.of(
    DateTimeFormatter.ofPattern("yyyy-MM-dd"),
    DateTimeFormatter.ofPattern("yyyy/MM/dd")
);

LocalDate parse(String text) {
    for (DateTimeFormatter f : formatters) {
        try {
            return LocalDate.parse(text, f);
        } catch (Exception ignored) {}
    }
    throw new IllegalArgumentException("不正な日付形式");
}

5.5 例外処理と入力チェックの重要性

parse() は、形式が合わないと例外を投げます。

LocalDate.parse("2026-99-99"); // 例外

そのため、次の点を意識しましょう。

  • ユーザー入力は必ず例外を想定する
  • 「不正な日付」をどう扱うかを仕様として決める
  • ログに残して原因追跡できるようにする

5.6 「同日判定」「前後判定」はパース後に行う

文字列を日付型に変換した後は、
これまで解説した isBefore / isAfter / isEqual を使います。

if (d1.isEqual(d2)) {
    System.out.println("同じ日付");
}

ここまで来て、ようやく安全な日付比較になります。

5.7 まとめ:String比較は近道に見えて遠回り

  • Stringのまま日付を比較しない
  • 必ず LocalDate / LocalDateTime に変換する
  • フォーマットは明示する
  • 例外処理を前提に設計する

6. 範囲チェック(期間内か?締切を過ぎたか?)の定番実装

実務で非常によく使われるのが、「ある日付(日時)が、指定した期間内に含まれるか」という判定です。
予約、キャンペーン、契約期間、権限の有効期限など、用途は多岐にわたります。

6.1 基本形:「開始 ≤ 対象 ≤ 終了」を明確に書く

まず大切なのは、含める/含めない(inclusive / exclusive)をはっきり決めることです。
ここが曖昧だと、後から仕様バグになります。

日付(LocalDate)の範囲チェック例(両端含む)

LocalDate start = LocalDate.of(2026, 1, 1);
LocalDate end   = LocalDate.of(2026, 1, 31);
LocalDate target = LocalDate.of(2026, 1, 15);

boolean inRange =
    !target.isBefore(start) && !target.isAfter(end);

この書き方の意味は次の通りです。

  • !isBefore(start) → start より前ではない(start 以上)
  • !isAfter(end) → end より後ではない(end 以下)

「開始以上、終了以下」という意図がそのままコードに現れます。

6.2 片側を含めない場合(よくある締切判定)

「締切を過ぎていないか」のような判定では、
終了日は含めないケースもよくあります。

boolean beforeDeadline = target.isBefore(deadline);

この場合、

  • deadline 当日は 締切後
  • deadline より前だけが有効

という仕様になります。

inclusive / exclusive をコメントやメソッド名で明示するのがベストです。

6.3 LocalDateTime の範囲チェック

日時の場合も考え方は同じです。

LocalDateTime start =
    LocalDateTime.of(2026, 1, 9, 18, 0);
LocalDateTime end =
    LocalDateTime.of(2026, 1, 9, 20, 0);
LocalDateTime target =
    LocalDateTime.of(2026, 1, 9, 19, 0);

boolean inRange =
    !target.isBefore(start) && target.isBefore(end);

この例では、

  • 開始時刻は含む
  • 終了時刻は含まない

という、予約・利用時間でよく使われる仕様です。

6.4 null を含む期間の扱い(実務の落とし穴)

現場では、次のようなデータをよく見かけます。

  • 開始日が未設定(いつからでも有効)
  • 終了日が未設定(無期限)

この場合、null をどう解釈するかを先に決める必要があります。

例:null は制限なしとする

boolean inRange(LocalDate target,
                LocalDate start,
                LocalDate end) {

    if (start != null && target.isBefore(start)) {
        return false;
    }
    if (end != null && target.isAfter(end)) {
        return false;
    }
    return true;
}

このように関数化すると、

  • 仕様が一箇所に集約される
  • 判定ロジックの再利用ができる
  • バグが入りにくくなる

というメリットがあります。

6.5 「今日」を含めるかどうかの注意点

「今日が期間内か?」という判定は、意外とバグりやすいです。

LocalDate today = LocalDate.now();

ここで重要なのは、

  • サーバーのタイムゾーンはどこか
  • ユーザー基準の日付か、システム基準か

必要に応じて、ZoneId を明示しましょう。

LocalDate today =
    LocalDate.now(ZoneId.of("Asia/Tokyo"));

6.6 範囲チェックをメソッド化するメリット

範囲チェックは、そのまま書き散らすより、メソッド化がおすすめです。

boolean isWithinPeriod(LocalDate target,
                       LocalDate start,
                       LocalDate end) {
    return (start == null || !target.isBefore(start))
        && (end == null || !target.isAfter(end));
}
  • 名前で仕様が分かる
  • 条件の変更に強い
  • テストしやすい

実務では、この形が最も安定します。

6.7 まとめ:範囲チェックは「仕様をコードに刻む」

  • inclusive / exclusive を明確にする
  • null の意味を決める
  • メソッド化して再利用する
  • タイムゾーンを意識する

7. 差分計算:日数差・経過時間を正しく求める(Period / Duration)

日付比較の次に多い要件が、「どれくらい離れているか」を求める処理です。
Javaでは目的に応じて PeriodDuration を使い分けます。

7.1 日付ベースの差分:Period(年・月・日)

カレンダー上の差を求めたい場合は Period を使います。

LocalDate start = LocalDate.of(2026, 1, 1);
LocalDate end   = LocalDate.of(2026, 1, 31);

Period period = Period.between(start, end);

System.out.println(period.getDays());   // 30
System.out.println(period.getMonths()); // 0
System.out.println(period.getYears());  // 0

Period の特徴は次の通りです。

  • 年・月・日という 人間の感覚に近い単位
  • 月の日数(28〜31日)を考慮する
  • 「何日後か」「何か月後か」を表現しやすい

日数だけが欲しい場合の注意点

long days = ChronoUnit.DAYS.between(start, end);

こちらは 純粋な日数を返します。
「月をまたぐかどうか」を気にしない場合はこちらが便利です。

7.2 時間ベースの差分:Duration(時・分・秒)

時刻を含む差分を求めたい場合は Duration を使います。

LocalDateTime t1 =
    LocalDateTime.of(2026, 1, 9, 18, 0);
LocalDateTime t2 =
    LocalDateTime.of(2026, 1, 9, 20, 30);

Duration duration = Duration.between(t1, t2);

System.out.println(duration.toHours());   // 2
System.out.println(duration.toMinutes()); // 150

特徴は次の通りです。

  • 秒・分・時間といった 時間ベース
  • 1日は常に 24時間として扱われる

7.3 Durationの落とし穴:1日は常に24時間とは限らない?

Duration は「時間の長さ」を表すため、
サマータイム(DST)のある地域では注意が必要です。

  • 1日が23時間になる日
  • 1日が25時間になる日

このような日は、ZonedDateTime + Duration で扱うと結果が直感とズレることがあります。

7.4 差分計算の使い分け指針

目的別に整理すると、次のようになります。

  • 日付の差(年/月/日) → Period
  • 純粋な日数 → ChronoUnit.DAYS.between
  • 時間の差(時/分/秒) → Duration
  • 世界共通の経過時間 → Instant + Duration

7.5 まとめ:差分は「何を知りたいか」で決める

  • 人間向け表示 → Period
  • システム処理 → Duration
  • 日数カウント → ChronoUnit

8. タイムゾーンの落とし穴:ズレる比較・ズレない比較

日付・日時のバグで最も厄介なのが、タイムゾーンによるズレです。
「ローカルでは正しいのに、本番で壊れる」典型例でもあります。

8.1 LocalDateTime は「どこの時間か」を知らない

LocalDateTime now = LocalDateTime.now();

この時刻は、

  • サーバーがどこにあるか
  • 実行環境の設定

に依存します。

LocalDateTime にはタイムゾーンの概念がありません。
そのため、国をまたぐ処理には向きません。

8.2 ZonedDateTime:どこの日時かを明示する

ZonedDateTime tokyo =
    ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
ZonedDateTime newYork =
    ZonedDateTime.now(ZoneId.of("America/New_York"));
  • 同じ瞬間でも、表示される日時は異なる
  • 表示・UI・ユーザー向け処理に最適

8.3 Instant:比較・保存に最強の型

Instant a = Instant.now();
Instant b = Instant.now();

System.out.println(a.isBefore(b));

Instant は、

  • タイムゾーンを持たない
  • UTC基準の「瞬間」

という性質があります。

比較・DB保存・ログ記録は Instant
これは実務で非常に強力な設計指針です。

8.4 安全な設計パターン

よく使われる安全な流れは次の通りです。

  1. 入力 → ZonedDateTime(ユーザーのタイムゾーン)
  2. 内部処理・保存 → Instant
  3. 表示 → ZonedDateTime に変換

これにより、タイムゾーン起因のバグを大幅に減らせます。

9. まとめ:迷ったときの選び方チートシート

最後に、この記事全体の要点を整理します。

  • 日付だけを比較したい
    LocalDate
  • 日時を比較したい
    LocalDateTime
  • 世界共通で比較・保存したい
    Instant
  • ユーザー向け表示
    ZonedDateTime

比較方法の基本

  • 前後判定 → isBefore / isAfter
  • 同一判定 → isEqual
  • 並び替え → compareTo

よくある失敗を避けるために

  • Stringのまま比較しない
  • Dateは早めに java.time に変換
  • タイムゾーンを意識する
  • inclusive / exclusive を明確にする

FAQ

Q1. Javaで日付比較は Date と LocalDate のどちらを使うべき?

基本は LocalDate / LocalDateTime です。Date はレガシーで、比較の意図がコードに表れにくいため、可能な限り java.time を使いましょう。

Q2. 同じ日かどうかを判定したい場合は?

LocalDateTimetoLocalDate() に変換してから isEqual() で比較するのが安全です。

Q3. 文字列の日付を比較したいときの最短ルートは?

必ず パースしてから比較してください。
LocalDate.parse() または DateTimeFormatter を使います。

Q4. タイムゾーンが違う環境で比較するには?

内部処理・保存は Instant、表示は ZonedDateTime が最も安全です。