Метод IsDayLightSavingTime возвращает разные значения для одного и того же часового пояса и одного и того же времени - PullRequest
0 голосов
/ 26 апреля 2018

Следующий код, который проверяет определенное время в DST или не возвращает разные значения для того же времени с обычным временем и датой, полученным из файлового времени:

var tzInfo = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
var reminderstarttime = new DateTime(2018, 3, 10, 22, 0, 0);
var referencetime = reminderstarttime.AddHours(10);  // ReferencedTime is in DST;

var isRemDstWithNormal = tzInfo.IsDaylightSavingTime(reminderstarttime);
var isRefDstWithNormal = tzInfo.IsDaylightSavingTime(referencetime);

var reminderStartTimeToUtc = (ulong)reminderstarttime.ToFileTimeUtc();
var referenceTimeToUtc = (ulong)referencetime.ToFileTimeUtc();

var reminderStartTimeFromUtc = DateTime.FromFileTimeUtc((long)reminderStartTimeToUtc);
var referencetimeFromUtc = DateTime.FromFileTimeUtc((long)referenceTimeToUtc);

var isRemDSTFromFileTime = tzInfo.IsDaylightSavingTime(reminderStartTimeFromUtc);
var isRefTimeDSTFromFileTime = tzInfo.IsDaylightSavingTime(referencetimeFromUtc);

Console.WriteLine("isRemDstWithNormal: " + isRemDstWithNormal + 
                 " isRefDstWithNormal: " + isRefDstWithNormal + 
                 " isRemDSTFromFileTime " + isRemDSTFromFileTime + 
                 " isRefTimeDSTFromFileTime: " + isRefTimeDSTFromFileTime);

Ответы [ 4 ]

0 голосов
/ 27 апреля 2018

Зоар сделал это в основном правильно.Ключевым моментом является то, что DateTime.ToFileTimeUtc, как и многие методы, работающие с DateTime, зависит от Kind, связанного со значением.Когда передается DateTimeKind.Unspecified, этот конкретный метод предполагает, что ввод был уже в терминах UTC.Однако в вашем коде вы создаете эти значения, как если бы они соответствовали заданному часовому поясу.

Давайте сосредоточимся на виновнике:

var reminderStartTimeToUtc = (ulong)reminderstarttime.ToFileTimeUtc();
var referenceTimeToUtc = (ulong)referencetime.ToFileTimeUtc();

Поскольку оба reminderstarttime и referencetime имеют Kind == DateTimeKind.Unspecified, их итоговые значения времени файла неверны.В частности:

reminderStartTimeToUtc:  131651928000000000
             we wanted:  131652216000000000
            difference:       -288000000000  = -8 hours

    referenceTimeToUtc:  131652288000000000
             we wanted:  131652540000000000
            difference:       -252000000000  = -7 hours

Как видите, их значения отличаются от UTC для каждой соответствующей даты.

Преобразование их обратно в код с помощью DateTime.FromFileTimeUtc возвращает значения, которые do have DateTimeKind.Utc, который отбрасывает последующие проверки DST:

reminderStartTimeFromUtc:  2018-03-10 22:00:00 UTC
  which is equivalent to:  2018-03-10 14:00:00 PST (UTC-8)
               we wanted:  2018-03-10 22:00:00 PST (UTC-8)

    referencetimeFromUtc:  2018-03-11 08:00:00 UTC
  which is equivalent to:  2018-03-11 00:00:00 PST (UTC-8)
               we wanted:  2018-03-11 08:00:00 PDT (UTC-8)

Обратите внимание, что переключение с PST на PDT происходит в 02:00 PST, поэтому оба значения по-прежнему в стандартномвремя.

Так, как мы можем исправить это без взлома?Просто убедившись, что наши входные значения соответствуют DateTimeKind.Utc, перед тем как мы преобразуем время файла Windows.(DateTimeKind.Local также будет работать, но здесь нет необходимости задействовать местный часовой пояс)

// First convert the DateTime values from their unspecified zone-specific times to UTC
var reminderStartTimeUtc = TimeZoneInfo.ConvertTimeToUtc(reminderstarttime, tzInfo);
var referenceTimeUtc = TimeZoneInfo.ConvertTimeToUtc(referencetime, tzInfo);

// Then convert THOSE values to file-times.
var reminderStartTimeToUtc = (ulong)reminderStartTimeUtc.ToFileTimeUtc();
var referenceTimeToUtc = (ulong)referenceTimeUtc.ToFileTimeUtc();

Остальная часть кода будет работать правильно, как есть, и вы получите ожидаемый результат.

Обратите внимание, что формулировка этих методов несколько сбивает с толку.DateTime.ToFileTimeUtc означает, что вы конвертируете время файла, и что ввод DateTime с .Kind == DateTimeKind.Unspecified будет обрабатываться так, как если бы он был DateTimeKind.Utc.Другой метод, DateTime.ToFileTime, обрабатывает Unspecified виды как Local.Но они оба относятся к Utc и Local видам одинаково, и они оба генерируют время файла Windows, которое по своей сути основано на UTC.

В качестве альтернативы вышеприведенному подходу вымог бы использовать DateTimeOffset.ToFileTime вместо этого.Смещение будет корректно учтено при преобразовании во время файла.

// construct a DateTimeOffset for each value
var reminderStartTimeDto = new DateTimeOffset(reminderstarttime, tzInfo.GetUtcOffset(reminderstarttime));
var referencetimeDto = new DateTimeOffset(referencetime, tzInfo.GetUtcOffset(referencetime));

// then just convert them to file times
var reminderStartTimeAsFileTime = reminderStartTimeDto.ToFileTime();
var referenceTimeAsFileTime = referencetimeDto.ToFileTime();

