java-recipes

ホーム 日付・時刻 › D-10: 複数フォーマットの日付解析

D-10: 複数フォーマットの日付解析

外部システムや手入力で受け取る日付文字列は、フォーマットが統一されていないことがよくあります。 このページでは ISO 形式・スラッシュ区切り・8桁数字・英語表記・和暦など、複数のフォーマットをまとめて解析するパターンを解説します。

対応する日付フォーマット

入力例フォーマット用途
2024-04-01ISO_LOCAL_DATE(yyyy-MM-dd)標準的なAPI・DBのデータ
2024/04/01yyyy/MM/dd日本のシステムや手入力
20240401yyyyMMddバッチ・ファイル名によく使われる
Apr 1, 2024MMM d, yyyy(英語)英語圏のドキュメント・API
令和6年4月1日GGGGy年M月d日(和暦)官公庁書類・レガシーシステム

すべて同じ日付(2024年4月1日)を表しています。どのフォーマットが来ても LocalDate に変換できるメソッドを実装します。

サンプルコード

Sample.java
import java.time.LocalDate;
import java.time.chrono.JapaneseChronology;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

public class DateParserSample {

    // 対応する日付フォーマットの一覧(試行順に定義)
    private static final List<DateTimeFormatter> FORMATTERS = Arrays.asList(
        DateTimeFormatter.ISO_LOCAL_DATE,                                          // 2024-04-01
        DateTimeFormatter.ofPattern("yyyy/MM/dd"),                                 // 2024/04/01
        DateTimeFormatter.ofPattern("yyyyMMdd"),                                   // 20240401
        DateTimeFormatter.ofPattern("MMM d, yyyy", Locale.ENGLISH),               // Apr 1, 2024
        DateTimeFormatter.ofPattern("GGGGy年M月d日")                               // 令和6年4月1日
            .withChronology(JapaneseChronology.INSTANCE)
            .withLocale(Locale.JAPANESE)
    );

    /**
     * 複数フォーマットに対応した日付解析メソッド。
     * フォーマットを順番に試し、成功したものを返す(フォールバックチェーン方式)。
     */
    public static LocalDate parse(String input) {
        for (DateTimeFormatter formatter : FORMATTERS) {
            try {
                return LocalDate.from(formatter.parse(input));
            } catch (Exception e) {
                // このフォーマットでは解析できなかった → 次を試す
            }
        }
        throw new IllegalArgumentException("解析できない日付フォーマット: " + input);
    }

    public static void main(String[] args) {
        // サポートするすべてのフォーマットをテスト
        String[] inputs = {
            "2024-04-01",     // ISO 形式
            "2024/04/01",     // スラッシュ区切り
            "20240401",       // 数字8桁
            "Apr 1, 2024",    // 英語表記
            "令和6年4月1日",   // 和暦
        };

        for (String input : inputs) {
            LocalDate date = parse(input);
            System.out.printf("%-20s → %s%n", "\"" + input + "\"", date);
        }
    }
}

よくあるミス・注意点

⚠️ 和暦のパースには Chronology と Locale の両方が必要

「令和6年4月1日」を解析するには withChronology(JapaneseChronology.INSTANCE) withLocale(Locale.JAPANESE)両方を設定する必要があります。 片方だけだと「令」が認識されずに解析が失敗します。

// NG: Locale だけでは「令和」が認識されない
DateTimeFormatter.ofPattern("GGGGy年M月d日")
    .withLocale(Locale.JAPANESE);  // Chronology が抜けている

// OK: withChronology と withLocale の両方を設定する
DateTimeFormatter.ofPattern("GGGGy年M月d日")
    .withChronology(JapaneseChronology.INSTANCE)
    .withLocale(Locale.JAPANESE);

📌 フォーマットの試行順序を意識する

例えば yyyyMMdd yyyy/MM/dd より前に試すと、 数字8桁は両方にマッチする可能性があります。 より具体的(一致しにくい)なフォーマットを先に試し、曖昧なものは後回しにしましょう。

📌 DateTimeFormatter は再利用可能・スレッドセーフ

DateTimeFormatter SimpleDateFormat と異なりスレッドセーフです。 static final で定数として定義して使い回せます。 フォーマットの一覧リストも static final にするのがベストプラクティスです。

テストする観点

  • 5種類のフォーマットがそれぞれ同じ日付(例: 2024-04-01)に解析されるか
  • 和暦の元号をまたぐ日付(2019-04-30 = 平成31年4月30日、2019-05-01 = 令和元年5月1日)が正しく解析されるか
  • うるう年(2024-02-29)が正しく解析されるか
  • サポート外のフォーマット(例: "01/04/2024")で IllegalArgumentException がスローされるか
  • 空文字列・null が渡されたときにどのような例外が発生するか

GitHub でソースコードを見る →