【问题标题】:IO.File.GetLastAccessTime is off by one hourIO.File.GetLastAccessTime 关闭一小时
【发布时间】:2011-01-05 15:45:46
【问题描述】:

我正在开发一个程序,该程序记录文件中的日期元数据,例如创建时间、上次修改时间等。该程序的旧版本是用 VBA 编写的,并且执行以下操作:

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

相关文件的输出:

?getfilelastaccesstime("SomePath")
7/30/2010 2:16:07 PM 

这是我从 Windows Exploder 中的文件属性中获得的值。幸福。

我正在将此功能移植到 VB.Net 应用程序。新代码:

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

简单本身。输出:

?GetLastAccessTime("SomePath")
#7/30/2010 3:16:07 PM#

一小时后。

这两个函数在同一台机器上运行,检查同一个文件。我也尝试过使用具有相同结果的 IO.FileInfo 类。我检查了数千个文件,它们都关闭了一小时。创建时间和上次修改时间的其他日期属性也相差一小时。

救命!

原帖忘记说了,电脑的时区是CST,目前没有夏令时。

我已经在 Windows 7 64 位和 Windows XP 32 位上重现了这个问题。

谢谢。

2011 年 1 月 6 日更新:

感谢所有建议尝试使用适当的时区偏移量从 UTC 计算所需日期的人。在这个时候,我决定这样做不值得冒险。对于这个特定的业务需求,最好说日期值不是您所期望的,因为这正是 API 的工作方式。如果我试图“修复”它,那么我拥有它,我宁愿不这样做。

只是为了好玩,我尝试通过互操作使用旧的 Scripting.FileSystemObject。它给出了与 Windows Explorer 一致的预期结果,与 System.IO 相比,性能损失约为 5 倍。如果事实证明我必须得到与 Windows 资源管理器匹配的日期,我会硬着头皮走这条路。

我尝试的另一个实验是通过 C# 直接进入 kernel32 中的 GetFileTime API 函数:

