Mr. Cluey : How To ...? : What Time is It?


How do I change the system clock under Win32?

The clumsy and painful way to change the clock is to use the Date/Time applet in the control panel. Much faster, and much neater, is to use the Win32 API directly.

To find out which API function you want, grab the nearest copy of Dan Appleman's Visual Basic Programmer's Guide to the Win32 API. Although targeted towards programmers familiar with Visual Basic, it is very easy to translate the various calls into their 4Test equivalents.

In this case, we need to wrap SetSystemTime(), which lives inside of Kernel32. The function takes one parameter, a pointer to a SYSTEMTIME structure. The equivalent of a structure in 4Test is a record, and Partner knows that these are to be passed by pointer.

type WORD is short ;

type SYSTEMTIME is record
{
	WORD wYear;
	WORD wMonth; 
	WORD wDayOfWeek;
	WORD wDay;
	WORD wHour;
	WORD wMinute;            
	WORD wSecond;     
	WORD wMilliseconds;
} ; 

dll "kernel32.dll"
{
	LONG SetSystemTime( in SYSTEMTIME rTime ) ;
}

SetSystemTime returns a LONG. If the value returned is zero, the function failed. Rather than forcing programmers to check this value each time, we can check it for ourselves, and raise an exception if anything goes amiss.

To do this, rather than telling everybody to use SetSystemTime, we could tell them to use some wrapper function. A more elegant solution is to wrap SetSystemTime around itself.

4Test allows you to alias dll functions. Using aliases makes it easy to change the actual function that you call without changing any of the calls. This can be particularly important to windows programmers (especially those dealing with ANSI and UNICODE libraries), and we can pervert that technique to fit our needs.

Consider this dll wrapper

dll "kernel32.dll"
{
	LONG _SetSystemTime( in SYSTEMTIME rTime ) alias "SetSystemTime";
}

The alias tells Partner what function to call (this should be the name of the function that you are actually trying to call. The function name is what you use in your scripts. So now, you can create your own SetSystemTime function for everybody to use

private dll "kernel32.dll"
{
	LONG _SetSystemTime( in SYSTEMTIME rTime ) alias "SetSystemTime" ;
}

SetSystemTime ( SYSTEMTIME rTime )
{
	if ( 0 == _SetSystemTime( rTime ) )
	{
		raise 123, "SetSystemTime Failed!!!!" ;
	}
}

By declaring the dll wrapper, we prevent any users shooting themselves in the foot by using the dll call directly. The SetSystemTime we expose (remember, we used the alias to hide the real SetSystemTime) has the error checking built into it automatically.

You could also change the interface to SetSystemTime, but this is a bad idea - why not take give people an easy way to learn what the windows API is like? What would be a good idea, however, is to create a function with a different name that accepts, for example, a month, day, and year; you can thus insulate your users from the SYSTEMTIME structure.

What about distributed testing?

No problem: DLL calls go through the agent; if you set the runtime option "print agent calls" you will see that the agent is calling your aliased DLL method. Most of the time, that will be exactly what you want - when it isn't, you can direct the agent to connect to the local machine instead.

Are we home free?

Not quite. You had better remember to put the clock back where it belongs if you want Partner's timing reports to be reasonably accurate. I suspect that most folks are going to be interested primarily in Y2K testing. If you are one, don't play with the time of day - let the hours, minutes, and seconds just keep on ticking. This way, you can just set the calendar back to where it should be, and the elapsed time will still be correct.

To do this, you will also want to wrap the GetSystemTime function. Not only will this tell you when the test started, but it will also fill in all of the data fields for you, so that you need only change those that are part of your test.

The private keyword we put on the dll wrapper we used before will also make the GetSystemTime function private. We could wrap it like the other, but in this case, to make it clear that we aren't doing anything to it, we should instead put it into a DLL block of its own.

dll "kernel32.dll"
{
	GetSystemTime( inout SYSTEMTIME rTime ) ;
}

Note that the rTime parameter is inout here, because the system is writing data into the structure for us.

Once you have GetSystemTime in place, you should ensure that the TestcaseEnter and TestcaseExit events save and restore the current time (don't make that the programmers job if you can possibly help it).

Final Gotchyas

If you are running the test at midnight (on the machine where the agent is running, remember), you had best be certain that you advance the calendar to Today rather than yesterday "Hey, the test took negative time to run! We might actually get all of the testing in before we ship this time!"

The easiest way to do this is to check the "current" time. If the current day is still the one you set the clock to, you are safe to restore the original time. Otherwise, you have to advance the calendar by a day to make sure you got it right. This gets gritty when you start considering the test at midnight of New Year's Eve.

Don't use SetSystemTime from two different threads. It's a long walk back from the parellel time streams. Remember to pack a lunch.

Oh yes, various services (for instance, segue's licensing service), will no doubt become unhappy if they happen to check their watches while you are time traveling. Software licenses tend to expire if you jump about carelessly, especially if you come back before you left.


Mr. Cluey : How To ...? : What Time is It?

Return to ATS Automated Testing Resources Page