12.07.2011

Unix time conversion in AWK (with Fliegel-Van Flandern algorithm)

AWK: フリーゲルの公式に基づく Unix time 変換処理

gawk が使える場合を除き、AWKスクリプトにおけるグレゴリオ暦とUnix time(POSIX time)の相互変換処理は自前で準備する必要がある。
そこで今回はフリーゲルの公式を使って実装してみる。

・unix_time.awk ※テスト用コードも実装済み

#!/usr/bin/nawk -f

# Based on Fliegel-Van Flandern algorithm.
# Inputs must be Epoch (1970/01/01 00:00:00) and over.
# Time zone will not be considered.
function to_unix_time(yyyy, mm, dd, hh, mi, ss) {
  if (mm - 2 <= 0) { --yyyy; mm += 12}
  days = int(365.25 * yyyy) + int(yyyy / 400) - int(yyyy / 100) \
      + int(30.59 * (mm - 2)) + dd - 719499  # 1721089 - 2440588(Epoch)
  return ((days * 24 + hh) * 60 + mi) * 60 + ss
}

function to_string(unix_time) {
  ss = unix_time % 60; unix_time = (unix_time - ss) / 60
  mi = unix_time % 60; unix_time = (unix_time - mi) / 60
  hh = unix_time % 24; unix_time = (unix_time - hh) / 24
  j = unix_time + 2472632  # 2440588(Epoch) + 32044
  g = int(j / 146097); dg = j % 146097
  c = int((int(dg / 36524) + 1) * 3 / 4); dc = dg - c * 36524
  b = int(dc / 1461); db = dc % 1461
  a = int((int(db / 365) + 1) * 3 / 4); da = db - a * 365
  y = g * 400 + c * 100 + b * 4 + a
  m = int((da * 5 + 308) / 153) - 2
  d = da - int((m + 4) * 153 / 5) + 122
  yyyy = y - 4800 + int((m + 2) / 12)
  mm = (m + 2) % 12 + 1
  dd = d + 1
  return sprintf("%04d-%02d-%02d %02d:%02d:%02d", yyyy, mm, dd, hh, mi, ss)
}

# test code
{
  x = to_unix_time(substr($1, 1, 4), substr($1, 6, 2), substr($1, 9, 2))
  expected = (NR - 1) * 60 * 60 * 24
  if (x != expected) {
    print "Assertion failed. line " NR " - Expected: " expected ", Received: " x
    exit
  }
  print to_string(x)
}

Python の datetime モジュールの処理結果と比較することで、1970/1/1~9999/12/31 の期間の全日付について連続性および妥当性を検証する。

・unix_time_test.py

#!/usr/bin/env python

import datetime

s = datetime.date(1970, 1, 1)
t = datetime.date(9999, 12, 31)

while True:
  print '%s 00:00:00' % s
  if s >= t: break
  s += datetime.timedelta(days=1)

datetime.date では西暦10000年は表現できないので注意。(Exception が発生)
テスト用のシェルを作るならこのような感じである。

・unix_time_test.sh

#!/usr/bin/ksh

./unix_time_test.py > ./test_in.txt
./unix_time.awk ./test_in.txt > ./test_out.txt
diff ./test_in.txt ./test_out.txt > /dev/null
if [ $? -eq 0 ]; then print 'OK'; else print 'NG'; fi

改行コードがLFの場合、58657940 bytes(≒56MB) のファイルが2個生成される。(test_in.txt, test_out.txt)
Windows 環境なら diff のところを comp で検査。

・出力結果

OK

ただ実際のところ、もう少しわかりやすいコーディングのほうが周囲の理解を得やすいかもしれない。
Perl や Python、Ruby が使える環境であれば尚更である。

参考:
http://www5d.biglobe.ne.jp/~noocyte/Programming/GregorianAndJulianCalendars.html#DateToDayNumber
http://en.wikipedia.org/wiki/Julian_day

0 件のコメント:

コメントを投稿