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