Network Time Protocol , or NTP, is widely-used to synchronize system clocks among a set of distributed time servers and clients.
The DAYTIME and TIME protocols are simple responders. When a connection is made or a packet recieved the server simply responds with the current time as an ASCII string for DAYTIME or as a binary value for TIME.
SNTP is a reduced version of the NTP protocol that can be used to replace TIME clients.
NTP is a far more sophisticated protocol created by Dave Mills.
NTP is implemented via UDP over port 123, and can operate in broadcast and multicast modes, or by direct queries. NTP can synchronise to multiple time sources, making allowances for the network delays and apparent reliability of the clocks it synchronises to.
(The assigned ports list includes 123/tcp as NTP as well as 123/udp but the tcp port is not used by ntpd)
tcllib includes an implementation of a client for SNTP and TIME.
package require time proc NetTime {server} { set tok [time::getsntp $server] ;# or gettime to use the TIME protocol time::wait $tok if {[time::status $tok] eq {ok}} { set result [time::unixtime $tok] set code ok } else { set result [time::error $tok] set code error } time::cleanup $tok return -code $code $result } clock format [NetTime ntp.blueyonder.co.uk]
Joe Mistachkin 2003-05-12: Broken on NT 4.0 and perhaps Windows 2000 because there is no "date.exe". This can be fixed by using the following code to set the date/time:
set newtime [clock format $seconds -format %H.%M.%S] exec cmd /c "echo $newtime | time" puts "set time to $newtime" set newdate [clock format $seconds -format %m/%d/%Y] exec cmd /c "echo $newdate | date" puts "set date to $newdate"
The following code was recently posted to comp.lang.tcl:
## ## good-time - for the correct time, ... ## ## Placed into the public domain by [email protected], January 1999. ## ## usage: good-time [host [port [timezone]]] ## host - defaults to time.nist.gov. ## port - defaults to 13. ## timezone - defaults to nothing, not necessary for NIST format. ## ## After wading through a complicated linux time synchronization ## package, it turned out that the key was to telnet to time.nist.gov ## on port 13. You can do that in tcl, so here it is. ## ## It also turns out that a lot of machines return some kind of time ## service on port 13, so I recognize a few variations in the format ## returned. ## ## See http://www.boulder.nist.gov/timefreq/ for more information and ## links to yet more information. ## # 23Jan02RT - add -set flag set doset 0 if {$argc >= 1 && [string equal [lindex $argv 0] -set]} { incr argc -1 set argv [lrange $argv 1 end] set doset 1 } ## ## choose the host ## # 11Jan02RT TODO use host list # numeric values come from "Atomic Clock Sync v2.5" server menu set hlist { 129.6.15.28 129.6.15.29 132.163.4.101 132.163.4.102 132.163.4.103 128.138.140.44 time-nw.nist.gov time-a.timefreq.bldrdoc.gov time.nist.gov allhoststried } # set host time.nist.gov if {$argc > 0} { set hlist [concat[lindex $argv 0] $hlist] } ## ## choose the port ## set port 13 if {$argc > 1} { set port [lindex $argv 1]; } ## ## choose the timezone ## set timezone {} if {$argc > 2} { set timezone [lindex $argv 2]; } ## ## set a time out ## after [expr 30*1000] { close $sock puts stderr {timed out} if {[info exists lines]} { puts stderr "no time found in:\n$lines" } exit 1; } ## ## open a socket ## foreach h $hlist { if {[catch {socket $h $port} sock]} { #puts stderr "socket: $sock" #exit 2; puts stderr "host $h - FAILED" continue } break } ## ## read what comes back ## while {[gets $sock line] >= 0} { # typical daytime response is unix date format without timezone indication # # Mon Sep 16 21:59:22 1996 set seconds 0 if {[regexp \ {^... ([A-Z][a-z][a-z]) ([ \d]\d) (\d\d:\d\d:\d\d) (\d\d\d\d)$} \ $line all month mday time year]} { set seconds [clock scan "$month $mday, $year $time $timezone"] } # # NIST time service format returned by: # # time.nist.gov # time-nw.nist.gov # time-a.timefreq.bldrdoc.gov # # 50343 96-09-17 05:02:07 50 0 0 50.0 UTC(NIST) * # if {!$seconds && [regexp \ {^..... (\d\d)-(\d\d)-(\d\d) (\d\d:\d\d:\d\d) .. . . ..... UTC\(NIST\)} \ $line all year month day time]} { set seconds [clock scan "$month/$day/$year $time UTC"] } # # USNO master clock format returned by: # # tick.usno.navy.mil # tock.usno.navy.mil # # 50343 261 044932 UTC # if {!$seconds && [regexp {^..... ([ \d][ \d]\d) (\d\d)(\d\d)(\d\d) UTC$} \ $line all doy hour minute second]} { set seconds [clock scan $hour:$minute:$second -gmt true] } if {$seconds} { puts "FETCHED: [clock format $seconds]" puts " LOCAL: [clock format [clock seconds]]" if {$doset} { # JJM 5/12/2003 -- added missing "." in between %H and %M. set newtime [clock format $seconds -format %H.%M.%S] exec date.exe $newtime puts "set date to $newtime" } close $sock; exit 0; } # # save unrecognized stuff for final error message # append lines $line\n } ## ## give up ## close $sock; puts stderr "no time found in:\n$lines"; exit 4
See RFC 868 for the background. The client connects to a TIME server on port 37, the server returns a 32-bit unsigned integer of seconds since 1900-01-01 00:00.00 UTC. The protocol takes no account of network delays, the time you get is the time when sent, in seconds.
This little client proc adjusts the return value to the Tcl [clock seconds] standard, by subtracting the number of seconds between 1900 (TIME epoch) and 1970 (Tcl epoch). The arithmetic plays horrible games with Tcl's 32-bit signed integers, but works.
proc TIMEseconds {host} { set c -2208988800 ;# epoch-shifter: 1970-1900 in seconds set s [socket $host 37] ;# 37 is TIME fconfigure $s -translation binary set r [read $s] close $s binary scan $r I t ;# "I" is uppercase i, not lowercase L incr t $c } puts [clock format [TIMEseconds $myTIMEserver]]
PS Thanks to an anonymous donor for teaching me the binary command.
If you wanted to use this client to set the system clock (Windows):
exec cmd.exe /c time [clock format [ TIMEseconds $myTIMEserver] -format %H:%M:%S]
The format required to set the system date with cmd.exe depends on your International settings - this worked on Windows 2000:
package require registry set fmt [ registry get {HKEY_CURRENT_USER\Control Panel\International} sShortDate] foreach e {d m y} {regsub -nocase $e+ $fmt %$e fmt} exec cmd.exe /c date [clock format [TIMEseconds $myTIMEserver] -format $fmt]
Anonymous Donor points out that: slightly more portable than
exec cmd.exe ...
is a formulation based on auto_execok, perhaps
eval exec [auto_execok date] ...
Ralf Fassel rightly points out that the TIME service makes no promises about accuracy - but my guess is that TIME on an NTP server will be pretty accurate, good enough to keep a desktop machine's clock up to date at least.
The ideal TIME server would be one of the public stratum 2 NTP servers, of which there is a very useful, maintained list at [L1 ]. However, relatively few of the public servers leave port 37 open, and you'll need to experiment to find one that does. You should also read and respect the site access restrictions, and you may need to warn the site operator if you intend serious use.
Ralf Fassel (again) points out that NTP remains the best way to keep a network of clocks up to date - while waiting for Tcl to support UDP sockets, install an NTP client/server on your network.
Bob Clark:
There's a pool of public NTP servers that are rotated by DNS magic. You can get time from 0.pool.ntp.org, 1.pool.ntp.org, etc. (I don't recall how many there are.) The names are always valid but every hour or so they resolve to a different server. -- CLN Even better the "pool" is sub-divided into world regions (Europe, Asia, etc) then further into countries. This leaves you plenty of choice to pick up a place close to you, thus minimising the network delay that isn't abstracted away by NTP. EF