dateコマンドで前月を計算する時の注意(前月に基準日の日付がない場合)
dateコマンドで日付計算するときに、「1ヶ月前」「1日後」といった構文は便利なのでよく使うのだが、今回この「1ヶ月前」指定をなんとなく使っていて、まずい動きをすることがあったのでメモ。
どういうときにまずいかというと、「日付を比較する基準日が前月のカレンダー上にない場合」の挙動である。例えば、3月31日の前月は、2月31日ということになるが、この日付はカレンダー上存在しないため、結果はなんと3月3日になってしまう。(2月28日 + 3日という計算ロジックなのだろう。。)
具体的に実行結果を見てみる。
まず、普通に実行すると、以下のように正しく1ヶ月前を取得することができる。
$ date +'%Y-%m-%d' 2015-07-30 $ date -d '1 month ago' +'%Y-%m-%d' 2015-06-30
次に、3月31日の1ヶ月前を取得してみる。
$ date -d '2015/03/31' +'%Y-%m-%d' 2015-03-31 $ date -d '2015/03/31 1 month ago' +'%Y-%m-%d' 2015-03-03
このような結果となった。これはちょっと意識していないとまずいことが起こる。
例えば前月計算バッチなんかで、よく「前月」を算出したくて、以下のようなシェルスクリプトを何気なく書いてしまうと、特定の日付で処理がおかしいことになる。
7月30日に以下のシェルスクリプトを実行すると、last_monthの”2015-06”と想定どおりの結果になる。
$ last_month=`date -d '1 month ago' +'%Y-%m'` $ echo $last_month 2015-06
これが翌日7月31日に実行された場合は、last_monthの値は”2015-07"となってしまうが、「前月」を求めたい場合はこれは想定外の結果である。
$ last_month=`date -d '1 month ago' +'%Y-%m'` $ echo $last_month 2015-07
これは7月31日の1ヶ月前はdateコマンド的には7月1日となるためである。もちろんdateコマンドの立場的には”仕様”である(そもそも7月31日の1ヶ月前は?って聞かれれば人間で6月30日と答えるべきか迷うところがあるし)。なので、「前月」を算出しているつもりが、結果は「当月」を取得しているため、何らかの予期せぬ動きになる可能性も。。そして、この事象が原因のバグは年に数回しか発生しない。。
$ date -d '2015/07/31 1 month ago' +'%Y-%m-%d' 2015-07-01
この問題を回避するには、前月を求めたい場合は日付部分を無視して常に「1日」をベースに比較すれば、このような問題は発生しない。
$ this_month=`date +'%Y-%m-01'` $ last_month=`date -d "${this_month} 1 month ago" +'%Y-%m'` $ echo $last_month 2015-06
まとめ
dateコマンドで「前月」を計算したいときは、日付部分を「1日」基準で計算してやる必要がある!
以上