日付形式チェックは「“存在しない日付”を早めにはじく」ための技
業務システムでは、日付入力は定番中の定番です。
生年月日、締め日、請求日、支払日、予約日、納期…。
ここで何もチェックしないと、2024-13-01(13月)、2024-02-30(2月30日)、abcd-ef-gh みたいな値が平気でDBに入ります。
後で集計や帳票出力で落ちたり、「なんでこんな日付が入ってるの?」と未来の自分が苦しみます。
日付形式チェックの役割は、まさにここです。
「この文字列は“日付としてパースしてよさそうか”を事前に判定し、
おかしなものは早めにはじく」ためのフィルタだと思ってください。
まずは「どんな書式の日付を許すか」を決める
“フォーマット”を決めないと、チェックは始まらない
数値と同じで、日付も「どんな形で入力してもらうか」を先に決める必要があります。
2024-02-03 のような yyyy-MM-dd 形式にするのか。2024/02/03 のようなスラッシュ区切りにするのか。20240203 のような8桁数字にするのか。
フォーマットが決まらないと、チェックのしようがありません。
ここでは、実務でよく使われるこの3つを例にします。
yyyy-MM-dd(例:2024-02-03)yyyy/MM/dd(例:2024/02/03)yyyyMMdd(例:20240203)
そして、Java 8 以降で使える java.time パッケージを前提に話を進めます。
基本:LocalDate と DateTimeFormatter でチェックする
「パースできるかどうか」で形式と存在チェックを同時にやる
日付形式チェックで一番大事なポイントは、
「文字列の形だけでなく、“実在する日付かどうか”まで見たい」 ということです。
2024-02-30 は、\\d{4}-\\d{2}-\\d{2} という形には合っていますが、
実在しない日付です。
これを通してしまうと、後で必ず困ります。
そこで使うのが LocalDate.parse と DateTimeFormatter です。
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
public final class DateValidator {
private DateValidator() {}
private static final DateTimeFormatter YMD_DASH =
DateTimeFormatter.ofPattern("uuuu-MM-dd")
.withResolverStyle(ResolverStyle.STRICT);
public static boolean isValidYmdDash(String text) {
if (text == null || text.isBlank()) {
return false;
}
try {
LocalDate.parse(text, YMD_DASH);
return true;
} catch (DateTimeParseException e) {
return false;
}
}
}
Java使い方はこうなります。
System.out.println(DateValidator.isValidYmdDash("2024-02-03")); // true
System.out.println(DateValidator.isValidYmdDash("2024-02-29")); // true(うるう年)
System.out.println(DateValidator.isValidYmdDash("2023-02-29")); // false(存在しない日付)
System.out.println(DateValidator.isValidYmdDash("2024-13-01")); // false(13月)
System.out.println(DateValidator.isValidYmdDash("2024-2-3")); // false(0埋め必須)
System.out.println(DateValidator.isValidYmdDash("abcd-ef-gh")); // false
Javaここで深掘りしたい重要ポイントは三つです。
一つ目は、「ResolverStyle.STRICT を使って“厳密な日付チェック”をしている」ことです。STRICT にすることで、2024-02-30 のような存在しない日付や、2024-2-3 のようなフォーマット違いをきちんと弾いてくれます。
二つ目は、「uuuu を使っている」ことです。yyyy でも動きますが、uuuu は java.time で推奨される“年”の表現です。
(細かい歴史的理由はありますが、「java.time では uuuu を使う」と覚えておけばOKです。)
三つ目は、「例外を使って“パースできるかどうか”で判定している」ことです。LocalDate.parse が成功すれば「形式もOK・日付としてもOK」、DateTimeParseException が出れば「どこかがおかしい」と判断できます。
スラッシュ区切り yyyy/MM/dd に対応する
フォーマット違いはフォーマッタを変えるだけ
yyyy/MM/dd 形式も、ほぼ同じ書き方でチェックできます。
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
public final class DateValidator {
private DateValidator() {}
private static final DateTimeFormatter YMD_SLASH =
DateTimeFormatter.ofPattern("uuuu/MM/dd")
.withResolverStyle(ResolverStyle.STRICT);
public static boolean isValidYmdSlash(String text) {
if (text == null || text.isBlank()) {
return false;
}
try {
LocalDate.parse(text, YMD_SLASH);
return true;
} catch (DateTimeParseException e) {
return false;
}
}
}
Java使い方はこうです。
System.out.println(DateValidator.isValidYmdSlash("2024/02/03")); // true
System.out.println(DateValidator.isValidYmdSlash("2024/2/3")); // false(0埋め必須)
System.out.println(DateValidator.isValidYmdSlash("2024/13/01")); // false
Javaここでのポイントは、「フォーマットごとにフォーマッタを用意しておくと、コードが読みやすい」ことです。YMD_DASH と YMD_SLASH のように名前を付けておくと、
「このメソッドはどんな形式を期待しているのか」が一目で分かります。
8桁数字 yyyyMMdd に対応する
システム間連携やCSVでよくある形式
システム間連携やCSVでは、20240203 のような8桁数字で日付を扱うことも多いです。
これも同じようにチェックできます。
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
public final class DateValidator {
private DateValidator() {}
private static final DateTimeFormatter YMD_COMPACT =
DateTimeFormatter.ofPattern("uuuuMMdd")
.withResolverStyle(ResolverStyle.STRICT);
public static boolean isValidYmdCompact(String text) {
if (text == null || text.isBlank()) {
return false;
}
try {
LocalDate.parse(text, YMD_COMPACT);
return true;
} catch (DateTimeParseException e) {
return false;
}
}
}
Java使い方はこうです。
System.out.println(DateValidator.isValidYmdCompact("20240203")); // true
System.out.println(DateValidator.isValidYmdCompact("20240230")); // false
System.out.println(DateValidator.isValidYmdCompact("2024-02-03"));// false
Javaここでのポイントは、「“見た目は単なる8桁数字”でも、LocalDate にパースすることで“存在チェック”までできる」ことです。
単に \\d{8} だけ見ていると、20240230 も通ってしまいますが、LocalDate.parse を通すことで、そこまで含めてチェックできます。
「形式チェック」と「範囲チェック」は分けて考える
未来日・過去日を制限したい場合
日付形式チェックは、「その文字列が日付として成立しているか」を見るだけです。
「未来日はNG」「過去10年以内だけOK」といったビジネスルールは、
別のレイヤーで見るべき話 です。
例えば、「生年月日は今日より未来はNG」というチェックは、こう書けます。
import java.time.LocalDate;
public final class BirthDateValidator {
private BirthDateValidator() {}
public static boolean isValidBirthDate(String text) {
if (!DateValidator.isValidYmdDash(text)) {
return false;
}
LocalDate date = LocalDate.parse(text, DateValidator.YMD_DASH);
return !date.isAfter(LocalDate.now());
}
}
Java(YMD_DASH を外から使えるように public にするか、parseYmdDash のようなメソッドを用意してもよいです。)
ここでの重要ポイントは、「形式チェックに通ったあとで、安心して LocalDate として扱っている」ことです。isValidYmdDash が true なら、LocalDate.parse で例外は出ない前提で書けます。
正規表現だけで日付をチェックしようとしない
「形だけ合っていても、存在しない日付」は必ず出てくる
初心者がやりがちな失敗が、
「正規表現だけで日付をチェックしようとする」ことです。
例えば、こんなパターンを書きたくなります。
// yyyy-MM-dd っぽいもの
"^\\d{4}-\\d{2}-\\d{2}$"
Javaこれは「形」としては合っていますが、2024-13-01 や 2024-02-30 も通ってしまいます。
これを正規表現だけで完全に防ごうとすると、
「月ごとに日数が違う」「うるう年」などを全部パターンに埋め込むことになり、
ほぼ読めない怪物になります。
日付は、正規表現ではなく“日付ライブラリに任せる”のが正解です。LocalDate.parse と ResolverStyle.STRICT を使えば、
「形」と「存在」の両方を、シンプルなコードでチェックできます。
例題:入力フォームでの日付形式チェック
例えば、「締め日」を yyyy-MM-dd 形式で入力してもらうフォームを考えます。
サーバ側では、こんな感じで使えます。
String closingDateText = request.getParameter("closingDate");
if (closingDateText == null || closingDateText.isBlank()) {
errors.add("締め日を入力してください。");
} else if (!DateValidator.isValidYmdDash(closingDateText.trim())) {
errors.add("締め日は yyyy-MM-dd 形式で正しい日付を入力してください。(例:2024-02-03)");
} else {
LocalDate closingDate = LocalDate.parse(closingDateText.trim(), DateValidator.YMD_DASH);
// ここから先は LocalDate として安全に扱える
}
Javaここでのポイントは三つです。
一つ目は、「未入力チェック」と「形式チェック」を分けている」ことです。
「入力してください」と「形式が正しくありません」は、ユーザーに伝えたい内容が違うので、
条件もメッセージも分けて書いたほうが親切です。
二つ目は、「形式チェックに通ったあとで、安心して LocalDate にパースしている」ことです。
これにより、後続の処理は「日付として正しいものだけ」を前提に書けます。
三つ目は、「trim() をどこでかけるかを決めている」ことです。
入力値の前後の空白を許すかどうかはポリシー次第ですが、
許すなら、チェック前に trim() しておくのが定番です。
まとめ:日付形式チェックユーティリティで身につけたい感覚
日付形式チェックは、「形だけ見る」のではなく、
「その文字列が“実在する日付としてパースできるか”を見るフィルタ」です。
押さえておきたいのは、
どのフォーマットを許すかを先に決めるLocalDate.parse + DateTimeFormatter + ResolverStyle.STRICT で
形式と存在を同時にチェックする
ビジネスルール(未来日NGなど)は、形式チェックとは別に書く
という3つの感覚です。
もしあなたのコードのどこかに、
LocalDate date = LocalDate.parse(request.getParameter("date"));
Javaのような行がそのまま書かれていたら、
そこを題材にして、ここで作った DateValidator.isValidYmdDash やisValidYmdSlash を一度挟んでみてください。
それだけで、「存在しない日付がシステムに入り込む」「パース時に例外で落ちる」といった事故を、かなり減らすことができます。