Обратите внимание, что здесь нет ToFileTimeUtc, потому что * DateTimeOffset нет, поэтому есть только один способ

И последнее.Обратите внимание, что DateTime.AddHours(10) не учитывает разрыв DST.Таким образом, в то время как вы говорите о 8:00 по тихоокеанскому времени, было всего 9 фактических часов, из-за разрыва в упругом движении вперед.10 фактических прошедших часов будут 9 утра PDT.Вы можете легко исправить это, если вы сохраните свои значения в виде DateTimeOffset типов до , добавив 10 часов.

0 голосов
/ 26 апреля 2018

Обновление

Несмотря на то, что я все еще придерживаюсь своего объяснения первой версии, преобразование в местное время даст неверные результаты тестирования, если время фактически является экономией летнего времени.Я не уверен, почему именно.Я играл с вашим кодом на rextester, пытаясь найти решение этой проблемы.Лучший из найденных мной довольно громоздкий - он включает создание нового экземпляра DateTime на основе экземпляра DateTime, который вы получаете в результате FromFileTimeUtc:

var tzInfo = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
var reminderstarttime = new DateTime(2018, 3, 10, 22, 0, 0);
var referencetime = reminderstarttime.AddHours(10);  // ReferencedTime is in DST;

var isRemDstWithNormal = tzInfo.IsDaylightSavingTime(reminderstarttime);
var isRefDstWithNormal = tzInfo.IsDaylightSavingTime(referencetime);

var reminderStartTimeToUtc = reminderstarttime.ToFileTimeUtc();
var referenceTimeToUtc = referencetime.ToFileTimeUtc();

var reminderStartTimeFromUtc = DateTime.FromFileTimeUtc(reminderStartTimeToUtc);
var referencetimeFromUtc = DateTime.FromFileTimeUtc(referenceTimeToUtc);

var isRemDSTFromFileTime = tzInfo.IsDaylightSavingTime(reminderStartTimeFromUtc);
var isRefTimeDSTFromFileTime = tzInfo.IsDaylightSavingTime(referencetimeFromUtc);

var referenceTimeFromFileTimeUnspecified = new DateTime(referencetimeFromUtc.Ticks);
var isReferenceTimeFromFileTimeUnspecifiedDTS =  tzInfo.IsDaylightSavingTime(referenceTimeFromFileTimeUnspecified);

Console.WriteLine("isRemDstWithNormal: " + isRemDstWithNormal + 
                 "\nisRefDstWithNormal: " + isRefDstWithNormal + 
                 "\nisRemDSTFromFileTime " + isRemDSTFromFileTime + 
                 "\nisRefTimeDSTFromFileTime: " + isRefTimeDSTFromFileTime +
                 "\nisReferenceTimeFromFileTimeUnspecifiedDTS: "+ isReferenceTimeFromFileTimeUnspecifiedDTS);

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

Первая версия

Проблема в том, что ваше referencetime свойство Kind имеет значение DateTimeKind.Unspecified.Если вы укажете DateTimeKind.Local в конструкторе reminderstarttime, вы получите точные результаты.

См. Раздел Замечания в документации метода DateTime.ToFileTimeUtc:

Метод ToFileTimeUtc использует свойство Kind, чтобы определить, является ли текущий объект DateTime местным временем, временем UTC, или неопределенным временем, которое рассматривается как время UTC. Если это местное время, оно преобразует время в UTC перед выполнением преобразования в время файла Windows.

(выделено мной)

Вот мой тестовый код:

var tzInfo = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
// This is the only change, except replacing the space with \n in the Console.WriteLine:
//var reminderstarttime = new DateTime(2018, 3, 10, 22, 0, 0);
var reminderstarttime = new DateTime(2018, 3, 10, 22, 0, 0, DateTimeKind.Local);
var referencetime = reminderstarttime.AddHours(10);  // ReferencedTime is in DST;

var isRemDstWithNormal = tzInfo.IsDaylightSavingTime(reminderstarttime);
var isRefDstWithNormal = tzInfo.IsDaylightSavingTime(referencetime);

var reminderStartTimeToUtc = reminderstarttime.ToFileTimeUtc();
var referenceTimeToUtc = referencetime.ToFileTimeUtc();

var reminderStartTimeFromUtc = DateTime.FromFileTimeUtc(reminderStartTimeToUtc);
var referencetimeFromUtc = DateTime.FromFileTimeUtc(referenceTimeToUtc);

var isRemDSTFromFileTime = tzInfo.IsDaylightSavingTime(reminderStartTimeFromUtc);
var isRefTimeDSTFromFileTime = tzInfo.IsDaylightSavingTime(referencetimeFromUtc);

Console.WriteLine("isRemDstWithNormal: " + isRemDstWithNormal + 
                 "\nisRefDstWithNormal: " + isRefDstWithNormal + 
                 "\nisRemDSTFromFileTime " + isRemDSTFromFileTime + 
                 "\nisRefTimeDSTFromFileTime: " + isRefTimeDSTFromFileTime);

И результаты:

isRemDstWithNormal: False
isRefDstWithNormal: False
isRemDSTFromFileTime False
isRefTimeDSTFromFileTime: False
0 голосов
/ 26 апреля 2018

referencetimeFromUtc.Kind - это Utc, поэтому его нет в DST.Проверьте источник: https://referencesource.microsoft.com/#mscorlib/system/timezone.cs,ccfb563c9867b5d0

0 голосов
/ 26 апреля 2018

Этот код работает нормально.Это дает этот ответ для перехода на летнее время из-за перехода на летнее время 11 марта 2018 года в 02:00 в США и Канаде.

...