前回、 laclefblog - NTPサーバから現在時刻を取得する でNTPについて触れたので、その詳細メモ。
NTP(Network Time Protocol)は動作が複雑なので、クライアント向けにSimpleなNTP(SNTP)が登場した。
UDP123番ポートを利用し、レスポンスヘッダ領域内に4つのタイムスタンプ(各64ビット)が格納される。
時刻設定の際には、上記タイムスタンプに加えて、Destination Timestamp(クライアントがレスポンスデータを受信した時刻)を使用する。
タイムスタンプは、1900年1月1日午前0時0分0秒(UTC)を起点とした積算秒数で表される。従って、UNIXなどで用いられるエポック(1970年1月1日午前0時0分0秒)を基準にする為には、レスポンスタイムスタンプから1900年1月1日から1970年1月1日までの秒数(2208988800秒 = 25567日 * 24時間 * 60分 * 60秒)を減算する。計算で閏秒を考慮する必要は無い。
Pythonで、NTPサーバから現在時刻を取得するプログラムを書く。
ASPN : Python Cookbook : Simple (very) SNTP client を参考にした。
>>> import socket
>>> import struct
>>> import time
>>> ntp_ip = "ntp.nc.u-tokyo.ac.jp"
>>> ntp_port = 123
>>> TIME1970 = 2208988800L
>>> client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
>>> data = '\x1b' + 47 * '\0'
>>> client.sendto(data, (ntp_ip, ntp_port))
48
>>> data, address = client.recvfrom(1024)
>>> t = struct.unpack('!12I', data)[10]
>>> print time.ctime(t - TIME1970)
Tue Jun 12 23:40:25 2007
IronPythonでも書いてみる。
>>> import System
>>> import System.Net
>>> import time
>>> ntp_ip = System.Net.Dns.Resolve("ntp.nc.u-tokyo.ac.jp").AddressList[0]
>>> ntp_port = 123
>>> TIME1970 = 2208988800L
>>> ipep = System.Net.IPEndPoint(ntp_ip, ntp_port)
>>> udpsock = System.Net.Sockets.UdpClient()
>>> msg = System.Array[System.Byte]([0x1b] + [0x00] * 47)
>>> udpsock.Send(msg, msg.Length, ipep)
48
>>> d = udpsock.Receive(ipep)
>>> t = d[0][40] * (2**(8*3)) + d[0][41] * (2**(8*2)) + d[0][42] * (2**(8*1)) + d[0][43]
>>> print time.ctime(t - TIME1970)
Traceback (most recent call last):
File , line 0, in <stdin>##48
File , line 0, in CTime##50
ValueError: year is too low
あれれー? 原因を探す(pyがPython、ipyがIronPythonでの挙動)。
(py) >>> time.time() 1181661656.046 (ipy)>>> time.time() 63317290858.0 (py) >>> time.gmtime() (2007, 6, 12, 15, 21, 34, 1, 163, 0) (ipy)>>> time.gmtime() (2007, 6, 12, 15, 21, 38, 1, 163, -1) (py) >>> time.gmtime(0) (1970, 1, 1, 0, 0, 0, 3, 1, 0) (ipy)>>> time.gmtime(0) (1, 1, 1, 0, 0, 0, 0, 1, -1)
この辺に原因がありそう。IronPythonでは、エポックが西暦1年になっている様だ。
従って、time.ctime()の引数に、西暦1年1月1日0時0分0秒から西暦1970年1月1日0時0分0秒までの秒数を加えてやれば、Pythonと同じ挙動をするはず。
>>> print time.ctime(t - TIME1970 + time.mktime((1970, 1, 1, 0, 0, 0, 3, 1, 0))) 水 6 13 00:28:26 2007
良し。