I'm working on a program that records date metadata from files, such as creation time, last modified time, etc. An old version of the program is written in VBA, and does something like this:
Public Function GetFileLastAccessTime(ByVal FilePath As String) As Date
Dim fso As New Scripting.FileSystemObject
Dim f As Scripting.File
Set f = fso.GetFile(FilePath)
GetFileLastAccessTime = f.DateLastAccessed
End Function
Output for the file in question:
?getfilelastaccesstime("SomePath")
7/30/2010 2:16:07 PM
That is the value I get from the file properties in Windows Exploder. Happiness.
I'm porting this functionality to a VB.Net app. The new code:
Public Function GetLastAccessTime(ByVal FilePath As String) As Date
Return IO.File.GetLastAccessTime(FilePath)
End Function
Simplicity itself. The output:
?GetLastAccessTime("SomePath")
#7/30/2010 3:16:07 PM#
One hour later.
Both functions are running on the same machine, checking the same file. I've also tried using the IO.FileInfo class with the same result. I've checked thousands of files and they are all off by one hour. The other date properties for creation time and last modified time are also off by one hour.
Help!
I forgot to mention in the original post, The computer's time zone is CST, and daylight savings time is currently not in effect.
I've reproduced the problem on Windows 7 64 bit and Windows XP 32 bit.
Thanks.
1/6/2011 update:
Thanks to everyone who suggested trying to calculate the desired date from UTC using the appropriate time zone offsets. At this time I'm deciding its not worth the risk to do so. For this particular business requirement, its much better to say the date value is not what you expected it to be because thats just the way the API works. If I attempt to "fix" this then I own it, and I'd rather not.
Just for kicks I tried using the good old Scripting.FileSystemObject via interop. It gives the expected results that agree with Windows Explorer, with about a 5x performance penalty compared to System.IO. If it turns out I must get dates that match what Windows Explorer has, I will bite the bullet and go this route.
Another experiment I tried was going directly to the GetFileTime API function in kernel32 via C#:
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GetFileTime(
IntPtr hFile,
ref FILETIME lpCreationTime,
ref FILETIME lpLastAccessTime,
ref FILETIME lpLastWriteTime
);
That resulted in exactly the same behavior System.IO had, the time was off by an hour from Windows Explorer.
Thanks again everyone.
I can reproduce this using .NET 4.0 and 2.0/3.5 on XP/Win2k3/Vista.
The issue is DST was in a different state to now, and .NET is processing this difference differently to Explorer and Scripting.FileSystemObject
.
(You can see a similar issue when extracting files from a zip file when DST is different to when the files were zipped.)
Via Reflector, the reason .NET differs from the non .NET code paths is that all three local datestamps in IO.File
(and IO.FileInfo
) are implemented as getting the Utc datestamp and applying .ToLocalTime
, which determines the offset to add based upon whether DST was happening in the local timezone for the Utc time.
I also checked, by changing the date to July 1 last year, creating a file and watching the timestamp when I return to the current date (March 16) (I'm in South Australia, so we're in DST now and were not on July 1), and Windows (and presumably FileSystemObject
) adds the DST in place NOW so the time displayed actually changes.
So, in summary, .NET is more correct.
But if you want the incorrect, but same as Explorer, date use:
Public Function GetLastAccessTime(ByVal FilePath As String) As Date
Return IO.File.GetLastAccessTimeUtc(FilePath) _
.Add(TimeZone.CurrentTimeZone.GetUtcOffset(Now))
End Function
This has been discussed on Raymond Chen's blog (where the summary is: .NET is intuitively correct but not always invertible, and Win32 is strictly correct and invertible).
You can detect the offset using the following code:
'...
Dim datetimeNow as DateTime = DateTime.Now
Dim localOffset as TimeSpan = datetimeNow - now.ToUniversalTime()
'...
Add the localOffset to your time
Daylight savings time was the culprit for me. datDate contained the time I needed to adjust, but simply checking whether "Now()" (or as shown above, no parameter does the same) is DST, this fixed it.
If TimeZone.CurrentTimeZone.IsDaylightSavingTime(Now()) = True Then
datDate = DateAdd(DateInterval.Hour, -1, datDate)
End If
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With