仕事で書いてるときに思いついたものをメモ。
日付のバリデーション
bashで日付の文字列のバリデーションについて考えます。
日付のフォーマットが yyyy-MM-dd
(4桁西暦-2桁月-2桁日) 形式になっていることを確認したいです。
# OK/NGの例 # OK date1='2020-06-01' # NG date2='2020/06/01' # NG date3='2020-06-01 20:00:00' # NG date4='2020-06-00' # NG (6月は30日まで) date5='2020-06-31'
正規表現
安直ですが、次のような正規表現チェックを実装してみました。
while read dt; do if [[ $dt =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then echo "$dt is ...OK" else echo "$dt is ...NOT OK" fi done <<EOS 2020-06-01 2020/06/01 2020-06-01 20:00:00 2020-06-00 2020-06-31 EOS
これを実行すると次のようになります。
2020-06-01 is ...OK 2020/06/01 is ...NOT OK 2020-06-01 20:00:00 is ...NOT OK 2020-06-00 is ...OK 2020-06-31 is ...OK
3つ目まではうまく判定できてますが、残りの2つはうまくいっていません。 正規表現が、数字の桁数しかチェックしていないからです。
では、次の例ではどうでしょうか。
while read dt; do if [[ $dt =~ ^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$ ]]; then echo "$dt is ...OK" else echo "$dt is ...NOT OK" fi done <<EOS 2020-06-01 2020/06/01 2020-06-01 20:00:00 2020-06-00 2020-06-31 EOS
結果は次のようになりました。
2020-06-01 is ...OK 2020/06/01 is ...NOT OK 2020-06-01 20:00:00 is ...NOT OK 2020-06-00 is ...NOT OK 2020-06-31 is ...OK
この正規表現では、0日や32日、13月などの明らかにあり得ないようなものは除外できるようになりましたが、
2020-06-31
のように、組み合わせ的にあり得ない日付を除外することができません。
うるう年の問題
月の日数は 28, 29, 30, 31 の4通りしかないので、4つの正規表現を並べることで組み合わせ的にあり得ない日付も除外できそうに思えます。 しかし、うるう年のことを考えると、正規表現だけで実現するのは難しいでしょう。
では、より厳密な日付のバリデーションをするにはどうしたらよいでしょうか。
dateコマンド
日付のことは日付の専門家 date コマンドにまかせればいいじゃない!ということで、日付のバリデーションにdateコマンド*1を使ってみます。
while read dt; do if date --date="$dt"; then echo "$dt is ...OK" else echo "$dt is ...NOT OK" fi done <<EOS 2020-06-01 2020/06/01 2020-06-01 20:00:00 2020-06-00 2020-06-31 1900-02-29 EOS
結果は次のようになりました。
2020年 6月 1日 月曜日 00:00:00 2020-06-01 is ...OK 2020年 6月 1日 月曜日 00:00:00 2020/06/01 is ...OK 2020年 6月 1日 月曜日 20:00:00 2020-06-01 20:00:00 is ...OK date: invalid date ‘2020-06-00’ 2020-06-00 is ...NOT OK date: invalid date ‘2020-06-31’ 2020-06-31 is ...NOT OK date: invalid date ‘1900-02-29’ 1900-02-29 is ...NOT OK
※標準エラー出力ものまま表示しています。
2020-06-31
のほかに、一見するとうるう日に見えそうな 1900-02-29
もちゃんと除外できています*2。
一方で、今度は 2020/06/01
のような日付としては問題ないものの、 yyyy-MM-dd
のフォーマットを満たしていないものが除外されなくなりました。
フォーマットのチェックに、上で示したような正規表現のチェックを用いても良いのですが、もうちょっとスマートにやってみましょう。
while read dt; do if [[ $dt = $(date --date="$dt" +%F) ]]; then echo "$dt is ...OK" else echo "$dt is ...NOT OK" fi done <<EOS 2020-06-01 2020/06/01 2020-06-01 20:00:00 2020-06-00 2020-06-31 1900-02-29 EOS
dateコマンドは +%F
オプションで日付を yyyy-MM-dd
形式に変換できるので、これで変換したものが元と一致するかをチェックするという寸法です。
結果は次のようになりました。
2020-06-01 is ...OK 2020/06/01 is ...NOT OK 2020-06-01 20:00:00 is ...NOT OK date: invalid date ‘2020-06-00’ 2020-06-00 is ...NOT OK date: invalid date ‘2020-06-31’ 2020-06-31 is ...NOT OK date: invalid date ‘1900-02-29’ 1900-02-29 is ...NOT OK
2020/06/01
もちゃんと除外できてますね。
もっと良いやり方があればコメントください。