Jump to content

Recommended Posts

Posted (edited)
  On 10/28/2019 at 10:20 AM, CYCho said:

@TheXman After several hours of googling, I came up with the following code for calculating fractions of NTP time in millisecs. Your review and comment will be appreciated.

$iMilliSecs = Int(Dec(Hex(BinaryMid($xResponse, 45, 4)), $NUMBER_64BIT)/2^32*1000)
$iMilliSecs = StringFormat("%03i", $iMilliSecs)

 

Expand  

Very nice! :thumbsup: 

It looks almost exactly like the solution that I came up with.  The only difference between yours and mine is that I round the milliseconds.

$iSeconds  = Dec(Hex(BinaryMid($xResponse, 41, 4)), $NUMBER_64BIT) ; seconds since 1900-01-01 00:00:00
$iFraction = Dec(Hex(BinaryMid($xResponse, 45, 4)), $NUMBER_64BIT) ; picosec = 1 trillionth of a second (10^-12)
$iMsecs    = Round($iFraction / 2^32 * 1000)                       ; round to the nearest integer

My full script is below:

  Reveal hidden contents

 

Edited by TheXman
Posted
  On 10/28/2019 at 7:51 PM, CYCho said:

@TheXman Thank you. I am glad that I did it. The last 2 days were an intensive course of learning for me and I enjoyed it. I appreciate your help.

 

Expand  

You're welcome! :thumbsup:

Feel free to reach out to me directly if you ever run into any AutoIt obstacles in the future.  :bye:

Posted (edited)

Using @TheXman's UDF to get time from NTP server, I completely revamped my original code as below. I don't know the real benefit of doing it, but the code now synchronizes computer time to millisec level. According to my experience, Google time server produced far less timed out errors than other servers. I didn't have to worry about timezone offset because Windows takes care of it.

;~ Many thanks to @TheXman for his kind guidance.
;~ https://www.autoitscript.com/forum/topic/200643-pulling-time-from-ntp-server/?do=findComment&comment=1439629

#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_AU3Check_Parameters=-w 3 -w 4 -w 5 -w 6 -d
#AutoIt3Wrapper_Outfile=NTP_Time_V4.exe
#AutoIt3Wrapper_Res_Fileversion=4.0
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****

#RequireAdmin
#include <Constants.au3>
#include <Date.au3>

Global $hTimeoutTimer = TimerInit()
While 1
    If Ping("www.google.com") > 0 Then ExitLoop
    If TimerDiff($hTimeoutTimer) > 120000 Then
        WriteLog(0, "System has no internet connection")
        Exit
    EndIf
    Sleep(50)
WEnd

Global $NTP_Server = 'time.google.com', $NTP_Time

NTP_GetTime()

System_SetTime()


Func System_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 $mi = StringMid($NTP_Time, 15, 2)
    Local $s = StringMid($NTP_Time, 18, 2)
    Local $ms = StringMid($NTP_Time, 21, 3)
    ;~ Sets the new current time to the computer
    Local $tCurr = _Date_Time_EncodeSystemTime($m, $d, $y, $h, $mi, $s, $ms)
    Local $tTime = _Date_Time_GetSystemTime()
    _Date_Time_SetSystemTime(DllStructGetPtr($tCurr))

    Local $aTime = _Date_Time_SystemTimeToArray($tTime)
    WriteLog($aTime, "Update Time: " & $NTP_Time & " UTC")
EndFunc ;==>System_SetTime

