A Brief History of Time (and Date)

BillSoo
08-08-2002, 01:25 PM
As VB programmers, we have access to a wide range of functions to measure time. There are so many in fact, that at times it can be difficult to determine which one to use. Here is a brief tutorial on using some common functions.

Macro Time (time intervals of 1 second or larger)
A common question is what is the date or time right now? To answer that, VB has 3 functions available. The Date function returns the current date, the Time function returns the current time and the Now function returns the current time and date.

Actually, all three return time/date because all three return a variable of type Date. In previous versions of VB, there was no Date type. Instead, the functions returned a Double. Today, you can still treat the Date Type as if it were a double.

The Date type is an 8 byte double precision floating point number representing the number of days since midnight, Dec 30, 1899. The fractional part of the date represents the time in days. So 12 noon would be half a day, or 0.5.

For instance if the time/date now were Aug 8, 2002 6:00 PM, the values of the three functions would be:
Now() = 37476.75
Date() = 37476
Time() = 0.75

There are a number of functions like DateAdd or DateDiff that return a date after adding or subtracting other dates. Well, in some cases, you can do that directly. For instance, instead of

t = DateAdd("d",7,Now()) 'add one week to current date/time

you could simply say

t = Now() + 7 'add one week to current date/time


Similarily, you can add/subtract time if you keep in mind that time is in fractional days. There are 24 hrs in a day, 1440 minutes in a day and 86400 seconds in a day so to add, say, 10 seconds to a current time, you would use:

t = Now() + 10/86400 'convert 10 seconds to days


Other functions you might use are time conversion functions like Hour, Minute, Day, WeekDay, Month or Year. These functions all return the specified portion of the supplied date. eg. Year(now()) would return the current year (2002).

There is also the Format function, with support for various kinds of date formats, but that is somewhat outside the bounds of this tutorial.

Micro Time (time intervals less than 1 second)
The other end of the spectrum is measuring very fast events and there are a number of ways to do that.

Most newbies start by using the Timer control. This control repeatedly fires an event after a set number of milliseconds. However, despite the seemingly high resolution, the actual resolution is typically much worse than a millisecond. The Timer control sets it's time by the internal tick counter. On Win9x and lower machines, this counter fires around 18 times per second, so the resolution is about 55 ms. On Win2K and up, the tick counter fires faster so the resolution is a finer 10 ms or so.

Similarily, the Timer function, which returns the number of seconds since midnight, also uses the tick count internally, so it's resolution is about 55 (or 10) ms as well.

Higher resolution requires API calls. So let's take a look at some of the variety available.

GetTickCount - ms since system booted. Restricted to resolution of Tick counter
Sleep - halts app for specified number of ms
timeGetTime - returns system time in ms
QueryPerformanceCounter - returns current high performance count value
QueryPerformanceFrequency - returns number of counts per second

GetTickCount has the same resolution problems as Timer() and the timer control. But it may have less overhead so it could be useful.

Sleep has high resolution, but it will freeze your app while it waits. You have a bit more versatility with TimeGetTime or the other functions.

timeGetTime is OS dependant. On Win9x systems, its resolution is 1ms. On WinNT and higher, it's resolution is adjustable by using the timeBeginPeriod and timeEndPeriod API functions. Default resolution on NT is probably around 10ms.

QueryPerformanceCounter is also system dependant in that faster CPUs will cycle the counter faster than slow CPUs. That's why we need QueryPerformanceFrequency to determine the actual frequency for a particular system.

Some old systems do not support a high performance frequency counter. Some REALLY old systems do not support multimedia at all. So in theory, use QueryPerformanceCounter unless it isn't supported, in which case you can use timeGetTime, unless it isn't supported in which case you can use GetTickCount.

Oddball Time systems
There are a number of other time formats supported by windows. One is SystemTime and another is FileTime. SystemTime is a large structure that contains individual elements for day, year, month, hour, millisecond etc. FileTime is another structure that contains the number of 100 nano second periods since Jan 1, 1601. It is unlikely that will have to use these structures, but if you do, use MSDN.

BillSoo
11-11-2003, 03:36 PM
QueryPerformanceCounter and QueryPerformanceFrequency are the best way to go for the highest resolution, but they might be a bit difficult to implement. For one thing, they use a LONG_INTEGER structure to store time...

Declare Function QueryPerformanceCounter Lib "kernel32" Alias "QueryPerformanceCounter" (lpPerformanceCount As LARGE_INTEGER) As Long
Declare Function QueryPerformanceFrequency Lib "kernel32" Alias "QueryPerformanceFrequency" (lpFrequency As LARGE_INTEGER) As Long

This structure is composed of two longs. So far, so good, but ADDING or SUBTRACTING these structures is very convoluted.

Instead, we can simplify things immensely by "cheating" a bit and use a Currency variable instead of the LARGE_INTEGER

Private Declare Function QueryPerformanceCounter Lib "kernel32" (lpPerformanceCount As Currency) As Long
Private Declare Function QueryPerformanceFrequency Lib "kernel32" (lpFrequency As Currency) As Long

So now the values are stored in the 64bit currency variable and we can add/subtract just fine. There is a slight drawback in that Currency is a Fixed Decimal format. Thus the values we get are scaled by 1/10000.

QueryPerformanceFrequency returns the frequency of the timer in counts per second. So it might be something like:
3579545 (eg. each count takes less than 1/3 of a microsecond)
but since we are using a Currency variable, what we see is:
357.9545
We *could* simply multiply the value by 10000 to get the real frequency, but in most cases, this isn't necessary since the scaling factor tends to cancel itself out. For example, here is a simple delay function:


Private Sub WaitMs(Byval ms as long) 'wait a given number of milliseconds
Dim t as Currency
Dim f as currency
Dim e as Currency
QueryPerformanceFrequency f 'get number of counts/second
t = f * ms/1000# 'multiply f by number of seconds to get number of counts to wait
QueryPerformanceCounter e 'get current count number
e = e + t 'add number of counts to wait to current count
Do
QueryPerformanceCounter t
If t > e then exit do 'wait for current count to exceed e
DoEvents
Loop
End Sub


This works fine since both e and t are scaled by 1/10000 so they can be compared just fine.

Since the frequency can't be changed while the system is running, you could simply determine f once at the beginning of your project, then save it for later use. In addition, adding a DoEvents in the main loop may not be the most effecient thing to do since it takes a long time to execute. For ms resolution it isn't too bad, but if you want to time really fast events (microsecond resolution), then you may want to do away with DoEvents.

EZ Archive Ads Plugin for vBulletin Copyright 2006 Computer Help Forum