Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 145 lines
  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.

3 comments

Georgy Pruss 19 years, 11 months ago  # | flag

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:

from urllib import urlopen
from time import time
from os import system

def ask_time( url, tzn, tz2=None ):
  """Get time form the Internet.
  # url must be e.g. http://tycho.usno.navy.mil/cgi-bin/timer.pl :-)
  # tzn must be UTC,EST,PST,etc.
  #     UTC will return time like "22:30:40 UT"
  #     Other options return like "05:30:40 PM" i.e. with AM/PM
  # tz2 can be another time zone, e.g. EDT,PDT,etc.
  #Returns "HH:MM:SS AA" or empty string if any failure.
  """
  try:
    doc = urlopen( url ).readlines()
    for line in doc:
      line = line.strip()
      if line.endswith( tzn ) or (tz2 and line.endswith( tz2 )):
        col = line.index(':')
        return line[col-2:col+9] # 04:01:28 PM (or 22:30:40 UT)
  except:
    pass
  return ''

def synch_time( url, tz, tz2=None ):
  time_str = ask_time( url, tz, tz2 )
  if time_str:
    try:
      before = time()
      system( 'time ' + time_str ) # do synchronize!
      delta = time() - before
      return "Time is %s\nCorrected by %.1f sec" % (time_str,delta)
    except:
      pass
  return 'Not synchronized'

if __name__ == "__main__":
  from win32ui import MessageBox
  rc = synch_time( "http://tycho.usno.navy.mil/cgi-bin/timer.pl", "EST", "EDT" )
  MessageBox( rc, "synch_time" )

# EOF
Robin Becker (author) 19 years, 10 months ago  # | flag

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

Guy Stone 19 years, 7 months ago  # | flag

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

return line[col-2:col+9] # 04:01:28 PM (or 22:30:40 UT)

with

tval = line[col-2:col+9] # 04:01:28 PM (or 22:30:40 UT)
hh = int(tval[0:2])
if tval[9] == 'P':
    hh = hh + 12
return str(hh) + tval[2:8]