Func NTP_GetTime()
    Local $tBuffer
    Local $aSocket
    Local $xRequest = Binary(""), $xResponse = Binary(""), $hTimeoutTimer, $hEchoTimer, $iDelay
    Local $iSeconds= 0, $iFractions = 0, $iMsecs = 0

    ; Create the NTP request using a 48 byte buffer
    $tBuffer = DllStructCreate("byte[48]")
    DllStructSetData($tBuffer, 1, 0x23) ; 00 100 011 = LI(0) / VN(4-NTPv4) / Mode(3-Client)

    ; Copy the buffer to a variable for sending
    $xRequest = DllStructGetData($tBuffer, 1)

    ; Send NTP request
    UDPStartup()
    If @error Then
        WriteLog(0, "UDPStartup failed - @error = " & @error)
        Exit
    EndIf
    OnAutoItExitRegister("udp_shutdown")

    $aSocket = UDPOpen(TCPNameToIP($NTP_Server), 123)
    If @error Then
        WriteLog(0, "UDPOpen failed - @error = " & @error)
        Exit
    EndIf
    Sleep(50)

    $hTimeoutTimer = TimerInit()
    While 1
        $hEchoTimer = TimerInit()
        UDPSend($aSocket, $xRequest)
        If @error Then
            WriteLog(0, "UDPSend failed - @error = " & @error)
            Exit
        EndIf

        Do
            $xResponse = UDPRecv($aSocket, DllStructGetSize($tBuffer), $UDP_DATA_BINARY)
            If @error Then
                WriteLog(0, "UDPRecv failed - @error = " & @error)
                Exit
            EndIf
        Until $xResponse <> Binary("")
        $iDelay = Round(TimerDiff($hEchoTimer)/2)
        If $iDelay < 100 Then
            ExitLoop
        EndIf
        If TimerDiff($hTimeoutTimer) > 120000 Then ; If no response within 2 seconds, then exit
            WriteLog(0, "UDPRecv timed out")
            Exit
        EndIf
        Sleep(100)
    WEnd

    ; Close the socket
    UDPCloseSocket($aSocket)

    ; Parse timestamp values
    ; - Current time is calculated from the xmit timestamp in NTP response header
    ; - Xmit timestamp seconds is a big-endian, uint32, at binary position 41
    ; - Xmit timestamp fraction is a big-endian, uint32, at binary position 45
    $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) + $iDelay ; It normally takes about 50 milsecs to get NTP time
    If $iMsecs >= 1000 Then
        $iSeconds += 1
        $iMsecs -= 1000
    EndIf

    ; Current NTP time
    $NTP_Time = _DateAdd("s", $iSeconds, "1900/01/01 00:00:00") & StringFormat(".%03i", $iMsecs)
EndFunc   ;==>NTP_GetTime

Func udp_shutdown()
    UDPShutdown()
EndFunc   ;==>udp_shutdown

Func WriteLog($pTime, $sMessage)
    Local $fn, $sTime
    If $pTime = 0 Then
        $fn = FileOpen(@ScriptDir & "\TimeSync Failed.log", 2)
        $sTime = _NowCalc()
    Else
        $fn = FileOpen(@ScriptDir & "\TimeSync.log", 2)
        FileDelete(@ScriptDir & "\TimeSync Failed.log")
        $sTime = StringFormat("System Time: %04d/%02d/%02d %02d:%02d:%02d.%03d UTC", $pTime[2], $pTime[0], $pTime[1], $pTime[3], $pTime[4], $pTime[5], $pTime[6])
    EndIf
    FileWrite($fn, $sTime & @CRLF & $sMessage)
    FileClose($fn)
EndFunc   ;==>WriteLog

 

Edited by CYCho
Posted (edited)

All of the variables that you have defined that are not inside of a function are global in scope, even if you define them as Local.  So all of the Local vars at the top of your script should actually be defined as Global. 

Change your au3 parameters to the following and you will see the warnings:

#AutoIt3Wrapper_AU3Check_Parameters=-w 3 -w 4 -w 5 -w 6 -d

 

Edited by TheXman
  • 2 weeks later...
Posted

Did you look here:

?

 

Signature beginning:
Please remember: "AutoIt"..... *  Wondering who uses AutoIt and what it can be used for ? * Forum Rules *
ADO.au3 UDF * POP3.au3 UDF * XML.au3 UDF * IE on Windows 11 * How to ask ChatGPT for AutoIt Codefor other useful stuff click the following button:

  Reveal hidden contents

Signature last update: 2023-04-24

Posted (edited)

@mLipok I compared the performance of 2 different approaches to get NTP server time.

Approach #1:

#include <Date.au3>
#include <StructureConstants.au3>
#include <WinAPIMisc.au3>

