Simon Foster's recipe "Simple (very) SNTP client" (see http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/117211) inspired me to get SNTP to do something useful when our office moved to an ISP that didn't do client 37 time setting. This recipe uses SNTP to get an estimate of the time offset and uses Thomas Heller's wonderful ctypes module to allow getting/setting the win32 system time. I apologise in advance for the rather awful int vs long bit twiddling in _L2U32 etc.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | import sys, socket
from struct import pack, unpack
from time import time, ctime, mktime
__all__=('sntp_time',)
_TIME1970 = 2208988800L # Thanks to F.Lundh
_data = '\x1b' + 47*'\0'
#typedef struct _SYSTEMTIME { // st
# WORD wYear;
# WORD wMonth;
# WORD wDayOfWeek;
# WORD wDay;
# WORD wHour;
# WORD wMinute;
# WORD wSecond;
# WORD wMilliseconds;
#} SYSTEMTIME;
#VOID GetSystemTime(
# LPSYSTEMTIME lpSystemTime // address of system time structure
#);
#SYSTEMTIME st;
#GetSystemTime(&st);
#SetSystemTime(&st);
from ctypes import windll, Structure, c_ushort, byref, c_ulong, c_long
kernel32_GetSystemTime = windll.kernel32.GetSystemTime
kernel32_SetSystemTime = windll.kernel32.SetSystemTime
kernel32_SystemTimeToFileTime=windll.kernel32.SystemTimeToFileTime
kernel32_FileTimeToSystemTime=windll.kernel32.FileTimeToSystemTime
class SYSTEMTIME(Structure):
_fields_ = (
('wYear', c_ushort),
('wMonth', c_ushort),
('wDayOfWeek', c_ushort),
('wDay', c_ushort),
('wHour', c_ushort),
('wMinute', c_ushort),
('wSecond', c_ushort),
('wMilliseconds', c_ushort),
)
def __str__(self):
return '%4d%02d%02d%02d%02d%02d.%03d' % (self.wYear,self.wMonth,self.wDay,self.wHour,self.wMinute,self.wSecond,self.wMilliseconds)
class LONG_INTEGER(Structure):
_fields_ = (
('low', c_ulong),
('high', c_long),
)
def GetSystemTime():
st = SYSTEMTIME(0,0,0,0,0,0,0,0)
kernel32_GetSystemTime(byref(st))
return st
def SetSystemTime(st):
return kernel32_SetSystemTime(byref(st))
def GetSystemFileTime():
ft = LONG_INTEGER(0,0)
st = GetSystemTime()
if kernel32_SystemTimeToFileTime(byref(st),byref(ft)):
return (long(ft.high)<<32)|ft.low
return None
def SetSystemFileTime(ft):
st = SYSTEMTIME(0,0,0,0,0,0,0,0)
ft = LONG_INTEGER(ft&0xFFFFFFFFL,ft>>32)
r = kernel32_FileTimeToSystemTime(byref(ft),byref(st))
if r: SetSystemTime(st)
return r
def _L2U32(L):
return unpack('l',pack('L',L))[0]
_UTIME1970 = _L2U32(_TIME1970)
def _time2ntp(t):
s = int(t)
return pack('!II',s+_UTIME1970,_L2U32((t-s)*0x100000000L))
def _ntp2time((s,f)):
return s-_TIME1970+float((f>>4)&0xfffffff)/0x10000000
def sntp_time(server):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(0.5)
#originate timestamp 6:8
#receive timestamp 8:10
#transmit timestamp 10:12
t1 = time()
s.sendto(_data, (server,123))
data, address = s.recvfrom(1024)
data = unpack('!12I', data)
t4 = time()
t2 = _ntp2time(data[8:10])
t3 = _ntp2time(data[10:12])
delay = (t4 - t1) - (t2 - t3)
offset = ((t2 - t1) + (t3 - t4)) / 2.
return address[0], delay, offset
except:
return 3*(None,)
if __name__=='__main__':
go = '--go' in sys.argv
if go: sys.argv.remove('--go')
servers = sys.argv[1:] or '''a.ntp.alphazed.net bear.zoo.bt.co.uk ntp.cis.strath.ac.uk ntp.exnet.com ntp2a.mcc.ac.uk
ntp2b.mcc.ac.uk ntp2c.mcc.ac.uk time-server.ndo.com'''.split()
t0 = time()
mu = 0
ss = 0
n = 0
data = []
a = data.append
for server in servers:
address, delay, offset = sntp_time(server)
if address:
#recursions for mean and sigma squared
n1 = n
n += 1
mu = (offset+mu*n1)/n
d = offset - mu
if n1: ss = ((n1-1)*ss+d*d*(n/n1))/n1
a((server, address, delay, offset))
ss = ss**0.5
print "Offset = %.3f(%.3f)" % (mu,ss)
for (server, address, delay, offset) in data:
print '%s(%s): delay=%.3f offset=%.3f' % (server, address,delay,offset)
if n>3:
if go:
if abs(mu)<5:
r = SetSystemFileTime(GetSystemFileTime()+long(mu*10000000L)) #100 nanosecond units (since 16010101)
print 'Adjustment',r and 'Carried out!' or 'Failed!'
else:
st = GetSystemTime()
print 'Current System Time', str(st)
ft = GetSystemFileTime()
adj = long(mu*10000000L) #100 nanosecond units (since 16010101)
print 'Current System FileTime', ft, 'adjustment', adj
ft += adj
print 'Adjusted System FileTime', ft
ft = LONG_INTEGER(ft&0xFFFFFFFFL,ft>>32)
r = kernel32_FileTimeToSystemTime(byref(ft),byref(st))
print 'Adjusted System Time', str(st), 'r=',r
|
Keeping the clock accurate is a pretty standard requirement, my standard mail/news client used to do this for me, but the new ISP doesn't support the old port 37 mechanism. There is a perl module, but one of the supporting Win32::API modules failed to install.
Why not do it in the batterified Python language? I'm sure others can propose improvements and or better schemes for obtaining accuracy etc. It took less than 5 hours to complete so I'm sure there are bugs etc.
A bit simplier... It can be a bit simplier if you use
http://tycho.usno.navy.mil/cgi-bin/timer.pl
and the time command. The full 47-line code is at
http://zxw.nm.ru/synch_time.py
Here it is without some comments:
Perls before swine :). I guess I must be a bit of a python pig as the thought of using perl inside python hasn't occurred to me. I guess if I had more patience I would have got the original perl module to work. Unfortunately I no longer feel comfortable doing awk like things; I used to love awk I guess I must be a bit of a python pig as the thought of using perl inside python hasn't occurred to me. I guess if I had more patience I would have got the original perl module to work. Unfortunately I no longer feel comfortable doing awk like things; I used to love awk
This required fix on my system. This didn't quite work for me. I'm using cygwin's time.exe in Win98, and it doesn't take AM/PM suffixes.
I replaced the line
with