[AccessD] One millisecond resolution (was: Classes and Events - EVENTS NOT REQUIRED)

Gustav Brock Gustav at cactus.dk
Wed Feb 11 04:24:04 CST 2009


Hi Terry

Your programmers may have to look a bit deeper into this.
While I have no reason to doubt the articles of the links from Stuart and John on the hardware limitations, Windows XP behaves a bit clever than stated.
Stuart mentions the API call timeBeginPeriod but doesn't show how to use it which is as simple as can be (more on this later).

If you are really interested in the millisecond business, not only for timing purposes but also to obtain absolute time, I can recommend the article here:

  http://www.devx.com/dbzone/Article/39046 

A free registration is required to access the page and download.

If you download the code with a bunch of functions, you'll find these two:

  Msec()
  FormatMsec()

The first returns the current time with a resolution of 1 ms and an accuracy of something close.
The next formats a date/time value with milliseconds for you to read as VB(A) rounds milliseconds to the second when displaying date/time values.

If you create a very simple loop, you are able to create a list of milliseconds from time values with a difference less than 1 ms as the code runs in about 0.5 ms per loop:

For n = 0 to 10 : ? FormatMsec(Msec,"i") : Next
.996
.997
.997
.998
.998
.998
.999
.999
.000
.001
.001

To obtain this, timeBeginPeriod is called as shown here:

<code>
Public Function Msec( _
  Optional ByVal intTimePart As Integer) _
  As Date

' Generates the current time with millisecond resolution.
'
' Returns current (local) date/time including millisecond.
' Parameter intTimePart determines level of returned value:
'   0: Millisecond value only.
'   1: Time value only including milliseconds.
'   2: Full Date/time value including milliseconds.
'   None or any other value: Millisecond value only.
'
' 2007-06-05. Cactus Data ApS. CPH.

  Const cintMsecOnly            As Integer = 0
  Const cintMsecTime            As Integer = 1
  Const cintMsecDate            As Integer = 2
  
  Static typTime      As SYSTEMTIME
  Static lngMsecInit  As Long

  Dim datMsec         As Date
  Dim datDate         As Date
  Dim intMilliseconds As Integer
  Dim lngTimeZoneBias As Long
  Dim lngMsec         As Long
  Dim lngMsecCurrent  As Long
  Dim lngMsecOffset   As Long
  
  ' Set resolution of timer to 1 ms.
  timeBeginPeriod 1
  lngMsecCurrent = timeGetTime()
  
  If lngMsecInit = 0 Or lngMsecCurrent < lngMsecInit Then
    ' Initialize.
    ' Get bias for local time zone respecting
    ' current setting for daylight savings.
    lngTimeZoneBias = GetLocalTimeZoneBias(False)
    ' Get current UTC system time.
    Call GetSystemTime(typTime)
    intMilliseconds = typTime.wMilliseconds
    ' Repeat until GetSystemTime retrieves next count of milliseconds.
    ' Then retrieve and store count of milliseconds from launch.
    Do
      Call GetSystemTime(typTime)
    Loop Until typTime.wMilliseconds <> intMilliseconds
    lngMsecInit = timeGetTime()
    ' Adjust UTC to local system time by correcting for time zone bias.
    typTime.wMinute = typTime.wMinute - lngTimeZoneBias
    ' Note: typTime may now contain an invalid (zero or negative) minute count.
    ' However, the minute count is acceptable by TimeSerial().
  Else
    ' Retrieve offset from initial time to current time.
    lngMsecOffset = lngMsecCurrent - lngMsecInit
  End If
  
  With typTime
    ' Now, current system time is initial system time corrected for
    ' time zone bias.
    lngMsec = (.wMilliseconds + lngMsecOffset)
    Select Case intTimePart
      Case cintMsecTime, cintMsecDate
        ' Calculate the time to add as a date/time value with millisecond resolution.
        datMsec = lngMsec / 1000 / clngSecondsPerDay
        ' Add to this the current system time.
        datDate = datMsec + TimeSerial(.wHour, .wMinute, .wSecond)
        If intTimePart = cintMsecDate Then
          ' Add to this the current system date.
          datDate = datDate + DateSerial(.wYear, .wMonth, .wDay)
        End If
      Case Else
        ' Calculate millisecond part as a date/time value with millisecond resolution.
        datMsec = (lngMsec Mod 1000) / 1000 / clngSecondsPerDay
        ' Return millisecond part only.
        datDate = datMsec
    End Select
  End With

  Msec = datDate
  
End Function
</code>

As you can see, the function "initiallizes" by running a loop to "sync" the absolute time of a lower 10 ms resolution with the timer with the 1 ms resolution.
This is, by the way, a perfect example of something that could be moved to the Initialize method of a class but, for the purpose the functions originally were developed, this was not practical.

To run the code you'll need some constants and declarations. These are listed in the module for download to be found here:

  http://assets.devx.com/sourcecode/AccessMillisecond.ZIP 

/gustav


>>> terry.mace at baesystems.com 11-02-2009 05:35 >>>
John,

When I run this I get times of 15,31,16,31,16, 109 or some numbers very
close to these. Programmers I work with (I'm not one) tell me that while
the tick count may be a 1mS interval, Windows XP cannot display time at
anywhere near this resolution and effectively time samples, hence the
results I'm getting.

This operation of Windows (inability to have time to less than 10mS)
prevents them from having a Windows based system for their application.

Regards
 
Terry Mace
Logistics Support Officer & Maintenance Supervisor





More information about the AccessD mailing list