UNIX 時刻に関する四方山話

no extension

この記事はなかなか面白かったが UNIX 時刻1 に関する説明がざっくりしすぎているので,補足を交えながら簡単に紹介してみる。

時刻系に関するおさらい

UNIX 時刻の説明に入る前に,前提となる時刻系についておさらいしておこう。

時刻系というのは,時間および時刻を測るために地球人類が考えた「決まりごと」である。 「決まりごと」なので「正しい時刻系」というのは存在しない。 強いて言うなら「考えた人の数だけ時刻系が存在する」。 とはいえ,やたら滅多に林立しても困るので,ある程度の統一を図る必要がある。

時刻系の歴史について語ると長くなるので,ここでは現代の日常生活に関係の深い2つの時刻系のみ紹介する。

世界時系 : 私達は太陽の日周運動2 を基準に生活している。 そこで昔の人は,この日周運動を時刻の基準にすればいいと考えた。 つまり天球3 上の平均太陽 (mean solar)4 と子午線との時角を 観測 し,そこから基準となる時間(の長さ)を決めればいいわけだ。 こうして決めた時刻系を世界時系と呼ぶ。 世界時系の代表が UT (世界時; universal time)5 である。

原子時系 : いわゆる原子時計のこと。 最初の原子時計の 定義 は「セシウム133原子の基底状態における2つの超微細準位($F=4, M=0$ および $F=3, M=0$)の間の遷移に対応する放射の周期の9,192,631,770倍を1秒とする」というもので,基本的に現在でもこの定義が踏襲されている6。 この原子時計に対して1958年1月1日0時0分0秒 UT2 を原点とした経過時間を TAI (国際原子時; international atomic time) と呼ぶ。 ちなみに GPS の時計も原子時系の一種である。

世界時系は私達の日常生活にマッチしているが観測値であり一定の時間を刻まない。 原子時系は定義された一定の時間を刻む7 が日常生活の基準である世界時系から僅かずつ乖離していく。

UT と TAI を整合させるために考えられたのが UTC (協定世界時; coordinated universal time) である。 UTC は原子時系の一種だが, UT1 との差が1秒未満になるよう閏秒による調整が行われる。 閏秒による調整は不定期だが,遅くとも半年前には告知される。

現在の UTC は1972年1月1日から運用を開始している。 運用開始時点では $TAI - UTC = 10\,\mathrm{sec}$ だったが,2017年1月1日時点では37秒まで拡大している8

UNIX 時刻(UNIX Time)とは

いよいよ本題へ。

大抵の文献では, POSIX 標準の UNIX 時刻の定義は「1970年1月1日0時0分0秒 UTC からの経過秒数」となっていて,かつては32ビット符号付き整数値で表されていた。 32ビットつまり4オクテット固定長データで表現できるためとても重宝されたが,2,147,483,647秒までしか表現できないため,2038年1月19日3時14分7秒より以降は桁あふれをおこしてしまう。 これがいわゆる「2038年問題」である。

「2038年問題」の回避策

「2038年問題」の回避方法としては以下の2つがある。

UNIX 時刻を32ビット符号なし整数で表す : 場当たり的な対処だが時刻を表すデータの長さを変更することなく移行できるため,古いデータ・フォーマットをそのまま流用できる利点がある。 ただし1970年1月1日0時0分0秒より前の時刻は取り扱えなくなる。 これにより UNIX 時刻は4,294,967,295秒(2106年2月7日6時28分15秒)まで拡張される。

UNIX 時刻を64ビット符号付き整数で表す : データ長が倍の8オクテットになるが,抜本的に対応するのであればこちらを選択すべきだろう。 多くの現行システムはこちらでの対応になっている筈である。 これなら9,223,372,036,854,775,807秒(約3000億年)まで表現できる。 ビッグバンからビッグクランチまで(笑)

もっとも時刻情報については UNIX 時刻以外にも様々な形式が存在するため,あえて UNIX 時刻に拘る必要はないかもしれない。 そもそも1秒未満の桁を表現できない UNIX 時刻はタイムスタンプとしてはもはや機能不足と言える。

【実装例1】 OpenPGP における時刻情報

OpenPGP (RFC 4880) パケットにおける時刻情報は,古い仕様を踏襲する形で,4オクテット固定長で定義されている。 現在,次期仕様(RFC 4880bis)で V5 パケットの仕様が検討されているが,時刻情報に関しては4オクテットのままでいくようだ。

【実装例2】 Go 言語の time パッケージ

Go 言語には標準ライブラリとして time パッケージが用意されているが,時刻を表す time.Time 型は西暦元年1月1日0時を原点としナノ秒単位まで対応している。 また UNIX 時刻を返す関数も用意されているが,この関数の返り値は int64 (64ビット符号付き整数) の値を返す。

このように最近のシステムやプログラミング言語は時刻に関する独自のクラス・オブジェクトを用意し,後方互換性を保つために UNIX 時刻を扱うメソッドが用意されていることが多い。

UNIX 時刻と UTC

ところで,前述した UNIX 時刻の定義を見ておかしいとは思わなかっただろうか。

現在の UTC の運用が始まったのは1972年からである。 なのに UNIX 時刻の原点は「1970年1月1日0時0分0秒 UTC」となっているのだ。 存在しない時刻9 を原点にするなんて,おかしいよね!

つまり実際の UNIX 時刻は「1970年1月1日0時0分0秒 UTC からの経過秒数」ではなく,グレゴリオ暦と現在の時制のルールに基づいて,2時点間を単純に引き算しているだけなのである。 このことが最も顕著に現れるのが UTC における閏秒の扱いである。