$tFT = _NTP_FT('pool.ntp.org')
For $i = 1 To 10
    $hTimer = TimerInit()
    $tFT = _NTP_FT('pool.ntp.org')
    ConsoleWrite(_Date_Time_FileTimeToStr($tFT) & " - " & TimerDiff($hTimer) & @CRLF)
Next

Func _NTP_FT($sServer, $fLocal = True)

    Local $tNTP = DllStructCreate('byte Header[4];byte RootDelay[4];byte RootDispersion[4];byte ReferenceIdentifier[4];byte ReferenceTimestamp[8];byte OriginateTimestamp[8];byte ReceiveTimestamp[8];byte TransmitTimestamp[8];byte KeyIdentifier[4];byte MessageDigest[16]')
    Local $tPacket = DllStructCreate('byte Packet[68]', DllStructGetPtr($tNTP))
    Local $bPacket = 0, $tFT, $tQW, $aSocket, $aResult

;~  0x1B000000 = 00011011 00000000 00000000 00000000b (LI = 00b VN = 011b Mode = 011b Stratum = 00000000b Poll = 00000000b Precision = 00000000b)
    $tNTP.Header = Binary('0x1B000000')
    UDPStartup()
    If @error Then
        Return SetError(1, 0, 0)
    EndIf
    $aSocket = UDPOpen(TCPNameToIP($sServer), 123)
    If @error Then
        ; Nothing
    Else
        UDPSend($aSocket, $tPacket.Packet)
        If @error Then
            ; Nothing
        Else
            While 1
                $bPacket = UDPRecv($aSocket, 68, 1)
                If (@error) Or ($bPacket) Then
                    ExitLoop
                EndIf
                Sleep(100)
            WEnd
        EndIf
    EndIf
    UDPCloseSocket($aSocket)
    UDPShutdown()
    If Not $bPacket Then
        Return SetError(2, 0, 0)
    EndIf

    $tFT = DllStructCreate($tagFILETIME)
    If $fLocal Then
        $tQW = DllStructCreate('uint64 Timestamp')
    Else
        $tQW = DllStructCreate('uint64 Timestamp', DllStructGetPtr($tFT))
    EndIf
    $tPacket.Packet = $bPacket
    $tQW.Timestamp = _WinAPI_SwapDWord(DllStructGetData(DllStructCreate('uint', DllStructGetPtr($tNTP, 'TransmitTimestamp')), 1)) * 10000000 + 94354848000000000
    If $fLocal Then
        $aResult = DllCall('kernel32.dll', 'bool', 'FileTimeToLocalFileTime', 'struct*', $tQW, "struct*", $tFT)
        If (@error) Or (Not $aResult[0]) Then
            Return SetError(3, 0, 0)
        EndIf
    EndIf
    Return SetError(0, 0, $tFT)
EndFunc    ;==>_NTP_FT

The reault:

12/06/2019 22:29:06 - 50.5885
12/06/2019 22:29:06 - 48.0765
12/06/2019 22:29:06 - 52.2912
12/06/2019 22:29:06 - 46.8422
12/06/2019 22:29:06 - 42.2072
12/06/2019 22:29:07 - 44.1788
12/06/2019 22:29:07 - 43.5889
12/06/2019 22:29:07 - 42.9144
12/06/2019 22:29:07 - 45.8183

Approach #2

#include <Constants.au3>
#include <Date.au3>

Global $NTP_Server = 'pool.ntp.org', $NTP_Time

NTP_GetTime()
For $i = 1 To 10
    $tTimer = TimerInit()
    NTP_GetTime()
    ConsoleWrite($NTP_Time & " - " & TimerDiff($tTimer) & @CRLF)
Next


