カタカタブログ

SIerで働くITエンジニアがカタカタした記録を残す技術ブログ。Java, Oracle Database, Linuxが中心です。たまに数学やデータ分析なども。

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日」基準で計算してやる必要がある!

以上