NTP うるう秒

$Id: ntpd-leapseconds.html,v 1.5 2007/02/14 04:57:22 candy Exp candy $
この文書の内容は無保証です。 ツッコミはこっち
  1. 秒の定義

    1 秒の長さは、 SI (国際単位) によって、 セシウム原子の固有の周波数で定義されている(SI 秒)。
    「秒は、セシウム 133 原子の基底状態の 2 つの超微細準位(F=4, M=0 および F=3, M=0)の間の遷移に対応する放射の 9 192 631 770 周期の継続時間」
    原子時計は、この 1 秒を積算することによって、時刻を決定する。

    この定義が制定されたのは 1967 年で、 それ以前は地球の運動に基づいた定義だった。

  2. タイムシステム

    時刻を表す方法はいくつもある。 普段我々が使っている時刻は日本標準時(JST)であるが、 それは協定世界時(UTC)を 9 時間進めた時刻である。 UTC は TAI と UT1 から決定される。 参考:
  3. うるう秒

    地球の自転は年々変化するので、UT1 もそれに伴い一日の長さが変化する。 UT1 と UTC との差が +-0.9 秒以内に収まるように、 UTC に 1 秒単位で挿入か削除を行う。 この 1 秒をうるう秒と言う。 うるう秒の調整は 6 月 30 日か 12 月 31 日に行い、 挿入の場合は 23:59:59 の次に 23:59:60 を挿入し、 削除の場合は 23:59:59 を削除する。

    UTC が TAI - 10 で開始以来 1998/12/31 23:59:60 まで、 うるう秒調整は 22 回あった(すべて挿入)。 この時点で UTC = TAI - 32。 2005/12/31 23:59:60 のうるう秒挿入後は、 UTC = TAI - 33 となる。

  4. UNIX time とうるう秒

    UNIX time はある起点からの秒数として表現されるので、 UNIX time と日付を正しく相互変換するには、うるう秒を考慮する必要がある。 うるう秒を考慮するかどうかはシステムの設定に依存する。 考慮しないシステムでは、うるう秒調節がなかったことにして変換する。 当然、うるう秒を考慮するシステムとしないシステムとでは、時刻の互換性がない (同じ値を持つ UNIX time が異なる日付に表示される。 同じ日付を UNIX time に変換すると、異なる値になる)。

    例えば 2006/01/01 09:00:00 (JST) は、 うるう秒考慮なしのシステムでは

    $ perl -e 'use POSIX; printf("%d\n", mktime(0, 0, 9, 1, 0, 106));'
    1136073600
    
    となり、うるう秒考慮ありのシステムでは、
    $  perl -e 'use POSIX; printf("%d\n", mktime(0, 0, 9, 1, 0, 106));'
    1136073623
    
    となる。

    2006/01/01 JST のうるう秒挿入前後の JST と UNIX time の動きを、 時系列で表すと次のようになる。
    日付(JST)UNIX time (うるう秒考慮なし)UNIX time (うるう秒考慮あり
    2006/01/01 08:59:5911360735991136073621
    2006/01/01 08:59:6011360736001136073622
    2006/01/01 09:00:0011360736001136073623
    2006/01/01 09:00:0111360736011136073624
    うるう秒考慮のあるシステムでは、うるう秒の前後でも連続して 1 秒ずつ増加する。

    うるう秒考慮のないシステムの場合、 08:59:60 と 09:00:00 で同じ値を返すのが正しい(POSIX 仕様)。 つまりうるう秒挿入直後、UNIX time を 1 秒戻さなければならない (削除の場合は 1 秒進める)。

    ではだれが UNIX time を調整するのか? カーネルがうるう秒のスケジュールを知っていて、調整してくれるなら問題ない。 そうでなければ誰かが(NTP を使うなり手作業でやるなり)調整する必要がある。

    (例えば FreeBSD のカーネルはうるう秒など知らないし、 FreeBSD の ntpd も、うるう秒操作時に UNIX time を調整してくれない (そもそも NTP time のうるう秒調整すらしない)。 従って、FreeBSD の場合、09:00:00 に、 ntpdate なり date コマンドなりでカーネルの UNIX time を調整する必要がある。)

    参考:

  5. NTP とうるう秒

    NTP time はある起点からの秒数として表現されるが、 UTC と NTP time の変換では、うるう秒を考慮しなくてよい。 その代わり、 うるう秒調整が起きると、NTP time は一秒だけ止まったり(挿入時)、 一秒飛ばしたり(削除時)する。

    うるう秒調整がある場合、 その日(6 月 30 日か、12 月 31 日)の NTP パケットには、 Leap Indicator (LI) が特別な値になる。 通常 LI の値は 00(2進数)だが、 うるう秒挿入の日は 01 (2進数)に、 うるう秒削除の日は 10 (2進数)にセットされる。 これによって、NTP はうるう秒調整を知ることができる。

    2006/01/01 JST のうるう秒挿入前後の JST と NTP time の動きを、 時系列で表すと次のようになる。 うるう秒と次の秒で、NTP time が同じ値を示すことに注意!
    日付(JST)NTP timeLI
    2006/01/01 08:59:59334506239901
    2006/01/01 08:59:60334506240001
    2006/01/01 09:00:00334506240000
    (参考: The NTP Timescale and Leap Seconds) (この NTP の仕様では、 うるう秒挿入時は NTP time を止め、 時刻問い合わせがあるごとに 1 ナノ秒進めることになっている。 よって UTC, TAI, and UNIX time で指摘されたように時間が逆行することはない (極めてゆっくりと進む)。 しかし ntpd 4.2.0 の実装は仕様通りではなく、 相変わらず逆行するようである)

  6. ntpd とうるう秒

    ntpd は NTP の参照実装で、2003 年 10 月に version 4.2.0 がリリースされている。 ntpd には参照クロックドライバがいくつか用意されており、 原子時計 や GPS や 電波時計などから、時刻を入力できるようになっている。 参照クロックドライバは、ntpd のカーネルへと時刻を伝えるが (関数 refclock_process())、 そこで与える時刻は、年月日時分秒で表すことになっている。 ntpd カーネルはその時刻を NTP time に変換した上で(関数 clocktime())、 処理を続ける。 この関数 clocktime() は、UNIX の標準関数である mktime(3) は使用せず、 自前で変換アルゴリズムを用意している。 そのアルゴリズムでは、うるう秒は考慮されていない。

    NTP は、うるう秒調整日には、 パケットの Leap Indicator (LI) をセットすることになっている。 上位の NTP サーバが LI をセットすれば、 下位の NTP サーバは、それを順次伝えて行く。 では最上位 (Stratum 1) の NTP サーバに、うるう秒調整日を教えるのはだれか? 実は、それも各参照クロックドライバにまかされているのである。 そして、LI を設定する参照クロックドライバは、全体の 3 割だけだ。 7 割のドライバは LI をセットしない(うるう秒に対応していない)。 (例えば GPS で一般的に使われている NMEA フォーマット用のドライバは、 LI をセットしない。) また、たとえドライバがうるう秒に対応していたとしても、 接続する機器がうるう秒を正しく処理するかどうかはわからない。

    LI がセットされていたとして、 それを理解して、内部に保持する NTP time を調整するのは NTP クライアントの仕事である。 実はStratum 1 の NTP サーバも、 参照クロックドライバから疑似的な NTP パケットを受信する NTP クライアントである。 ところが、ntpd 4.2.0 には LI を処理するコードが含まれていない。 従ってドライバが LI を設定しても、 ntpd の保持する NTP time には、うるう秒調節が行われない。

    ドライバが LI を設定しない場合どうなるか。

    1. 2006/01/01 08:59:59 までは、正しい NTP time を返す。
    2. 2006/01/01 08:59:60 も正しい NTP time を返す。
    3. 2006/01/01 09:00:00 以降、NTP time は 1 秒大きい値を返す。
    4. 一定時間経過すると、GPS に対する offset が -1 秒になる。 NTP time は 1 秒大きい値を返す。
    5. さらに一定時間経過すると、時刻修正を行い時刻を 1 秒もどす。 NTP time 正しい値を返すようになる。 同時に UNIX time も調整される。
  7. 望ましい対処(?)

  8. おまけ: FreeBSD とうるう秒

    FreeBSD 4 以降の場合、標準ではうるう秒を考慮しない設定になっている。 うるう秒を考慮するようにするには、
    # cd /usr/src/share/zoneinfo/
    # vi leapseconds
    Leap 1998 の行の下に、以下の行を挿入する。
    Leap    2005    Dec     31      23:59:60        +       S
    
    # make clean
    # make LEAPSECONDS=yes install
    # cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
    
    とする。 元に戻すには
    # cd /usr/src/share/zoneinfo/
    # make clean
    # make install
    # cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
    
    とする。 これは mktime(3), gmtime(3) 等の関数の動作に影響を与えるが、 各コマンドは起動時に /etc/localtime を参照するだけなので、 動作中のコマンドには影響しない。

    うるう秒を考慮する /etc/localtime と、考慮しない /etc/localtime をそれぞれ /etc/localtime.with_leap (73 バイト), /etc/localtime.without_leap (257 バイト) などとコピーしておけば、

    # cp /etc/localtime.with_leap /etc/localtime   (うるう秒を考慮するようにする。)
    # cp /etc/localtime.without_leap /etc/localtime   (うるう秒を考慮しないようにする。)
    
    のように簡単に切替えが可能になる。
  9. うるう秒の時の ntpd の動作

    当方で管理している ntpd は何一つうるう秒対応らしいことをしていない。 うるう秒挿入時、その ntpd の動作を検証した。 (ちなみに参照クロックはジュピター GPS)

[戻る]