开发者

IO.File.GetLastAccessTime is off by one hour

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 开发者_JAVA百科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


Try GetLastAccessTimeUtc as I suspect this is an issue of local time and daylight savings.


Well... I can't reproduce that on my Windows 7 computer, but it sounds like a Day light savings issue. You might try something like:

    Dim dt As DateTime = System.IO.File.GetLastAccessTime(FilePath)
    If Not dt.IsDaylightSavingTime() Then
        dt.AddHours(1)
    End If

Experimentation may be required as to whether to add/subtract...


What do you get if you use the current culture to format the date output?, e.g.,

Dim dt As DateTime = System.IO.File.GetLastAccessTime(FilePath)
Dim dateText As String = dt.ToString(System.Globalization.CultureInfo.CurrentCulture)


As already mentioned I think the answer to this is to use the Utc time, and if you need to know what time that is 'locally' work out the local time based on the daylight savings time at that particular point in time using the current culture information. Not quite trivial but at least a work around?


Public Function GetLastAccessTime(ByVal FilePath As String) As Date
    Return IO.File.GetLastAccessTime(FilePath).ToLocalTime()
End Function


VBA does not allow to fiddle with timezones so it must be using something standard from Win API. I would suggest experiment:

  • go to Date & Time properties and first make sure Daylight Saving Time is off and compare results
  • then change timezone to several different ones and see how both applications behave
  • select UTC timezone and see what happens

Will you ever get the same last accessed time?


I encountered this problem as well, and I believe I understand what is happening.

I have a Powershell script and a CMD script (using the DIR command) that reports the modified date/time of files. After the recent change from Daylight to Standard time, the Powershell script was reporting the time of files created before the time change as one hour later than the DIR command. Windows Explorer reported the same time as Powershell.

Since NTFS file timestamps are stored as UTC, they are convered to local time for display/output. It appears that the DIR command uses the current time zone offset from UTC when displaying the time (+8, since I'm in Pacific time zone and it's now standard time), while Powershell (and Windows Explorer) is using the local time offset THAT WAS IN EFFECT at the time of the timestamp (when the files were created), which was UTC +7, since it was still daylight time.

I think the Powershell conversion is correct. I suppose you can argue either way, but if you look at the timestamp before and after a daylight time change, Powershell results would be the same, while DIR results would be different, and inconsistent.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