Introduction
Windows XP can automatically synchronize its system clock with an external Internet reference provided by the government. This class provides the same feature under programmer control. You use the Daytime
class to get the external date and time, compare it with the current system time, and optionally update the system time.
Background
RFC867, written by Jonathan Postel in May 1983, describes the Daytime protocol, a simple ASCII string which time servers listening on port 13 use to communicate the current time to requestors. The protocol couldn't be simpler: Once a connection is made with the time server, it responds by sending the formatted time string and terminating the connection.
The format of the string is set by NIST, not RFC867. The string looks like:
JJJJJ YY-MM-DD HH:MM:SS aa b c ddd.d UTC(NIST) *
In addition to the basic month, day, year, hour, minute, second time, the time server also returns a Modified Julian Date (JJJJJ), an indication of daylight savings time status in the United States (aa, "50" if daylight savings), a flag (b, "1" if leap second) indicating that a leap second will be added at the end of the current month, and other data (c is "0" if the time server is functioning normally, ddd.d is the network latency in milliseconds).
The National Institute of Standards and Technology (NIST) maintains several time servers which support Daytime and other standards, such as the Network Time Protocol (RFC1305).
Windows Internet Time Support
Modern Windows systems have support for Internet Time Services (ITS) built-in. The screen shot (above) illustrates the third tab of the system clock's applet. If enabled, Windows will synchronize the clock monthly or on demand.
Using the code
There is no reason to instantiate Daytime
objects. The Daytime
class wraps four shared methods:
Daytime.GetTime()
As DateTime
- Returns the current UTC time from NIST
Daytime.SecondsDifference(dt1 as DateTime, dt2 as DateTime)
As Integer
- Returns the difference between two times in seconds
Daytime.WindowsClockIncorrect()
As Boolean
- Returns True
if NIST time is not close to system time
Daytime.SetWindowsClock(utcTime as DateTime)
- Sets the Windows system time to utcTime
, expressed in UTC.
A call to WindowsClockIncorrect
checks the NIST server (trying each server it knows of until it receives a response) and compares it to the current system time. (Note that system time is kept in Coordinated Universal Time (UTC), also known as Greenwich Mean Time (GMT) or, in the military, Zulu. Windows converts UTC to local time using your specified time zone.)
After a call to WindowsClockIncorrect
or GetTime
, Daytime
's two shared variables are updated:
Daytime.LastHost
As String
- The IP address used to get NIST time (or "" if no server responded)
Daytime.LastSysTime
As DateTime
- The Windows system time just before the call to the NIST server, for comparison.
The following code illustrates a typical use of the Daytime
class:
If Daytime.WindowsClockIncorrect() Then
Daytime.SetWindowsClock(Daytime.GetTime())
End If
GetTime
returns the current system time if it fails to connect with a time server.
Looking at the code itself, you may want to modify the list of time servers or the number of seconds that you'll tolerate between NIST time and the Windows clock. The tolerance, THRESHOLD_SECONDS
, is 15 by default. It should be no less than 3 seconds, to account for network delays and general entropy, or you might want to make it zero to force an update every time.
The list of servers comes from NIST Internet Time Servers. As they suggest, the code uses IP addresses instead of host names.
Your results may vary, but here's how the various time servers responded to my computer in San Jose, CA:
53032 04-01-28 19:16:20 00 0 0 320.7 UTC(NIST) * [129.6.15.28] Gaithersburg, MD
53032 04-01-28 19:16:21 00 0 0 917.8 UTC(NIST) * [129.6.15.29] Gaithersburg, MD
53032 04-01-28 19:16:21 00 0 0 624.7 UTC(NIST) * [132.163.4.101] Boulder, CO
53032 04-01-28 19:16:21 00 0 0 406.8 UTC(NIST) * [132.163.4.102] Boulder, CO
53032 04-01-28 19:16:21 00 0 0 121.3 UTC(NIST) * [132.163.4.103] Boulder, CO
53032 04-01-28 19:16:22 00 0 0 754.1 UTC(NIST) * [128.138.140.44] Boulder, CO
53032 04-01-28 19:16:26 00 0 0 124.0 UTC(NIST) * [192.43.244.18] Boulder, CO
53032 04-01-28 19:16:27 00 0 0 459.2 UTC(NIST) * [131.107.1.10] Redmond, WA
53032 04-01-28 19:16:28 00 0 0 82.4 UTC(NIST) * [66.243.43.21] San Jose, CA
53032 04-01-28 19:16:40 00 0 0 110.5 UTC(NIST) * [216.200.93.8] (Abovenet) VA
53032 04-01-28 19:16:42 00 0 0 657.3 UTC(NIST) * [208.184.49.9] San Jose, CA
53032 04-01-28 19:16:43 00 0 0 649.7 UTC(NIST) * [207.126.98.204] Sunnyvale, CA
53032 04-01-28 19:18:03 00 0 0 441.7 UTC(NIST) * [205.188.185.33] (AOL) VA
Owing to the MyDoom virus, perhaps, you can see that this test took 163 seconds to complete. Because the first server will likely respond successfully, performance is usually sub-second, but I wouldn't try doing it in a tight loop.
Future Enhancements
It might be nice in the future to test the Internet connection before calling a time server. I assume that dialup users would not want to routinely dial up just to adjust their clock. Fortunately, code to check LAN, DIALUP, or OFFLINE is fairly simple; it is described in Microsoft Knowledgebase Article 821770.
Another possible enhancement would be to call several time servers and average their times, as is typical in Network Time Protocol (RFC1305) implementations.
Points of Interest
This code illustrates a simple TcpClient session and a simple call to the Windows Kernel DLL.
The NIST time string has only a two-digit year. (Remember the Y2K problem that was going to freeze elevators and have airplanes fall from the sky?) We have to look at the Modified Julian Date (MJD) to be sure that '04' means 2004. The code, as written, can handle NIST dates in the 1900s (MJD 15020 to 51543) or the 2000s (MJD >= 51544), but of course this code should never see a date earlier than 2004.
History
01/27/2004 AD: Initial