DST Confusion

Last week I posted about some Java alternatives to CFDIRECTORY and ConvertDate. I posted a snippet of code which attempted to show how to get the DateLastModified information for files using Java API calls. One of my readers pointed out that the code I posted may not return the correct time given that I was applying the current DST offset to the lastModified value and not the DST offset in effect when the file was written.

I quickly updated my post to attempt to use the DST offset in effect when the file was modified. However after testing I found that this snippet was not returning the correct lastModified time.

To test I took a directory which had log files named with the date and time they were written. A Windows DIR on the directory resulted in the following:

01/16/2006 03:25 PM   3,214 LOG-0116060325030603PM.txt
02/23/2006 05:27 PM    671 LOG-0223060527350068PM.txt
05/25/2006 03:55 PM    863 LOG-0525060455550105PM.txt
09/30/2006 12:38 AM   4,427 LOG-0930060138240762AM.txt
10/30/2006 10:41 AM   1,493 LOG-1030061041080943AM.txt

Notice that when running this command during EST the file written at 4:55 PM EDT (third in the output above) was now being listed as modified at 3:55 PM. All the files modified during EDT were now showing up as being modified an hour earlier.

A cfdirectory listing of the same directory produced the following output:

These times match the time the file was written, even for files written during daylight saving time.

My original Java example applied the current DST offset to all files in the list.

<cfset jGregorianCalendar = createObject("java","java.util.GregorianCalendar").init() />
<cfset jCalendar = createObject("java","java.util.Calendar") />
<cfset dstOffset = jGregorianCalendar.get(jCalendar.DST_OFFSET) />
<cfset zoneOffset = jGregorianCalendar.get(jCalendar.ZONE_OFFSET) />
<cfset offset = dstOffset + zoneOffset />

<cfset fileList = createObject("java","java.io.File").init("\\somepath\logfiles").listFiles() />
<cfset qDir = queryNew("Name,DateLastModified") />
<cfloop from="1" to="#arrayLen(fileList)#" index="i">
<cfset queryAddRow(qDir) />
<cfset querySetCell(qDir, "Name", fileList[i].getName()) />
<cfset querySetCell(qDir, "DateLastModified", dateAdd('s', (fileList[i].lastModified() + offset)/1000, 'January 1 1970 00:00:00')) />
</cfloop>

<cfdump var="#qDir#">

This code produced the following results, which seemed to match the cfdirectory output.

My second Java example attempted to apply the DST offset in effect when the file was written.

<cfset jGregorianCalendar = createObject("java","java.util.GregorianCalendar").init() />
<cfset jCalendar = createObject("java","java.util.Calendar") />

<cfset fileList = createObject("java","java.io.File").init("\\somepath\logfiles").listFiles() />
<cfset qDir = queryNew("Name,DateLastModified") />
<cfloop from="1" to="#arrayLen(fileList)#" index="i">
<cfset jGregorianCalendar.setTimeInMillis(fileList[i].lastModified()) />
<cfset dstOffset = jGregorianCalendar.get(jCalendar.DST_OFFSET) />
<cfset zoneOffset = jGregorianCalendar.get(jCalendar.ZONE_OFFSET) />
<cfset offset = dstOffset + zoneOffset />
<cfset queryAddRow(qDir) />
<cfset querySetCell(qDir, "Name", fileList[i].getName()) />
<cfset querySetCell(qDir, "DateLastModified", dateAdd('s', (fileList[i].lastModified() + offset)/1000, 'January 1 1970 00:00:00')) />
</cfloop>

<cfdump var="#qDir#">

It produced the following results, which listed the date modified time as one hour later for files written during daylight saving time.

So to summarize, given a file written at 9:00 AM EDT, a call to windows DIR during EST would list the lastModified time as 8:00 AM while my first Java snippet would return 9:00 AM and my second example would return 10:00 AM. What was going on here?

Well, it turns out that Windows (at least Windows XP on a NTFS formatted disk) stores the lastModified time as UTC. For display purposes Windows converts the UTC time to local time using the current DST offest. So if you save a file at 9:00 AM EDT that will get saved by the file system as 13:00 UTC. Later, when you view that file in the Windows Explorer during EST it shows up as being modified at 8:00 AM, 13:00 UTC minus the current 5 hour offset for standard time. It does this for a couple of reasons, one of which it that is nearly impossible to figure out what daylight saving time rules may have been in effect given that the laws governing daylight saving time are always changing. This explains the values returned by the DIR call in the earlier example, but what about the times returned by the Java API calls?

To try to figure this out I put together yet another Java snippet:

<cfset jGregorianCalendar = createObject("java","java.util.GregorianCalendar").init() />
<cfset jCalendar = createObject("java","java.util.Calendar") />

<cfset fileList = createObject("java","java.io.File").init("\\somepath\logfiles").listFiles() />
<cfset qDir = queryNew("Name,DateLastModified,ZoneOffset,DSTOffset,TotalOffset") />
<cfloop from="1" to="#arrayLen(fileList)#" index="i">
   
<cfset jGregorianCalendar.setTimeInMillis(fileList[i].lastModified()) />
<cfset dstOffset = jGregorianCalendar.get(jCalendar.DST_OFFSET)/1000/60/60 />
<cfset zoneOffset = jGregorianCalendar.get(jCalendar.ZONE_OFFSET)/1000/60/60 />
<cfset totalOffset = dstOffset + zoneOffset />

<cfset queryAddRow(qDir) />
<cfset querySetCell(qDir, "Name", fileList[i].getName()) />
<cfset querySetCell(qDir, "DateLastModified", dateAdd('s', fileList[i].lastModified()/1000, 'January 1 1970 00:00:00')) />
<cfset querySetCell(qDir, "ZoneOffset", zoneOffset) />
<cfset querySetCell(qDir, "DSTOffset", dstOffset) />
<cfset querySetCell(qDir, "TotalOffset", totalOffset) />

</cfloop>

<cfdump var="#qDir#">

This gave me the following results.

Notice that for the times with a DST offest of 1 hour the offset was added to the lastModified time returned from listFiles(). This explains why my first Java example was returning times that matched the file name time, but why was Java doing this? Because I had selected "Automatically adjust clock for daylight saving changes" in the Windows Data and Time Properties. After un-checking this option and restarting ColdFusion I got the following results.

If I add the current zone offset to these times I get times which match the Windows DIR a Explorer listings.

So if you want your lastModified times to match the times listed in Windows, either disable "Automatically adjust clock for daylight saving changes" and apply the zone offset, or if you have automatic adjustment enabled subtract the DST offset in effect when the file was modified from the lastModified time before applying the zone offset. If you want to attempt to list the time modified with the DST offset in effect when the file was written you can enable the automatic adjustment and subtract the zone offset.

Related Blog Entries

Comments
BlogCFC was created by Raymond Camden. This blog is running version 5.8.001.