[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GetFileTime(
IntPtr hFile,
ref FILETIME lpCreationTime,
ref FILETIME lpLastAccessTime,
ref FILETIME lpLastWriteTime
);

这导致 System.IO 的行为完全相同,时间与 Windows 资源管理器相差一个小时。

再次感谢大家。

【问题讨论】:

  • 对我来说似乎是夏令时问题
  • 上次访问时间的粒度约为 1 小时。另外请注意,在 Windows Vista 及更高版本中,默认情况下,Last Access Time value is not updated on NTFS Volumes by default.
  • 夏令时现在没有生效,但是文件最后一次修改时生效的。谁是对的?在 UTC 工作以避免不得不问这个问题。
  • 不幸的是,不能在 UTC 工作,我需要报告其原始时区的日期。
  • 我也想了解为什么 Windows Explorer 和 Scripting.FileSystemObject 在时间上一致,而 IO.File 却不一致。

标签: .net vb.net datetime vba


【解决方案1】:

我可以在 XP/Win2k3/Vista 上使用 .NET 4.0 和 2.0/3.5 重现这一点。

问题是 DST 与现在处于不同状态,.NET 处理这种差异的方式与 Explorer 和 Scripting.FileSystemObject 不同。

(当 DST 与压缩文件时不同时,从 zip 文件中提取文件时,您会看到类似的问题。)

通过 Reflector,.NET 与非 .NET 代码路径不同的原因在于,IO.File(和 IO.FileInfo)中的所有三个本地日期戳都被实现为获取 Utc 日期戳并应用 .ToLocalTime,这决定了要添加的偏移量取决于 DST 是否发生在本地时区 UTC 时间

我还检查了,通过将日期更改为去年的 7 月 1 日,创建一个文件并查看我返回当前日期(3 月 16 日)时的时间戳(我在南澳大利亚,所以我们现在在 DST并且不是在 7 月 1 日),并且 Windows(可能是 FileSystemObject)在 NOW 中添加了 DST,因此显示的时间实际上会发生变化。

所以,总而言之,.NET 更正确。

但是如果你想要不正确的,但和资源管理器一样的,日期使用:

Public Function GetLastAccessTime(ByVal FilePath As String) As Date
    Return IO.File.GetLastAccessTimeUtc(FilePath) _
      .Add(TimeZone.CurrentTimeZone.GetUtcOffset(Now))
End Function 

这已在Raymond Chen's blog 上讨论过(总结是:.NET 直观正确但并不总是可逆,Win32 严格正确且可逆)。

【讨论】:

    【解决方案2】:

    您可以使用以下代码检测偏移量:

    '...
    Dim datetimeNow as DateTime = DateTime.Now
    Dim localOffset as TimeSpan = datetimeNow - now.ToUniversalTime()
    '...
    

    将 localOffset 添加到您的时间

    【讨论】:

      【解决方案3】:

      夏令时是我的罪魁祸首。 datDate 包含了我需要调整的时间,但只是检查“Now()”(或如上所示,没有参数相同)是否为 DST,这已修复它。

      If TimeZone.CurrentTimeZone.IsDaylightSavingTime(Now()) = True Then
          datDate = DateAdd(DateInterval.Hour, -1, datDate)
      End If
      

      【讨论】:

      • 我知道几乎没有其他地方的夏令时偏移量不超过 1 小时,但结合 = True 和我在查看此内容时几乎多次单击 -1...
      【解决方案4】:

      试试GetLastAccessTimeUtc,因为我怀疑这是当地时间和夏令时的问题。

      【讨论】:

        【解决方案5】:

        嗯...我无法在我的 Windows 7 计算机上重现它,但这听起来像是夏令时问题。您可以尝试以下方法:

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

        可能需要试验是否加/减...

        【讨论】:

        • 我忘了在原帖中提到,计算机的时区是CST,目前没有夏令时。谢谢。
        • @Roger - 如果您使用 VB.Net 的 SetLastAccessTime 在文件上手动设置值,然后在 VBA 中读取值,会发生什么情况? VBA 和 Windows 资源管理器显示的时间是否与您在代码中设置的时间相同?可能是一个有趣的测试。
        【解决方案6】:

        如果你使用当前的文化来格式化日期输出,你会得到什么?例如,

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

        【讨论】:

        • 好主意!不幸的是,它返回的时间和以前一样不正确。
        【解决方案7】:

        如前所述,我认为答案是使用 Utc 时间,如果您需要知道“本地”时间,则根据该特定时间点的夏令时计算本地时间当前的文化信息。不是很简单,但至少可以解决?

        【讨论】:

          【解决方案8】:
          Public Function GetLastAccessTime(ByVal FilePath As String) As Date
              Return IO.File.GetLastAccessTime(FilePath).ToLocalTime()
          End Function
          

          【讨论】:

            【解决方案9】:

            VBA 不允许摆弄时区,因此它必须使用来自 Win API 的标准。我建议实验:

            • 转到日期和时间属性,首先确保夏令时已关闭并比较结果
            • 然后将时区更改为几个不同的时区,看看这两个应用程序的行为如何
            • 选择 UTC 时区,看看会发生什么

            你会得到相同的上次访问时间吗?

            【讨论】:

              【解决方案10】:

              我也遇到了这个问题,我相信我明白发生了什么。

              我有一个 Powershell 脚本和一个 CMD 脚本(使用 DIR 命令)报告文件的修改日期/时间。在最近从夏令时更改为标准时间之后,Powershell 脚本将时间更改之前创建的文件的时间报告为比 DIR 命令晚一小时。 Windows 资源管理器与 Powershell 报告的时间相同。

              由于 NTFS 文件时间戳存储为 UTC,因此它们将转换为本地时间以进行显示/输出。似乎 DIR 命令在显示时间时使用了与 UTC 的当前时区偏移(+8,因为我在太平洋时区,现在是标准时间),而 Powershell(和 Windows 资源管理器)正在使用本地时间偏移这在时间戳(创建文件时)有效,即 UTC +7,因为它仍然是夏令时。

              我认为 Powershell 转换是正确的。我想你可以用任何一种方式争论,但是如果你看一下夏令时前后的时间戳,Powershell 结果会是一样的,而 DIR 结果会不同,而且不一致。

              【讨论】:

                猜你喜欢
                • 2023-03-24
                • 2016-08-21
                • 1970-01-01
                • 2017-04-09
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多