きり丸の技術日記

技術検証したり、資格等をここに残していきます。

Java 8以降、Javaで文字列の日付存在チェックする(閏年でないときの閏日チェック)(Check leap day)

JSON等でデータのやりとりをする時には、直接日付型を渡すことができません。文字列で渡す必要があります。

ただし、日付に関しては閏年が存在するため、単純な正規表現では文字列の異常値を検出できません。

今回の記事では、Javaで文字列の日付が存在する日付であることをチェックします。「20220229」は閏年ではないので例外発生することが期待値です。

環境

  • Java
    • 17

前提

  • 日付項目の文字列のフォーマット自体はAPI仕様書等で定義されていること
    • yyyyMMdd
    • yyyy/MM/dd
    • yyyy-MM-dd
      • 等々

ユースケース

  • JSONやCSVに記載されている日付が存在することをチェックする

対応

日付型のLocalDateに変換できれば、存在する日付です。

文字列を日付型に変換するため、まずはフォーマットを用意します。DateTimeFormatterofPatternに日付フォーマットを設定します。次にwithResolverStyle(ResolverStyle.STRICT)を設定します。これにより厳密な変換となり、存在しない日付はLocalDateに変換できなくなります。

また、withResolverStyle(ResolverStyle.STRICT)を設定することで、日付フォーマットも厳密なものを要求されます。詳しいことは参考情報を見ていただきたいのですが、Java 8からはyyyyuuuuで設定する必要がありますので、注意してください。

// yyyyMMdd(uuuuMMdd)を厳密に変換する
var DATE_FORMATTER = DateTimeFormatter.ofPattern("uuuuMMdd").withResolverStyle(ResolverStyle.STRICT);

用意したフォーマットを使用して、LocalDateに変換します。ここでLocalDateに変換できれば日付が存在します。

LocalDate.parse(value, DATE_FORMATTER);

備考

発生するメッセージについて

エラーメッセージを見て気付いたのですが、かなり丁寧なメッセージとなっています。特に「20220229」を渡したときのメッセージが「2022年は閏年ではありません」といったメッセージまで出してくれるのは、非常にわかりやすくてよいですね。

Text '20200230' could not be parsed: Invalid date 'FEBRUARY 30'
Text '20220229' could not be parsed: Invalid date 'February 29' as '2022' is not a leap year
Text '20220230' could not be parsed: Invalid date 'FEBRUARY 30'

ResolverStyleによる挙動の違いについて

「20220229」を変換したときの挙動を見ていただくと分かりやすいです。

  // 動作確認コード
  @EnumSource(ResolverStyle.class)
  @ParameterizedTest
  void test_01(ResolverStyle value) {
    try {
      LocalDate uuuuMMdd = LocalDate.parse("20220229",
          DateTimeFormatter.ofPattern("uuuuMMdd").withResolverStyle(value));
      System.out.println(uuuuMMdd);
    } catch( Exception e){
      System.out.println(e);
    }
  }

処理結果

ResolverStyle.STRICTの場合
# 定義:Style to resolve dates and times strictly.
# 処理結果:
java.time.format.DateTimeParseException: Text '20220229' could not be parsed: Invalid date 'February 29' as '2022' is not a leap year
# 変換が厳密になる
ResolverStyle.SMARTの場合
# 定義:Style to resolve dates and times in a smart, or intelligent, manner.
# 処理結果:
2022-02-28
# いい感じに処理してくれる(期待するのは2月の月末だろうという変換)
ResolverStyle.LENIENTの場合
# 定義:Style to resolve dates and times leniently.
# 処理結果
2022-03-01
# 存在する日付(2022-02-28)に対して+-の日付を調整する

ソースコード

終わりに

昔のJavaでもDate型に変換して存在する日付チェックするのは普通でしたが、DateはもうDateTimeFormatterのデフォルトがResolverStyle.SMARTだったせいで、少々調査に時間がかかりました。

DateTimeFormatter#appendStrictは文字列を厳密にするものでしたし、DateTimeFormatter#toFormatterはpublicメソッドではないので呼べなかったですし…。

地味に調査がたいへんな割には情報が出てこなかったので、今回ブログとしてまとめられてよかったです。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

参考情報