#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_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 Global $NTP_Time, $iSeconds, $iFractions, $iMsecs, $hRoundTrip, $sError, $aSocket, $success, $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() GetTime() UDPShutdown() If $sError = "" Then SetTime() If $sError = "" Then SetSystemTimeAdjustment() EndIf EndIf WriteLog($NTP_Time, $sError) Func GetTime() For $i = 1 To 10 $NTP_IP = TCPNameToIP($NTP_Server) If $NTP_IP <> "" Then NTP_GetTime() If $NTP_Time <> "" Then Return EndIf EndIf Sleep(500) Next $sError = "" $NTP_Server = "pool.ntp.org" ; This had the shortest response time, mostly below 5 milliseconds, but with frequent outliers. For $i = 1 To 10 $NTP_IP = TCPNameToIP($NTP_Server) If $NTP_IP <> "" Then NTP_GetTime() If $NTP_Time <> "" Then Return EndIf EndIf Sleep(500) Next EndFunc ;===>GetTime Func NTP_GetTime() $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 Do $xResponse = UDPRecv($aSocket, DllStructGetSize($tBuffer), $UDP_DATA_BINARY) If @error Then $sError = "UDPRecv failed" UDPCloseSocket($aSocket) Return EndIf Until $xResponse <> "" $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 + 1.5) ; 1.5 milliseconds added to allow for time taken by this code 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") EndFunc ;==>NTP_GetTime Func SetTime() ; 2019/10/28 23:09:52.522 ; 12345678901234567890123 Local $m = StringMid($NTP_Time, 6, 2) Local $d = StringMid($NTP_Time, 9, 2) Local $y = StringMid($NTP_Time, 1, 4) Local $h = StringMid($NTP_Time, 12, 2) Local $min = StringMid($NTP_Time, 15, 2) Local $s = StringMid($NTP_Time, 18, 2) Local $tCurr = _Date_Time_EncodeSystemTime($m, $d, $y, $h, $min, $s, $iMsecs) ; stores the current system time for calculation of $iAdjustment in SetSystemTimeAdjustment() function $systemTime = _Date_Time_GetSystemTime() ; Synchronize system time with NTP time Local $bResult = _Date_Time_SetSystemTime(DllStructGetPtr($tCurr)) If $bResult = False Then $sError = "_Date_Time_SetSystemTime failed" EndFunc ;==>SetTime Func SetSystemTimeAdjustment() $aInfo = _Date_Time_GetTimeZoneInformation() Local $TZ_Offset = $aInfo[1] $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]) $sTime = _DateAdd("n", -$TZ_Offset, $sTime) & StringFormat(".%03d", $aTime[6]) $NTP_Time = _DateAdd("n", -$TZ_Offset, $NTP_Time) & "." & $iMsecs Local $hOffset = _DateDiffMS("s", $sTime, $NTP_Time) If $hOffset = 0 Then $hOffset = "+0.000" ElseIf $hOffset > 0 Then $hOffset = "+" & $hOffset EndIf $hRoundTrip = StringMid(Round($hRoundTrip, 3)+100.0001, 2, 6) Local $sLatency = (StringInStr($NTP_Server, "google") ? "GGL" : "NTP") & " Latency: " & $hRoundTrip & " milliseconds" $NTP_Time &= " Offset: " & $hOffset & " 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($NTP_Time, " ") 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 $NTP_Time &= $sAdjusted EndFunc ;===>SetSystemTimeAdjustment Func WriteLog($pTime, $sMessage) If $pTime = "" 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