|
Description:
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.
Source: Text Source
import sys, socket
from struct import pack, unpack
from time import time, ctime, mktime
__all__=('sntp_time',)
_TIME1970 = 2208988800L
_data = '\x1b' + 47*'\0'
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)
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:
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))
print 'Adjustment',r and 'Carried out!' or 'Failed!'
else:
st = GetSystemTime()
print 'Current System Time', str(st)
ft = GetSystemFileTime()
adj = long(mu*10000000L)
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
Discussion:
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.
|