IO.File.GetLastAccessTime отключен на один час

Я работаю над программой, которая записывает метаданные даты из файлов, такие как время создания, время последнего изменения и т.д. Старая версия программы написана в 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 с тем же результатом. Я проверил тысячи файлов, и все они отключены на один час. Другие свойства даты для времени создания и последнего измененного времени также отключены на один час.

Help!

Я забыл упомянуть в исходном сообщении, часовой пояс компьютера - это CST, а летнее время в настоящее время не действует.

Я воспроизвел проблему на 64-разрядной версии Windows 7 и 32-разрядной версии Windows XP.

Спасибо.

Обновление

1/6/2011:

Спасибо всем, кто предложил рассчитать желаемую дату из UTC, используя соответствующие смещения часового пояса. В это время я решаю, что не стоит этого рисковать. Для этого конкретного бизнес-требования гораздо лучше сказать, что значение даты не то, что вы ожидали от него, потому что это так, как работает API. Если я попытаюсь "исправить" это, тогда я его получу, и я предпочел бы этого не делать.

Просто для ударов я попытался использовать старый добрый Scripting.FileSystemObject через interop. Он дает ожидаемые результаты, которые согласуются с Windows Explorer, с примерно 5-кратным снижением производительности по сравнению с System.IO. Если окажется, что я должен установить даты, которые соответствуют тому, что есть в Проводнике Windows, я буду укусить пулю и пойти по этому маршруту.

Другой эксперимент, который я попробовал, прямо перешел к функции API GetFileTime в kernel32 через С#:

[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

Я могу воспроизвести это с помощью .NET 4.0 и 2.0/3.5 на XP/Win2k3/Vista.

Проблема заключается в том, что DST находится в другом состоянии, и .NET обрабатывает эту разницу по-разному для Explorer и Scripting.FileSystemObject.

(Вы можете увидеть подобную проблему при извлечении файлов из zip файла, когда DST отличается от того, когда файлы были заархивированы.)

Через Reflector причина, по которой .NET отличается от кодов нечетного кода .NET, заключается в том, что все три локальные datestamps в IO.FileIO.FileInfo) реализованы как получение датстопа Utc и применение .ToLocalTime, которое определяет смещение для добавления на основе того, происходит ли DST в локальном часовом поясе для времени Utc.

Я также проверил, изменив дату на 1 июля прошлого года, создав файл и посмотрев метку времени, когда вернусь к текущей дате (16 марта) (я нахожусь в Южной Австралии, поэтому мы сейчас в DST и не были 1 июля), а Windows (и предположительно FileSystemObject) добавляет DST на место СЕЙЧАС, поэтому отображаемое время действительно изменяется.

Итак, в итоге,.NET вернее.

Но если вы хотите ввести неверный, но такой же, как Explorer, используйте дату:

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

Об этом говорилось в блоге Raymond Chen (где резюме:.NET интуитивно корректно, но не всегда обратимо, а Win32 строго правильный и обратимый).

Ответ 2

Летнее время было для меня виновником. datDate содержало время, которое мне нужно было настроить, но просто проверяя, является ли "Now()" (или, как показано выше, ни один параметр) DST, это фиксировало его.

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

Ответ 3

Попробуйте GetLastAccessTimeUtc, поскольку я подозреваю, что это проблема экономии локального времени и летнего времени.

Ответ 4

Ну... я не могу воспроизвести это на своем компьютере под управлением Windows 7, но это похоже на проблему экономии дневного света. Вы можете попробовать что-то вроде:

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

Может потребоваться эксперимент по добавлению/вычитанию...

Ответ 5

Что вы получаете, если используете текущую культуру для форматирования вывода даты?, например,

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

Ответ 6

Как уже упоминалось, я думаю, что ответ на этот вопрос заключается в использовании времени Utc, и если вам нужно знать, какое время "локально" работает локально, исходя из летнего времени в тот конкретный момент времени, используя текущую информацию о культуре. Не совсем тривиально, но, по крайней мере, работа вокруг?

Ответ 7

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

Ответ 8

Вы можете обнаружить смещение, используя следующий код:

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

Добавьте localOffset к вашему времени

Ответ 9

VBA не позволяет возиться с часовыми поясами, поэтому он должен использовать что-то стандартное из Win API. Я бы предложил эксперимент:

  • перейдите в свойства "Дата и время" и сначала убедитесь, что "Летнее время" выключено и сравнивает результаты.
  • затем измените часовой пояс на несколько разных и посмотрите, как работают оба приложения.
  • выберите часовой пояс UTC и посмотрите, что произойдет

Получите ли вы время последнего доступа?

Ответ 10

Я тоже столкнулся с этой проблемой, и я считаю, что понимаю, что происходит.

У меня есть Powershell script и CMD script (с использованием команды DIR), которая сообщает измененную дату/время файлов. После недавнего перехода с дневного на стандартное время Powershell script сообщал время создания файлов до изменения времени на один час позже, чем команда DIR. Проводник Windows сообщал то же самое время, что и Powershell.

Поскольку временные метки файлов NTFS хранятся в формате UTC, они преобразуются в локальное время для отображения/вывода. Похоже, что команда DIR использует текущее смещение часового пояса от UTC при отображении времени (+8, так как я в Тихоокеанском часовом поясе, и теперь это стандартное время), в то время как Powershell (и Windows Explorer) использует локальное смещение по времени ЧТО БЫЛО В ЭФФЕКТЕ во время отметки времени (когда файлы были созданы), что было UTC +7, так как это было еще дневное время.

Я думаю, что преобразование Powershell верное. Я полагаю, вы можете спорить в любом случае, но если вы посмотрите на метку времени до и после изменения дневного времени, результаты Powershell будут одинаковыми, тогда как результаты DIR будут разными и непоследовательными.