2011年5月16日月曜日

unix time

unixには1970年1月1日正子(0時0分)からの延秒数を数えている32ビットの時計がある. 2038年1月19日にサインビットが立つといわれ, 2000年問題みたいになにか起きかもしれないが, 私は多分もうこの世にはいず, 状況を知ることはかなわぬ.

この時計の元は, MITのMulticsではないか. Multicsには, 1900年1月1日正子からのマイクロ秒を数える52ビット時計があった. マイクロは10-6だから, 20ビット程度であり, unixの32ビットに対して52ビットなのは分かる. 私がMITに滞在したのは, 1973年9 月から74年7月までだが, その時計のサインビットが立ったのは, その少し前のたしか5月だったとある院生から聞いた.

まず脱線して, それがいつだったか計算してみよう. 例の個人用電卓が活躍する. 251は2251799813685248. これを1日のマイクロ秒864000000000で割る.

商は26062, 剰余は43013685248. つまり1900年1月1日から26062日後を知りたい. それには1900年1月1日のユリウス日2415021に26062を足し, その2441083がユリウス日になる日を知ればよい.

ここから先は電卓から離れ, 理科年表のユリウス日の表による.

すると, 1971年5月11日がその日であることが判明. 日以下を計算すると, 11時 56分 53秒 685248マイクロ秒であった.

jdをユリウス日を計算する関数として, Schemeで検算すると,

(+ (* (- (jd 1971 5 11) (jd 1900 1 1)) 86400000000)
(* 11 3600000000) (* 56 60000000) (* 53 1000000) 685248)
=> 2251799813685248

(factorize 2251799813685248)
=>
(2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2)

(length (factorize 2251799813685248)) => 51


本題へ戻り, 通常のy年mon月d日h時min分s秒からunix timeへの変換の方は, Multicsの場合と同様に簡単だ.

(JD(y,mon,d)-JD(1970,1,1))*86400+h*3600+min*60+s

となる.

一方, unix time tからy,mon,d,h,min,sへの変換は, ユリウス日からy,mon,dへの変換関数があればなんでもないが, まだそういうプログラムは書いたことがない.

カレンダーの変換で一番面倒なのは, Grogorian暦でのうるう年の計算である. しかし, ことunix timeに限れば, 2000年が通常のうるう年なのが幸いだ. そこで書いたのが次のSchemeのプログラムである.

(define (unixtime t)
(let* ((s (modulo t 60)) (m (quotient (modulo t 3600) 60))
(h (quotient (modulo t 86400) 3600)) (d (quotient t 86400))
(y (- (floor (/ (+ d 731) 365.25)) 2))
(c (- d (floor (+ (* y 365.25) 0.25))))
(e (if (= (modulo y 4) 2)
'(0 31 60 91 121 152 182 213 244 274 305 335)
'(0 31 59 90 120 151 181 212 243 273 304 334)))
(n (apply + (map (lambda (f) (if (<= f c) 1 0)) e))))
(list (inexact->exact (+ 1970 y)) n
(inexact->exact (- c (list-ref e (- n 1)) -1)) h m s)))


引数のtがunixtimeである. 最初にs(秒), m(分), h(時)を取り出す. dは通算の日数になる. その後の変数は, yが1970年以降の年数, cがその年内の日数, nが月だ. yはdを365で割ってfloorを取ればよいが, 閏年があるからそうは問屋が卸ろさない.

まず, y年について, 前の年の終りまでの日数は,



欲しい値は,



365の代りに365.25で割ればよさそうに見えるので, 2.25でテストしてみる.

(map (lambda (n) (floor (/ n 2.25))) (a2b 0 20))
=> (0 0. 0. 1. 1. 2. 2. 3. 3. 4. 4. 4. 5. 5. 6. 6. 7. 7.
8. 8.)

4が3個並ぶのがうるう年に対応し, これを2年にしたいから, 3つの0と2つの1の5個をスキップするために, nの代りに(+ n 5)とし, 最後に2を引く.

(map (lambda (n) (- (floor (/ (+ n 5) 2.25)) 2)) (a2b 0 20))
=> (0. 0. 1. 1. 2. 2. 2. 3. 3. 4. 4. 5. 5. 6. 6. 6. 7. 7.
8. 8.)

なるほどうまくいくので, 356.25に修正し, テストする.

(map (lambda (n) (- (floor (/ (+ n 366 365) 365.25)) 2))
'(0 364 365 729 730 1095 1096 1460 1461 1825 1826 2190
2191 2555))
=> (0. 0. 1. 1. 2. 2. 3. 3. 4. 4. 5. 5. 6. 6.)

うまくいく. これを1970に足せばよい.

次に上の表にあった前年までの日数の和を計算するには,

(lambda (y) (+ (* y 365) (quotient (+ y 1) 4)))

(lambda (y) (floor (+ (* y 365.25) 0.25)))

とする. これをdから引くと, その年内の日数cが得られる. 前の年の終りまでの日数の和のように, 前の月の終りまでの日数の和のリストを, うるう年か否かで変数eに用意する.

月nは, このリストで, cが越えるものの数として得る. nが決れば, 月内の日数は, cから先ほどのリストの要素を引いて作る.

これで完成. テストしてみる.

(unixtime 0) => (1970 1 1 0 0 0)
(unixtime (expt 2 31)) => (2038 1 19 3 14 8)

0 件のコメント: