日付比較ユーティリティは何のために必要か
業務システムでは、
「締め日を過ぎているか?」「有効期限内か?」「開始日 ≤ 対象日 ≤ 終了日か?」
といった“日付の比較”が、あらゆるところに出てきます。
このとき、毎回 if 文の中でゴチャゴチャ書いていると、
条件が読みにくくなり、バグも入りやすくなります。
そこで役に立つのが「日付比較ユーティリティ」です。
よく使う比較パターンを小さなメソッドにまとめておくことで、
「何を判定しているのか」が一目で分かるコードにできます。
Java で日付を比較する基本(LocalDate 編)
before / after / equals の感覚をつかむ
LocalDate には、日付を比較するためのメソッドが用意されています。
import java.time.LocalDate;
public class LocalDateCompareBasic {
public static void main(String[] args) {
LocalDate d1 = LocalDate.of(2025, 3, 10);
LocalDate d2 = LocalDate.of(2025, 3, 15);
System.out.println(d1.isBefore(d2)); // true
System.out.println(d1.isAfter(d2)); // false
System.out.println(d1.isEqual(d2)); // false
}
}
Javaここで大事なのは、isBefore は「より前の日付か?」isAfter は「より後の日付か?」isEqual は「同じ日付か?」
という、直感的なメソッドが用意されていることです。
compareTo で数値比較のように書くこともできますが、
初心者のうちは isBefore / isAfter / isEqual を素直に使った方が読みやすくなります。
「今日より前か?」「今日以降か?」を判定するユーティリティ
締め切りチェックの典型パターン
よくあるのが、「今日が締め切りを過ぎているかどうか」の判定です。
import java.time.LocalDate;
public class DateCompareUtils {
public static boolean isExpired(LocalDate today, LocalDate deadline) {
return today.isAfter(deadline);
}
public static boolean isOnOrBefore(LocalDate target, LocalDate base) {
return !target.isAfter(base);
}
public static boolean isOnOrAfter(LocalDate target, LocalDate base) {
return !target.isBefore(base);
}
}
Java使い方の例です。
LocalDate today = LocalDate.of(2025, 3, 20);
LocalDate deadline = LocalDate.of(2025, 3, 18);
System.out.println(DateCompareUtils.isExpired(today, deadline)); // true
System.out.println(DateCompareUtils.isOnOrBefore(today, deadline)); // false
System.out.println(DateCompareUtils.isOnOrAfter(today, deadline)); // true
Javaここで深掘りしたいのは、「否定をうまく使って“以上”“以下”を表現している」点です。
target <= base を表現したいときは「target が base より後ではない」
つまり !target.isAfter(base) と書けます。
target >= base は「target が base より前ではない」
つまり !target.isBefore(base) です。
このパターンを覚えておくと、「以上」「以下」の条件をきれいに書けます。
「範囲内かどうか」を判定するユーティリティ
開始日 ≤ 対象日 ≤ 終了日 のチェック
業務で本当によく出るのが、「この日付が期間内かどうか」の判定です。
import java.time.LocalDate;
public class DateRangeUtils {
public static boolean isBetweenInclusive(LocalDate target,
LocalDate startInclusive,
LocalDate endInclusive) {
if (endInclusive.isBefore(startInclusive)) {
throw new IllegalArgumentException("終了日は開始日以降である必要があります");
}
boolean notBeforeStart = !target.isBefore(startInclusive);
boolean notAfterEnd = !target.isAfter(endInclusive);
return notBeforeStart && notAfterEnd;
}
public static boolean isBetweenHalfOpen(LocalDate target,
LocalDate startInclusive,
LocalDate endExclusive) {
if (!endExclusive.isAfter(startInclusive)) {
throw new IllegalArgumentException("終了日は開始日より後である必要があります");
}
boolean notBeforeStart = !target.isBefore(startInclusive);
boolean beforeEnd = target.isBefore(endExclusive);
return notBeforeStart && beforeEnd;
}
}
Java使い方の例です。
LocalDate target = LocalDate.of(2025, 3, 15);
LocalDate start = LocalDate.of(2025, 3, 10);
LocalDate end = LocalDate.of(2025, 3, 20);
System.out.println(DateRangeUtils.isBetweenInclusive(target, start, end)); // true
Javaここで特に重要なのは、「範囲の定義をユーティリティで固定する」ことです。
開始・終了を「両方含む(閉区間)」で扱うのか、
「開始を含み、終了は含まない(半開区間)」で扱うのか。
これがあちこちでバラバラだと、バグの温床になります。
プロジェクトとして「日付範囲は基本的に半開区間で扱う」などのルールを決め、
それをユーティリティに閉じ込めておくと安全です。
LocalDateTime / Instant の比較と「タイムゾーン」の罠
LocalDateTime の比較は「同じタイムゾーン前提」
LocalDateTime も isBefore / isAfter / isEqual を持っています。
import java.time.LocalDateTime;
public class LocalDateTimeCompareBasic {
public static void main(String[] args) {
LocalDateTime t1 = LocalDateTime.of(2025, 3, 10, 10, 0);
LocalDateTime t2 = LocalDateTime.of(2025, 3, 10, 15, 0);
System.out.println(t1.isBefore(t2)); // true
}
}
Javaただし、ここで深掘りしておきたい大事なポイントがあります。
LocalDateTime は「タイムゾーンを持たない日時」です。
つまり、「同じタイムゾーンで解釈される前提」で比較する必要があります。
もし「JST の 10:00」と「UTC の 10:00」を LocalDateTime としてそのまま比較すると、
本当は 9時間の差があるのに「同じ時刻」とみなされてしまいます。
タイムゾーンをまたいで“絶対的な瞬間”を比較したいときは、Instant や ZonedDateTime を使うのが安全です。
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class InstantCompareBasic {
public static void main(String[] args) {
ZonedDateTime jst = ZonedDateTime.of(2025, 3, 10, 10, 0, 0, 0, ZoneId.of("Asia/Tokyo"));
ZonedDateTime utc = ZonedDateTime.of(2025, 3, 10, 1, 0, 0, 0, ZoneId.of("UTC"));
Instant i1 = jst.toInstant();
Instant i2 = utc.toInstant();
System.out.println(i1.equals(i2)); // true(同じ瞬間)
}
}
Java業務システムでは、「日付だけ比較したいのか」「瞬間として比較したいのか」を意識して、LocalDate / LocalDateTime / Instant を使い分けることがとても重要です。
「同じ日か?」を判定するユーティリティ
LocalDateTime から日付だけを取り出して比較する
「同じ日付のデータかどうか」を判定したい場面もよくあります。
このとき、LocalDateTime 同士をそのまま比較すると、時間まで一致しないと true になりません。
日付だけで比較したいなら、toLocalDate() で日付部分を取り出してから比較します。
import java.time.LocalDate;
import java.time.LocalDateTime;
public class SameDayUtils {
public static boolean isSameDay(LocalDateTime t1, LocalDateTime t2) {
LocalDate d1 = t1.toLocalDate();
LocalDate d2 = t2.toLocalDate();
return d1.isEqual(d2);
}
}
Java使い方の例です。
LocalDateTime t1 = LocalDateTime.of(2025, 3, 10, 9, 0);
LocalDateTime t2 = LocalDateTime.of(2025, 3, 10, 18, 30);
System.out.println(SameDayUtils.isSameDay(t1, t2)); // true
Javaここでのポイントは、「何をもって“同じ”とみなすか」をユーティリティで明示していることです。
「日付が同じなら同じ日」「タイムゾーン込みで同じ瞬間なら同じ」など、
意味をはっきりさせておくと、後から読んだときに迷いません。
セキュリティ・運用の観点から見た日付比較
「境界条件」のバグはそのまま障害になる
日付比較のバグで一番多いのが、「境界条件」の間違いです。
締め切り日を「<」で判定すべきところを「≤」にしてしまう。
有効期限の終了日を含めるべきかどうかを勘違いする。
開始日と終了日の大小チェックをしておらず、逆転した期間を受け入れてしまう。
これらは、請求・支払・契約・SLA などに直結するため、
そのまま「お金のトラブル」「契約違反」「インシデント」につながります。
だからこそ、
「日付比較のパターンをユーティリティにまとめる」
「開始・終了の関係を必ずチェックする」
「閉区間/半開区間のルールをチームで統一する」
といった設計が、とても重要になります。
まとめ:日付比較ユーティリティで身につけてほしい感覚
日付比較ユーティリティは、
「前か後か」「範囲内か」「同じ日か」といった判定を、
読みやすく・間違えにくく書くための小さな道具です。
isBefore / isAfter / isEqual を素直に使う。
「以上」「以下」は !isAfter / !isBefore で表現する。
開始日・終了日と対象日の関係をユーティリティで統一する(閉区間か半開区間かを決める)。LocalDate / LocalDateTime / Instant を、「何を比較したいか」に応じて使い分ける。
もしあなたのコードの中に、
「if (a.compareTo(b) <= 0 && a.compareTo(c) >= 0)」のような、
パッと見て意味が分かりにくい条件があれば、
それを一度「日付比較ユーティリティ」に置き換えられないか眺めてみてください。
それだけで、コードの意図がはっきりし、
境界条件のバグも減り、“業務で戦える”日付・時間まわりの設計に近づきます。