Go 言語で簡単なコードを書いてみよう。

かつて2017年1月1日0時0分0秒 UTC の直前に閏秒が挿入された。 したがって

package main

import (
    "fmt"
    "time"
)

func main() {
    t1 := time.Date(2016, time.December, 31, 23, 59, 59, 0, time.UTC)
    t2 := time.Date(2017, time.January, 1, 0, 0, 0, 0, time.UTC)
    fmt.Printf("t1 = %v (%v)\n", t1, t1.Unix())
    fmt.Printf("t2 = %v (%v)\n", t2, t2.Unix())
    fmt.Printf("t2 - t1 = %v sec\n", t2.Unix()-t1.Unix())
}

というコードを書いたとき,もし UNIX 時刻が閏秒を考慮しているのであれば,2時点の UNIX 時刻の差は2になる筈である。 しかし実際に実行してみると

$ go run unix-time.go
t1 = 2016-12-31 23:59:59 +0000 UTC (1483228799)
t2 = 2017-01-01 00:00:00 +0000 UTC (1483228800)
t2 - t1 = 1 sec

となる。 これは他の言語でも(独自に閏秒に対応していないのであれば)同じようになる筈である。

まぁ,しかし,これは UNIX 時刻を設計した人を責めるべきではないだろう。

そもそも UT と UTC の違いを意識してる人なんて殆どいない。 閏秒がネット上で騒がれるようになったのは2012年頃からである10。 今さら UNIX 時刻の仕様を変えようものなら逆に大変な騒ぎになりそうである(UNIX 時刻で格納している過去の時刻データが全てオシャカになる)。

ブックマーク

参考図書

photo
[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識
武内 覚 (著)
技術評論社 2018-02-23 (Release 2018-02-23)
Kindle版
B079YJS1J1 (ASIN)
評価     

コンテナ全盛のこの時代にかなり硬派な内容の Linux 解説書。コンピュータの教科書としても使えそう。

reviewed by Spiegel on 2019-01-07 (powered by PA-APIv5)

photo
天体の位置計算
長沢 工 (著)
地人書館 1985-09-01
単行本
4805202254 (ASIN), 9784805202258 (EAN), 4805202254 (ISBN)
評価     

B1950.0 分点から J2000.0 分点への過渡期に書かれた本なので情報が古いものもあるが,基本的な内容は位置天文学の教科書として充分通用する。

reviewed by Spiegel on 2015-01-11 (powered by PA-APIv5)

photo
猫暦(1) (ねこぱんちコミックス)
ねこしみず美濃 (著)
少年画報社 2014-10-14 (Release 2016-02-15)
Kindle版
B01BHGVLOY (ASIN)
評価     

「寛政の改暦」のころの伊能勘解由(忠敬)とその妻とされる「おえい」の物語。感想はこちら

reviewed by Spiegel on 2016-05-06 (powered by PA-APIv5)


  1. この記事では時刻とはある時点からの経過時間を指すものとして書き分けている。時刻を表すには原点となる時点が必須である。 ↩︎

  2. もちろん地球を中心とした見かけ上の話。正しくは地球の自転運動および太陽を中心とした公転運動を基準にしている,というべきだろう。ちなみに地球の自転運動を基にした時刻系を恒星時系と呼ぶ。世界時系は恒星時系の一種と言える。 ↩︎

  3. 天球とは地球を中心とした無限遠の仮想球面である。地球から見える月や惑星や太陽などの星々は天球への写像と見做すわけですね。 ↩︎

  4. 太陽に対する地球の公転軌道は(ほぼ円に近い)楕円なので,天球上を移動する太陽(真太陽)の移動速度は季節によって僅かに進み遅れが発生する。これに対し,天球上の移動速度が年間で一定となるような仮想的な太陽を平均太陽と呼ぶ。 ↩︎

  5. 厳密には UT には UT0 から UT2 まである。 UT0 はほぼ生の観測値で,観測地点ごとの UT0 を集計し極運動等の補正をかけたものを UT1 と呼ぶ。 UT2 は UT1 から更に自転速度の年間の進み遅れを補正したものである。現在では UT2 は使われていないようだ。 ↩︎

  6. SI 単位系の「秒」の定義。 ↩︎

  7. 「原子時系は一定の時間を刻む」というのは相対論的効果を除外した場合の話。実は,厳密に言うと現在の TAI は相対論的効果などを加味した座標時系として再定義されている。だが日常生活でそれを意識することはないだろう。更に余談だが,座標時系のひとつに TT (地球時; terrestrial time) というのがあって天体力学や位置天文学の分野で使われている。 TAI と TT の間には $TT = TAI + 32.184\,\mathrm{sec}$ という関係がある(したがって,ある時点の UTC が分かれば TT も自動的に求められる)。何故こんな事になっているかについては聞くも涙の歴史物語があったりする(笑) ↩︎

  8. 閏秒に関する議論については「2017年直前の閏秒について」あたりを参考にどうぞ。 ↩︎

  9. 厳密に言うと1972年以前にも UTC と呼ばれる時刻系は存在していたが,現在の UTC とは全く異なり,どちらかというと UT に近いルールだった。このため管理が煩雑になりすぎて破綻してしまったのだ。 ↩︎

  10. 情報処理の高速化により秒単位ではギャップが大きくなりすぎるため。またサービスの可用性(availability)に対する要求が高くなったという点も挙げられるだろう。 ↩︎