概要

CSV 取り込みや外部システムとの連携では、日付文字列の形式が統一されていないことが珍しくありません。「2025-03-27」「2025/03/27」「20250327」「令和7年3月27日」のように混在するデータを、ひとつのメソッドで受け付けられるようにしておくと、取り込みロジックが格段にシンプルになります。この記事では、複数の DateTimeFormatter をフォールバックチェーンとして順番に試す方式で、多形式対応のパーサーを実装します。和暦のパースに必要な JapaneseChronology の設定、パース失敗時のエラーハンドリングも含めて整理します。

使いどころ

CSV ファイルの日付カラムが「yyyy-MM-dd」と「yyyy/MM/dd」で混在している場合に統一的にパースする

外部システムから受け取った和暦表記の日付を LocalDate に変換して DB に保存する

ユーザー入力の日付欄で、8桁数字やスラッシュ区切りなど複数の入力形式を許容する

コード例

複数フォーマット対応の日付パーサー
import java.time.LocalDate;

public class MultiFormatDateParser {

    private static final DateTimeFormatter ISO =
        DateTimeFormatter.ISO_LOCAL_DATE;
    private static final DateTimeFormatter SLASH =
        DateTimeFormatter.ofPattern("yyyy/MM/dd");
    private static final DateTimeFormatter COMPACT =
        DateTimeFormatter.ofPattern("yyyyMMdd");
    private static final DateTimeFormatter JAPANESE =
        DateTimeFormatter.ofPattern("GGGGy年M月d日")
            .withChronology(JapaneseChronology.INSTANCE)
            .withLocale(Locale.JAPANESE);

    public static LocalDate parse(String input) {
        return Stream.of(ISO, SLASH, COMPACT, JAPANESE)
            .map(fmt -> tryParse(input, fmt))
            .filter(Optional::isPresent)
            .findFirst()
            .flatMap(opt -> opt)
            .orElseThrow(() -> new IllegalArgumentException(
                "解析できない日付: " + input));
    }

    private static Optional<LocalDate> tryParse(
            String input, DateTimeFormatter fmt) {
        try {
            return Optional.of(
                LocalDate.from(fmt.parse(input)));
        } catch (Exception e) {
            return Optional.empty();
        }
    }

    public static void main(String[] args) {
        var inputs = new String[]{
            "2025-03-27", "2025/03/27",
            "20250327", "令和7年3月27日"
        };
        for (var input : inputs) {
            System.out.printf("%-20s -> %s%n",
                input, parse(input));
        }
    }
}

Java 8 / 17 / 21 の完全なサンプルコードは GitHub リポジトリ で確認できます。

Version Coverage

Stream + Optional でフォールバックを関数的に記述できる。var と組み合わせて簡潔に書ける。

Java 17
// Java 17: Stream + Optional でフォールバック
return Stream.of(ISO, SLASH, COMPACT, ENGLISH, JAPANESE)
    .map(fmt -> tryParse(input, fmt))
    .filter(Optional::isPresent)
    .findFirst()
    .flatMap(opt -> opt)
    .orElseThrow(() -> new IllegalArgumentException(
        "解析不可: " + input));

Library Comparison

Pure Java (DateTimeFormatter)対応形式が数パターン程度なら標準 API で十分。フォールバックチェーンで柔軟に対応できる。対応形式が増えるとフォーマッタのリスト管理が必要になる。
Apache Commons Lang (DateUtils.parseDate)SimpleDateFormat ベースの既存コードとの互換性が必要な場合。スレッドセーフではないため、DateTimeFormatter への移行が望ましい。

注意点

フォールバックチェーンの順序が重要。「yyyyMMdd」は「yyyy-MM-dd」のハイフンなし版と誤解されないよう、より厳密な形式を先に試す

和暦パースには JapaneseChronology.INSTANCE と Locale.JAPANESE の両方が必要。片方が欠けると例外になる

DateTimeFormatter.parse() の結果は TemporalAccessor なので、LocalDate.from() で変換する。直接キャストはできない

月や日が1桁の場合(4月1日 vs 04月01日)はパターンの M と MM、d と dd の違いに注意する

FAQ

パース失敗時に例外を投げるかデフォルト値を返すか、どちらが良いですか?

業務データは不正値を黙って通すとバグの温床になるため、例外で明示的に失敗させるのが安全です。

和暦パースで「元年」をどう扱えばよいですか?

JapaneseChronology を使えば「令和元年」のパースは自動的に対応されます。フォーマットパターンに GGGG を使います。

フォールバックチェーンの性能は問題になりませんか?

例外の生成にコストはありますが、バッチで数万件程度なら実用上の問題にはなりません。大量処理では事前に形式を判定する方法もあります。

関連書籍

この記事のテーマをさらに深く学びたい方へ。

※ Amazon アソシエイトリンクを含みます