読者です 読者をやめる 読者になる 読者になる

しおしお

IntelliJのあれやこれや

SimpleDateFormatを使って日付精査してやらかした

SimpleDateFormatで日付精査をしていて見事にバグを埋め込んでいた。。。

精査のロジックはSimpleDateFormatに指定したformatで文字列がDateにパースできるかで精査していました。
実際にバグのあったコードと、バグの内容です。(Java書くのが面倒だったのでGroovyで書いています。)

精査のコード
def isDate(def string) {
  def format = new SimpleDateFormat('yyyy/MM/dd')
  try {
    format.parse(string)
    true
  } catch (ParseException e) {
    false
  }
}
テスト結果
assert isDate('2012/07/08')
assert !isDate('2012')
assert !isDate('20120708')
assert !isDate('2012/0708')
assert !isDate('2012/07/081')

上のテストを実行すると、本来日付形式でエラーとなることを想定していた「2012/07/081」は正常な値と判断されてしまいます。実際に実行してみると、期待値と異なることがわかります。

assert !isDate('2012/07/081')
       ||
       |true
       false

実はこれ、JavaDocにもちゃんと明記されているんですよね。以下は、parseメソッドのJavaDocを抜粋した内容です。ちゃんとJavaDocは読まないとダメですね。

指定された文字列の先頭からテキストを解析して日付を生成します。メソッドは指定された文字列のテキスト全体に使用されない場合もあります。

このバグを解決するために対応したコードはこんな感じになっています。一度Dateに変換した値を同じフォーマットで文字列に変換して、精査対象の日付と同じであるかをチェックしています。なんで、こんなコードにしたかというと、桁数で精査(このフォーマットの場合10桁)しようとしたけど、「2012/7/081」が10桁でかつparseできてしまうという問題にぶつかってしまったからです。

def isDate(def string) {
  def format = new SimpleDateFormat('yyyy/MM/dd')
  try {
    def date = format.parse(string)
    format.format(date) == string
  } catch (ParseException e) {
    false
  }
}

今回まなんだことは、ちゃんとドキュメント読んでテストをする。やっぱりこれが一番かなと。

おわり。