#cs This program has rwo functions: 1. Fetches NTP server time and synchronizes system time with NTP server time. 2. Makes adjustment of system clock rate so that it keeps accurate time, using _Date_Time_SetSystemTimeAdjustment() function. How to use this program: 1. Disable Windows default time update service by one of following methods: a. Turn off "Settings - Time and language - Date and time - Set time automatically" b. Click off "Control panel - Date and time - Internet time - Synchronize with an internet time server" If left alone, Windows default time update will revert SystemTimeAdjustment value to default 156250 every time it updates the system time. 2. Create a Windows task to run this program with highest privileges. The task should have 2 triggers defined: the first to run this program upen login and the second to run the program in 1-hour intervals every day. 3. Initially levae the system turned on for several hours to allow the program to find a suitable SystemTimeAdjustment value. This value will be recorded in NTP_TimeSync.ini file and used when the system reboots the next time. Many thanks to @TheXman for his kind guidance in getting NTP server time. https://www.autoitscript.com/forum/topic/200643-update-system-time-based-on-ntp-server-time/page/2/?tab=comments#comment-1439629 #ce #RequireAdmin #Region ;**** Directives created by AutoIt3Wrapper_GUI **** #AutoIt3Wrapper_Res_Fileversion=7.0.0.0 #AutoIt3Wrapper_Outfile=NTP_TimeSync.exe #AutoIt3Wrapper_AU3Check_Parameters=-w 3 -w 4 -w 5 -w 6 -d #EndRegion ;**** Directives created by AutoIt3Wrapper_GUI **** #include #include #include #include #include If Not _WinAPI_IsInternetConnected() Then WriteLog("", "System has no internet connection") Exit EndIf Global $syncInterval = 3600 ; in seconds, as defined by Windows Task Scheduler Global $systemTime, $aTime, $sTime, $aInfo, $hTimer, $hOffset, $medianOffset, $hRoundTrip, $medianRoundTrip Global $NTP_Time, $iSeconds, $iFractions, $iMsecs, $sError, $aSocket, $aLog[1] Global $iniFile = @ScriptDir & "\NTP_TimeSync.ini" ; If multiple devices share this folder, each device should have its own log file. If you define an alias for your ; device in the ini file, that alias will be used as a part of its log file name, making it uinque. Global $sAlias If FileExists($iniFile) Then $sAlias = IniRead($iniFile, "Alias", @ComputerName, "") Else IniWrite($iniFile, "TimeAdjustment", @ComputerName, 0) IniWrite($iniFile, "Alias", @ComputerName, "") EndIf Global $logFile = @ScriptDir & "\NTP_TimeSync" & ($sAlias<>"" ? "-": "") & $sAlias & ".log" Global $tBuffer = DllStructCreate("byte[48]") ; Structure of NTP data packet DllStructSetData($tBuffer, 1, 0x23) ; 00 100 011 = LI(0-Leap Indicator) / VN(4-NTPv4) / Mode(3-Client Mode) Global $xRequest = DllStructGetData($tBuffer, 1), $xResponse OnAutoItExitRegister("udp_shutdown") Global $NTP_Server = "time.google.com" ; Out of several alternatives, Google was chosen for its consistency in response times. Global $NTP_IP UDPStartup() GetMedianOffset() UDPShutdown() If $sError = "" Then SetTime() If $sError = "" Then SetSystemTimeAdjustment() EndIf EndIf WriteLog($sTime, $sError) Func GetMedianOffset() $NTP_IP = TCPNameToIP($NTP_Server) Local $i = 0, $aOffset[5][3] If $NTP_IP <> "" Then $hTimer = TimerInit() While 1 NTP_GetTime() If $NTP_Time <> "" Then $aOffset[$i][0] = Number($hOffset) $aOffset[$i][1] = $hRoundTrip $aOffset[$i][2] = $i If $i = 4 Then ExitLoop $i += 1 EndIf If TimerDiff($hTimer) > 5000 Then ExitLoop Sleep(200) WEnd EndIf If $i <> 4 Then $NTP_Server = "pool.ntp.org" ; This had the shortest response time, mostly below 5 milliseconds, but with frequent outliers. $NTP_IP = TCPNameToIP($NTP_Server) If $NTP_IP <> "" Then $hTimer = TimerInit() $i = 0 While 1 NTP_GetTime() If $NTP_Time <> "" Then $aOffset[$i][0] = Number($hOffset) $aOffset[$i][1] = $hRoundTrip $aOffset[$i][2] = $i If $i = 4 Then ExitLoop $i += 1 EndIf If TimerDiff($hTimer) > 5000 Then ExitLoop Sleep(200) WEnd EndIf EndIf If $i <> 4 Then $sError = "Median offset could not be obtained." Return EndIf _ArraySort($aOffset) $medianOffset = $aOffset[2][0] $medianRoundTrip = $aOffset[2][1] ; _ArrayDisplay($aOffset) ; Exit EndFunc ;===>GetMedianOffset Func NTP_GetTime() $NTP_Time = "" $hTimer = TimerInit() $aSocket = UDPOpen($NTP_IP, 123) If @error Then $sError = "UDPOpen failed" Return EndIf UDPSend($aSocket, $xRequest) If @error Then $sError = "UDPSend failed" UDPCloseSocket($aSocket) Return EndIf $xResponse = UDPRecv($aSocket, DllStructGetSize($tBuffer), $UDP_DATA_BINARY) If @error Then $sError = "UDPRecv failed" UDPCloseSocket($aSocket) Return EndIf $systemTime = _Date_Time_GetSystemTime() $hRoundTrip = TimerDiff($hTimer) UDPCloseSocket($aSocket) $iSeconds = Dec(Hex(BinaryMid($xResponse, 41, 4)), $NUMBER_64BIT) ; seconds since 1900-01-01 00:00:00 $iFractions = Dec(Hex(BinaryMid($xResponse, 45, 4)), $NUMBER_64BIT) ; the maximum value is 0xFFFFFFFF, which represents 1 second $iMsecs = Round($iFractions/(2^32)*1000 + $hRoundTrip/2) If $iMsecs >= 1000 Then $iSeconds += Int($iMsecs/1000) $iMsecs = Mod($iMsecs, 1000) EndIf $iMsecs = StringFormat("%03d", $iMsecs) $NTP_Time = _DateAdd("s", $iSeconds, "1900/01/01 00:00:00") & "." & $iMsecs $aTime = _Date_Time_SystemTimeToArray($systemTime) $sTime = StringFormat("%04d/%02d/%02d %02d:%02d:%02d.%03d", $aTime[2], $aTime[0], $aTime[1], $aTime[3], $aTime[4], $aTime[5], $aTime[6]) $hOffset = _DateDiffMS("s", $sTime, $NTP_Time) EndFunc ;==>NTP_GetTime Func SetTime() ; $hTimer = TimerInit() $systemTime = _Date_Time_GetSystemTime() $aTime = _Date_Time_SystemTimeToArray($systemTime) $sTime = StringFormat("%04d/%02d/%02d %02d:%02d:%02d", $aTime[2], $aTime[0], $aTime[1], $aTime[3], $aTime[4], $aTime[5]) $aTime[6] += $medianOffset*1000 + 1 ; 1 millisecond added to compensate time taken by this function If Abs($aTime[6]) >= 1000 Then $sTime = _DateAdd("s", Int($aTime[6]/1000), $sTime) $aTime[6] = $aTime[6]-Int($aTime[6]/1000)*1000 EndIf If $aTime[6] < 0 Then $sTime = _DateAdd("s", -1, $sTime) $aTime[6] += 1000 EndIf $aTime = StringSplit($sTime & StringFormat(".%03d", $aTime[6]), ":./ ", 2) Local $tCurr = _Date_Time_EncodeSystemTime($aTime[1], $aTime[2], $aTime[0], $aTime[3], $aTime[4], $aTime[5], $aTime[6]) ; MsgBox(0,'',TimerDiff($hTimer)) ; 0.5 ~ 1 millisecond ; Exit ; Synchronize system time with current time plus $medianOffset Local $bResult = _Date_Time_SetSystemTime(DllStructGetPtr($tCurr)) If $bResult = False Then $sError = "_Date_Time_SetSystemTime failed" EndFunc ;==>SetTime Func SetSystemTimeAdjustment() ; Local $TZ_Offset = _Date_Time_GetTimeZoneInformation()[1] ; -540 minutes for Korea ; $sTime = _DateAdd("n", -$TZ_Offset, $sTime) & StringFormat(".%03d", $aTime[6]) ; Above $sTime does not take Daylight Saving Time into consideration. $sTime = _NowCalc() $medianOffset = StringFormat("%.3f", $medianOffset) If $medianOffset >= 0 Then $medianOffset = "+" & $medianOffset $medianRoundTrip = StringMid(Round($medianRoundTrip, 3)+100.0001, 2, 6) Local $sLatency = (StringInStr($NTP_Server, "google") ? "GGL" : "NTP") & " Latency: " & $medianRoundTrip & " milliseconds" $sTime &= " Offset: " & $medianOffset & " second(s), " & $sLatency If Not FileExists($logFile) Then FileWriteLine($logFile, _NowCalc() & " Initialized") EndIf While 1 $aLog = FileReadToArray($logFile) If @error = 0 Then ExitLoop WEnd $aInfo = _Date_Time_GetSystemTimeAdjustment() Local $iAdjustment = IniRead($iniFile, "TimeAdjustment", @ComputerName, 0), $sAdjusted If $iAdjustment = 0 Then If $aInfo[0] <> $aInfo[1] Then IniWrite($iniFile, "TimeAdjustment", @ComputerName, $aInfo[0]-$aInfo[1]) EndIf ElseIf $iAdjustment <> $aInfo[0] - $aInfo[1] Then _Date_Time_SetSystemTimeAdjustment($aInfo[1]+$iAdjustment, False) $sAdjusted = "*" & $iAdjustment EndIf ; _ArrayDisplay($aInfo) ; Default values: $aInfo[0]=156250, $aInfo[1]=156250, $aInfo[2]=True ; Increment of 1 in $aInfo[0] value makes system clock 23.04 milliseconds faster in 1 hour, 0.55296 second a day ; Desktop-Main was running slower by about 75 milliseconds per hour, need to speed up => $iAdjustment = 3 While 1 ; To determine if $iAdjustment needs further adjustment If UBound($aLog) < 3 Then ExitLoop ; 2021/12/07 18:55:02.311 Adjustment: -0.005 second(s), NTP Latency: 01.901 milliseconds Local $aNow = StringSplit($sTime, " ") If $aNow[0] <> 9 Then ExitLoop Local $nowTime = $aNow[1] & " " & $aNow[2] Local $nowSign = StringLeft($aNow[4], 1) Local $nowOffset = Abs($aNow[4])*1000 ; in milliseconds Local $nowServer = $aNow[6] Local $sPrev = $aLog[UBound($aLog)-1] ; Time log 1 $syncInterval ago Local $aPrev = StringSplit($sPrev, " ") If $aPrev[0] <> 9 Then ExitLoop Local $prevTime = $aPrev[1] & " " & $aPrev[2] Local $prevSign = StringLeft($aPrev[4], 1) Local $prevOffset = Abs($aPrev[4])*1000 ; in milliseconds Local $prevServer = $aPrev[6] Local $sPrev2 = $aLog[UBound($aLog)-2] ; Time log 1 $syncInterval ago Local $aPrev2 = StringSplit($sPrev2, " ") If $aPrev2[0] <> 9 Then ExitLoop Local $prevTime2 = $aPrev2[1] & " " & $aPrev2[2] Local $prevServer2 = $aPrev2[6] Local $allowedOffset = Int($syncInterval*1000/$aInfo[1]/2)+1 ; 12 milliseconds if $syncInterval is 3600 seconds If $nowSign <> $prevSign Or $nowOffset <= $allowedOffset Or $prevOffset <= $allowedOffset _ Or Abs(_DateDiff("s", $prevTime, $nowTime) - $syncInterval) > 10 _ Or Abs(_DateDiff("s", $prevTime2, $prevTime) - $syncInterval) > 10 _ Or $nowServer <> $prevServer Or $prevServer <> $prevServer2 Then ExitLoop EndIf If $nowSign = "+" Then $iAdjustment += Round($nowOffset/($syncInterval*1000/$aInfo[1])) Else $iAdjustment -= Round($nowOffset/($syncInterval*1000/$aInfo[1])) EndIf _Date_Time_SetSystemTimeAdjustment($aInfo[1]+$iAdjustment, False) ; Result for Desktop-Main: $aInfo[0]=156253, $aInfo[1]=156250, $aInfo[2]=False IniWrite($iniFile, "TimeAdjustment", @ComputerName, $iAdjustment) $sAdjusted = "*" & $iAdjustment ExitLoop WEnd $sTime &= $sAdjusted EndFunc ;===>SetSystemTimeAdjustment Func WriteLog($pTime, $sMessage) If $sMessage <> "" Then $pTime = _NowCalc() & " " & $sMessage EndIf If Not FileExists($logFile) Then FileWriteLine($logFile, _NowCalc() & " Initialized") EndIf While 1 $aLog = FileReadToArray($logFile) If @error = 0 Then ExitLoop WEnd If UBound($aLog) > 199 Then _ArrayDelete($aLog, "0 - " & UBound($aLog)-200) EndIf _ArrayAdd($aLog, $pTime) Local $fn While 1 $fn = FileOpen($logFile, 2) If @error = 0 Then ExitLoop Sleep(10) WEnd _FileWriteFromArray($fn, $aLog) FileClose($fn) EndFunc ;==>WriteLog Func udp_shutdown() UDPShutdown() EndFunc ;==>udp_shutdown Func _DateDiffMS($sType, $sStartDate, $sEndDate) Local $iDiff If $sType = "ms" Or $sType = "s" Then Local $sStartMS, $sEndMS, $iDiffSec If StringInStr($sStartDate, ".") Then $sStartMS = StringMid($sStartDate, StringInStr($sStartDate, "."), 5)*1000 $sStartDate = StringLeft($sStartDate, StringInStr($sStartDate, ".")-1) EndIf If StringInStr($sEndDate, ".") Then $sEndMS = StringMid($sEndDate, StringInStr($sEndDate, "."), 5)*1000 $sEndDate = StringLeft($sEndDate, StringInStr($sEndDate, ".")-1) EndIf $iDiffSec = _DateDiff("s", $sStartDate, $sEndDate) If @error Then Return SetError(@error, 0, 0) $iDiff = $iDiffSec*1000+Round($sEndMS-$sStartMS) If $sType = "s" Then $iDiff = StringFormat("%.3f", $iDiff/1000) EndIf Else If StringInStr($sStartDate, ".") Then $sStartDate = StringLeft($sStartDate, StringInStr($sStartDate, ".")-1) EndIf If StringInStr($sEndDate, ".") Then $sEndDate = StringLeft($sEndDate, StringInStr($sEndDate, ".")-1) EndIf $iDiff = _DateDiff($sType, $sStartDate, $sEndDate) EndIf Return SetError(@error, 0, $iDiff) EndFunc ;===>_DateDiffMS