日付の計算にGNUのdateを使ってみる

会社ではEDI伝送システムの担当をしているbonlifeです。Oracleに溜め込んだ過去の伝送データの実績を調査する際、対象期間を長くすると一時セグメントを拡張できず、エラーになってしまうので、シェルで月ごとに分割した結果を取得したりしています。(ホントは定期的にSUMMARY用のテーブルに書き出しておけば良いんですけどね。)シェルの引数にFROMとTOをYYYYMM形式で指定して月をインクリメントし、都度SQLを発行するようにしています。最近、GNUのdateでは結構高度な演算が出来たことを知ったので、今までの泥くさいやり方をGNUのdateを使ったスマートなやり方に書き換えてみることにしました。(今まではAIXの検証環境からシェルでAIXの本番環境につないでいたのですが、諸事情あってそれが禁止になってしまったので、クライアントのWindows端末からつないで処理をすることになり、AIXでのシェルの代替としてcygwinbashを使うことにしました。おかげでGNUデビューですよ。)
尚、GNU dateの使い方に関して、とらぶろぐさんを参考にさせていただきました。ありがとうございます。

sample_a.sh (手動で月の計算を行うサンプル)

#!/bin/sh

error_exit () {
  rm -f *_$$.dat 2> /dev/null
  echo "ERROR END."
  exit 1
}

if [ -z `echo $1 | egrep '^200[0-9](0[1-9]|1[0-2])$'` ];
then
  error_exit
fi
if [ -z `echo $2 | egrep '^200[0-9](0[1-9]|1[0-2])$'` ];
then
  error_exit
fi

if [ $1 -gt $2 ];
then
  error_exit
fi

YEAR_TARGET=`expr substr $1 1 4`
MONTH_TARGET=`expr substr $1 5 2`
YEAR_TO=`expr substr $2 1 4`
MONTH_TO=`expr substr $2 5 2`

while [ $YEAR_TARGET -lt $YEAR_TO -o $YEAR_TARGET -eq $YEAR_TO -a $MONTH_TARGET -le $MONTH_TO ];
do
  echo $YEAR_TARGET$MONTH_TARGET

##############
# ここで処理 #
##############

  if [ $MONTH_TARGET -lt 9 ];
  then
    MONTH_TARGET=`expr $MONTH_TARGET + 1`
    MONTH_TARGET="0"$MONTH_TARGET
  elif [ $MONTH_TARGET -ge 9 -a $MONTH_TARGET -lt 12 ];
  then
    MONTH_TARGET=`expr $MONTH_TARGET + 1`
  else
    MONTH_TARGET="01"
    YEAR_TARGET=`expr $YEAR_TARGET + 1`
  fi
done
echo "finished."

うーん、われながら強引な処理です。月の値が1桁かどうか、によって処理を分けたりしてますからね。続いて、GNU dateを使ったサンプルです。

sample_b.sh (GNU dateコマンドを活用して月の計算を行うサンプル)

#!/bin/sh

error_exit () {
  rm -f *_$$.dat 2> /dev/null
  echo "ERROR END."
  exit 1
}

if [ -z `echo $1 | egrep '^200[0-9](0[1-9]|1[0-2])$'` ];
then
  error_exit
fi
if [ -z `echo $2 | egrep '^200[0-9](0[1-9]|1[0-2])$'` ];
then
  error_exit
fi

if [ $1 -gt $2 ];
then
  error_exit
fi

DUMMY_DAY=01
TARGET_DATE=$1$DUMMY_DAY

while [ `expr substr $TARGET_DATE 1 6` -le $2 ];
do
  TARGET_MONTH=`expr substr $TARGET_DATE 1 6`
  echo $TARGET_MONTH

############## 
# ここで処理 #
##############

  TARGET_DATE=`date --date "$TARGET_DATE 1 month" +%Y%m%d`
done
echo "finished."

日(DD)の部分まで値を入れておかないと正しく処理してくれないので、DUMMY_DAYを使っているのが格好悪いですが、ソースは圧倒的にシンプルになりました。これで万事オッケー!
と思ったのですが…GNU dateを使ったsample_b.shの方がなんとなく遅いのです。
「time sample_a.sh 200001 200912」としてsample_a.shの実行時間を計測したところ以下の通りでした。

real    0m9.834s
user    0m3.610s
sys     0m2.090s

ふむふむ。それほどストレスなく表示されます。続いて「time sample_b.sh 200001 200912」としてsample_b.shの実行時間を計測したところ以下の通りでした。

real    0m25.054s
user    0m10.550s
sys     0m6.440s

あらあら、倍以上かかっちゃってますよ…。コイツぁ、厳しいかもしれない。やっぱりかなりゴージャスな日付計算に対応しているGNUのdateは処理にかかる時間もゴージャスなのね!とガッカリしかけたのですが、本来は「ここで処理」と書いている部分でヘヴィーなSQLを発行するので、トータルの実行時間を考慮すると数秒程度の違いはたいした問題ではないことに気付きました。ただ、月レベルだと手組みの方が速いですが、日、時間、分、秒とどんどん単位を小さくしていくとどこかでdateコマンドの方が速くなってくるんでしょうね。後日気が向いた時に調査してみようと思います。(と言いつつ、気が向くことはこの先おそらくないので、調査はしないでしょう…。)