[ Go to October 1997 Table of Contents ]

Web Programming /
Martin Heller

Of Dates and Times

Last month, when referring to the Windows 95 version-checker Web page we're building, I explained how to determine file versions from an ActiveX object.

As usual, I wasn't done when I thought I was.

It turns out that the version resources in Windows 95 DLLs are inconsistent. In some cases, several versions of a file with different dates and different contents have the same internal version number. In other cases, version numbers are not kept in order. When that happens, it's important for a version-checking utility to be able to retrieve the file's size, date and time.

I thought adding that feature would be trivial. I was wrong.

It's true that actually determining the information is trivial: Win32 has GetFileTime and GetFileSize functions that will dig out the information you need. The problem is converting the time to a format that OLE and ActiveX can use, which in turn will work well with VBScript and JavaScript.

GetFileTime fills a FILETIME structure-a 64-bit value representing the number of 100-nanosecond intervals since Jan. 1, 1601. (I'm sure that choice of starting date made sense to someone.) File time-stamps are given in Coordinated Universal Time (UTC), formerly called Greenwich Mean Time. So the first step in making sense of the file time is to convert the date to the local time zone and then to system time (which is always given in local time)

Win32 has functions for those steps, too: FileTimeToLocalFileTime and FileTimeToSystemTime. The latter fills a SYSTEMTIME structure, which represents the year, month, day of week, day, hour, minute, second and millisecond as individual WORD values. We now have the time zone corrected, the 1/1/1601 base date removed and all the individual values set, so we're ready to convert to the OLE date and time format. Right? Wrong.

This is the fun part: The OLE DATE type is an 8-byte floating-point number. The days are represented as whole-number increments starting with midnight on Dec. 30, 1899 as time zero (another starting date that must have made sense to someone, once). Hour values are expressed as the absolute value of the fractional part of the number. So Jan. 4, 1900 at noon is represented as 5.5.

I was absolutely sure there would be an OLE function to convert a SYSTEMTIME to a DATE. If there is one, though, it escaped my search. So I started to write such a function, and quickly found myself tearing my hair out over minutiae like leap years, days in months and that 1899 start date.

I thought about this a little more, searched for a Microsoft Foundation Class (MFC) method to do the job and found the COleDateTime class. COleDateTime uses the DATE representation internally, and includes constructors that convert from both FILETIME and SYSTEMTIME structures. However, I was explicitly not using MFC to create my component because of the large size of the MFC libraries.

So I dug up the source code for the COleDateTime class.

The COleDateTime constructor that converts from FILETIMEs relies on the constructor that converts from SYSTEMTIMEs, using the UTC/local time/system time-conversion sequence just as we've described it. I hacked and slashed the MFC source code a bit, and came up with the OleDateFromFT and OleDateFromTm functions (see sidebar "Set a Date")

I used these functions to write a four-line get function for a new FileTime property in my ActiveX object, and could retrieve file time stamps from VBScript almost as though

the capability were built into the language. Amazingly, the function worked correctly the first time. That's one big advantage of stealing previously tested code.

I'd like to send a big orchid (or perhaps a six-pack of beer and a pizza would be more appreciated) to whoever wrote the MFC COleDateTime class. I'd also like to send onions to the individual geniuses who created six incompatible date/time formats for Windows programmers: the OLE DATE format, the Windows SYSTEMTIME structure, the Windows FILETIME structure, the C time_t type, and the MS-DOS date and time formats.

At any rate, you'll find the full source code for the wmVer component, including the date conversion code discussed here, on my Web page at http://www.winmag.com/people/mheller/. And you'll find the version checking utility page at http://www.winmag.com/windows.

Dynamic HTML

Dynamic HTML (DHTML) is the most exciting new Web-authoring technology to emerge in the last few months. I don't mean Netscape's rather limited implementation of DHTML, which uses JavaScript-accessible style sheets, layers and TrueDoc dynamic fonts. I mean Microsoft's full Document Object Model implementation of DHTML in Internet Explorer (IE) 4.0.

Microsoft's DHTML makes every element on a Web page accessible to scripts, no matter when the scripts run. In IE 3.0, you could generate HTML from scripts, but only "in-line" scripts, which run before HTML is sent to the browser. In IE 4.0, you can modify document and element styles, textual content, and HTML content even after the page has been displayed.

IE 4.0 also supports cascading style sheet (CSS) positioning. CSS lets you position every element relatively or absolutely on the page; you can also set each element's Z-order to control which element is on top when there is overlap. CSS positioning can be modified from scripts, so you can easily program sprite animation in DHTML. Finally, DHTML includes data binding, so it's no longer necessary to do all your database formatting on the Web server. DHTML supports data-aware "repeating" tables, which bind to server data sources but format the query results locally. That means the user can change the display order of a product catalog obtained from your Web page as easily as he can change the display order of a directory on his hard disk. No new database query is required. Now that's dynamic.

Set a Date
Here's how to convert date formats with OleDateFromFT and OleDateFromTm functions.

static int rgMonthDays[13] = {0, 31, 59, 90, 120, 151, 
181, 212, 243, 273, 304, 334, 365}; 
BOOL OleDateFromTm(WORD wYear, WORD wMonth, WORD wDay, 
WORD wHour, WORD wMinute, WORD wSecond, DATE& dtDest) 
// Validate year and month (ignore day of week) 
	if (wYear  9999 || wMonth< 1 || wMonth >12) 
		return FALSE; 
// Check for leap year and set # of days in the month 
	BOOL bLeapYear = ((wYear & 3) == 0) && 
		((wYear % 100) != 0 || (wYear % 400) == 0) 
	int nDaysInMonth = 
		rgMonthDays[wMonth] - rgMonthDays[wMonth-1] + 
		((bLeapYear && wDay == 29 && wMonth == 2) ? 
		1 : 0) 
// Finish validating the date 
	if (wDay< 1 || wDay >nDaysInMonth || 
		wHour  23 || wMinute  59 || 
		wSecond  59){return FALSE;} 
// Cache the date in days and time in fractional days 
	long nDate; 
	double dblTime; 
//It is a valid date; make Jan 1, 1AD be 1 
	nDate = wYear*365L + wYear/4 - wYear/100 + 
		wYear/400 + rgMonthDays[wMonth-1] + wDay; 
// If leap year and it's before March, subtract 1: 
	if (wMonth<= 2 && bLeapYear) -nDate;
// Offset so that 12/30/1899 is 0 
	nDate -= 693959L; 
	dblTime = (((long)wHour * 3600L) + // hrs in seconds 
		((long)wMinute * 60L) + // mins in seconds 
		((long)wSecond)) / 86400.; 
	dtDest = (double) nDate + ((nDate = 0) ? 
		dblTime : -dblTime) 
	return TRUE; 
DATE OleDateFromFT(FILETIME& filetimeSrc){ 
	// Assume UTC FILETIME, so convert to LOCALTIME 
	FILETIME filetimeLocal; 
	DATE dt=0; 
	if (FileTimeToLocalFileTime( &filetimeSrc,
		SYSTEMTIME systime; 
		FileTimeToSystemTime(&filetimeLocal, &systime) 
		OleDateFromTm(systime.wYear, systime.wMonth, 
			systime.wDay, systime.wHour, systime.wMinute, 
			systime.wSecond, dt) 
	return dt; 

Got the Time?
These functions retrieve a file's date, time, and size.

FILETIME FileCreateTime,FileAccessTime,FileWriteTime;
HANDLE fh=CreateFile(szPath,GENERIC_READ, 
	&FileWriteTime);	//use FileWriteTime
DWORD FileSize=GetFileSize(fh,NULL);

Senior contributing editor Martin Heller programs and writes about programming from Andover, Mass. Contact Martin at http://www.winmag.com/people/mheller/ or care of the Contact Lou care of the editor or the addresses on page 20.

Windows Magazine, October 1997, page 289.

[ Go to October 1997 Table of Contents ]