Func NTP_GetTime()
    Local $tBuffer
    Local $aSocket
    Local $xRequest = Binary(""), $xResponse = Binary(""), $hTimeoutTimer, $hEchoTimer
    Local $iSeconds= 0, $iFractions = 0, $iMsecs = 0

    ; Create the NTP request using a 48 byte buffer
    $tBuffer = DllStructCreate("byte[48]")
    DllStructSetData($tBuffer, 1, 0x23) ; 00 100 011 = LI(0) / VN(4-NTPv4) / Mode(3-Client)

    ; Copy the buffer to a variable for sending
    $xRequest = DllStructGetData($tBuffer, 1)

    ; Send NTP request
    UDPStartup()
    If @error Then
        ConsoleWrite("UDPStartup failed - @error = " & @error)
        Exit
    EndIf
    OnAutoItExitRegister("udp_shutdown")

    $aSocket = UDPOpen(TCPNameToIP($NTP_Server), 123)
    If @error Then
        ConsoleWrite("UDPOpen failed - @error = " & @error)
        Exit
    EndIf

    $hEchoTimer = TimerInit()
    UDPSend($aSocket, $xRequest)
    If @error Then
        ConsoleWrite("UDPSend failed - @error = " & @error)
        Exit
    EndIf

    Do
        $xResponse = UDPRecv($aSocket, DllStructGetSize($tBuffer), $UDP_DATA_BINARY)
        If @error Then
            ConsoleWrite("UDPRecv failed - @error = " & @error)
            Exit
        EndIf
    Until $xResponse <> Binary("")
    $iDelay = TimerDiff($hEchoTimer)
    ; Close the socket
    UDPCloseSocket($aSocket)

    ; Parse timestamp values
    ; - Current time is calculated from the xmit timestamp in NTP response header
    ; - Xmit timestamp seconds is a big-endian, uint32, at binary position 41
    ; - Xmit timestamp fraction is a big-endian, uint32, at binary position 45
    $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) + Round($iDelay/2)
    If $iMsecs >= 1000 Then
        $iSeconds += 1
        $iMsecs -= 1000
    EndIf

    ; Current NTP time
    $NTP_Time = _DateAdd("s", $iSeconds, "1900/01/01 00:00:00")
    $NTP_Time = _DateAdd("h", 9, $NTP_Time) & StringFormat(".%03i", $iMsecs)
EndFunc   ;==>NTP_GetTime

Func udp_shutdown()
    UDPShutdown()
EndFunc   ;==>udp_shutdown

The result:

2019/12/06 22:51:24.255 - 5.5304
2019/12/06 22:51:24.260 - 4.3448
2019/12/06 22:51:24.264 - 3.9764
2019/12/06 22:51:24.268 - 4.0598
2019/12/06 22:51:24.272 - 3.9179
2019/12/06 22:51:24.277 - 5.038
2019/12/06 22:51:24.281 - 5.3846
2019/12/06 22:51:24.287 - 4.7938
2019/12/06 22:51:24.293 - 6.4303
2019/12/06 22:51:24.298 - 5.383

I don't understand what makes this large difference in terms of time taken for each iteration. For unknow reason, the frst iteration always produced an outlying result, so I left that out.

Edited by CYCho
  • 2 weeks later...
Posted (edited)

In connection with this topic a need arose to compare two time strings in milliseconds. As _DateDiff function does not support millisecond data type, I came up with a UDF based on the original _DateDiff function.

#include <Date.au3>

$sDate1 = "2019/12/19 23:16:24"
$sDate2 = "2019/12/20 23:16:24.02"

ConsoleWrite("Difference in seconds: " & _DateDiffEx("s", $sDate1, $sDate2) & @CRLF)
ConsoleWrite("Difference in milliseconds: " & _DateDiffEx("ms", $sDate1, $sDate2) & @CRLF)


Func _DateDiffEx($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

 

Edited by CYCho
  • 2 months later...
Posted (edited)

With this code running in my desktop, I found that my computer needed an average upward adjustment of 1.8 seconds a day. Even though I can schedule this code to run as often as possible to forcefully adjust the system time at short intervals, I was wondering if there was a way to let the system clock run a little bit faster to keep better time by itself. And I found _Date_Time_SetSystemTimeAdjustment() function and included the following lines at the end of the code. The result is very satisfactory: it now lags less than a quarter of a second a day.

$aInfo = _Date_Time_GetSystemTimeAdjustment()
$iAdjustment = $aInfo[1] + 3    ; 156250 + 3 : The increment of 1 in $iAdjustment value represents 0.55296 second a day.
_Date_Time_SetSystemTimeAdjustment($iAdjustment, False)

 

Edited by CYCho

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...