LoginSignup
62
58

More than 5 years have passed since last update.

シェルスクリプトで時間計算を一人前にこなす

Last updated at Posted at 2014-01-30

時間の計算もShellScriptの泣き所だった

シェルスクリプトが敬遠される理由の一つ。それは時間の計算機能が弱いところだ。例えば、

  • 今から一週間前の年月日時分秒は?(それより古いファイルを消したい時など)

  • Ya年Ma月Da日とYb年Mb月Db日、その差は何日?(ログを整理したい時など)

  • この年月日は何曜日?(ファイルを曜日毎に仕分けしたい時など)

といった計算が簡単にはできない。dateコマンドの拡張機能を使えばできるものもあるが、できるようになることが中途半端なうえに、OS間の互換性がなくなる。これを解決する方法がこのTipsだ。

UNIX時間との相互変換ができれば一気に解決

前述のような日時の加減算や2つの日時の差を求めるなどといった時は、一旦UNIX時間に変換して計算し、必要に応じて戻せばよいことはご存知のとおり。曜日を求めるのだって、UNIX時間変換の値を一日の秒数(86400)で割って得られた商を、さらに7で割って余りを見ればよい、ということもお分かりだろう。

だが、そのUNIX時間との相互変換が面倒だった。なのでコードを書いてみた。一度コードさえできあがってしまえばあとはそれを使うだけ。必要なシーンで以下のコードをコピペして使ってもらえばいい。

日常の時間 → UNIX時間

UNIX時間への変換は、フェアフィールドの公式から導出される変換式にあてはめるだけなので簡単だ。

DateTime2UNIXtime
echo "ここにYYYYMMDDhhmmss" | # date '+%Y%m%d%H%M%S'などを流し込んでもよい
awk '{
  # 年月日時分秒を取得
  Y = substr($1, 1,4)*1;
  M = substr($1, 5,2)*1;
  D = substr($1, 7,2)*1;
  h = substr($1, 9,2)*1;
  m = substr($1,11,2)*1;
  s = substr($1,13  )*1;

  # 計算公式に流し込む
  if (M<3) {M+=12; Y--;} # 公式を使うための値調整
  print (365*Y+int(Y/4)-int(Y/100)+int(Y/400)+int(306*(M+1)/10)-428+D-719163)*86400+(h*3600)+(m*60)+s;
}'

UNIX時間 → 日常の時間

これはさっきのよりもちょっと面倒。公式使って一発変換とかできないものかと調べたけどそういうのは無いらしい。glibcのgmtime()関数を参考につくってみた。

UNIXtime2DateTime
echo "ここにUNIXtime" |
awk '{
  # 時分秒と、1970/1/1からの日数を求める
  s = $1 % 60;  t = int($1/60);
  m =  t % 60;  t = int( t/60);
  h =  t % 24;
  days_from_epoch = int( t/24);

  # 年を求める
  max_calced_year = 1970;              # To remember every days on 01/01 from
  days_on_Jan1st_from_epoch[1970] = 0; # the Epoch which was calculated once
  Y = int(days_from_epoch/365.2425)+1970+1;
  if (Y > max_calced_year) {
     i = days_on_Jan1st_from_epoch[max_calced_year];
     for (j=max_calced_year; j<Y; j++) {
       i += (j%4!=0)?365:(j%100!=0)?366:(j%400!=0)?365:366;
       days_on_Jan1st_from_epoch[j+1] = i;
     }
     max_calced_year = Y;
  }
  for (;;Y--) {
    if (days_from_epoch >= days_on_Jan1st_from_epoch[Y]) {
      break;
    }
  }

  # 月日を求める
  split("31 0 31 30 31 30 31 31 30 31 30 31", days_of_month);  # 各月の日数(2月は未定)
  days_of_month[2] = (Y%4!=0)?28:(Y%100!=0)?29:(Y%400!=0)?28:29;
  D = days_from_epoch - days_on_Jan1st_from_epoch[Y] + 1;
  for (M=1; ; M++) {
    if (D > days_of_month[M]) {
      D -= days_of_month[M];
    } else {
      break;
    }
  }

  # 結果出力
  printf("%04d%02d%02d%02d%02d%02d\n",Y,M,D,h,m,s);
}'

コードを読むと、年計算がなぜわざわざ配列なのかと思うかもしれないが、これは後々の拡張用。大量のUNIX timeデータを一気に扱うとなった時のため。まぁ、あまり気にしないでおいてもらいたい。

コマンド化したのも置いておいたよ

毎回いちいちソースの中にコピペするのは面倒くさいというあなたのために、utconvという名でコマンド化したものを用意したのでよかったら使ってもらいたい。

しかもこちらはかなりきっちりやっていて、 タイムゾーンを考慮した相互変換 までやっている。

そして最後に申し上げておきたい

「無いものは作れ!」 と。簡単にできる他言語に移行するばかりじゃなくてね。自分で作れば理解も深まるし、自由も利くし……。由緒正しいUNIXの教本にも、そんなようなことが書いてあるらしい。

ちなみに、この時間計算ができるよになると、シェルスクリプトは 自力でCookieが焼けるようになり、 そしてさらに HTTPのセッション管理ができるようになる。 詳しくは下記のTipsで。

62
58
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
62
58