2015-03-13 4 views
3

Я боюсь, что я действительно не понимаю, как класс .Net DateTime обрабатывает локальные метки времени (я живу в Германии, поэтому мой язык - de_DE). Возможно, кто-то может немного просветить меня ;-).Net DateTime с местным временем и DST

Конструктор DateTime можно вызвать с помощью параметров года, месяца и т. Д. Дополнительно может быть предоставлено значение DateTimeKindLocal, Utc или Unspecified (= по умолчанию).

Пример:

DateTime a = new DateTime(2015, 03, 29, 02, 30, 00, DateTimeKind.Local); 
DateTime b = new DateTime(2015, 03, 29, 02, 30, 00, DateTimeKind.Utc); 
DateTime c = new DateTime(2015, 03, 29, 02, 30, 00, DateTimeKind.Unspecified); 
DateTime d = new DateTime(2015, 03, 29, 02, 30, 00); 

По определению, значения с и d являются идентичными. Но если я сравниваю все друг с другом, все четыре идентичны. Проверка объектов в отладчике VS показывает, что значение TicksInternalTicks) одинаково для всех. Однако внутренние значения dateData различны, но оператор сравнения явно игнорируется.

Как вы могли заметить, я построил стоимость 29 марта этого года, 02:30. Этот момент времени не существует в нашем часовом поясе, поскольку он пропускается, переключаясь на летнее время. Поэтому я ожидал получить исключение для построения объекта a, но этого не произошло.

Кроме того, DateTime имеет способ ToUniversalTime(), который преобразует значение, которое интерпретируется как локальное время для эквивалентного значения UTC. Для тестирования я побежал петлю следующим образом:

DateTime dt = new DateTime(2015, 03, 29, 01, 58, 00, DateTimeKind.Local); 
DateTime dtEnd = new DateTime(2015, 03, 29, 03, 03, 00, DateTimeKind.Local); 
while (dt < dtEnd) 
{ 
    Log(" Localtime " + dt + " converted to UTC is " + dt.ToUniversalTime()); 
    dt = dt.AddMinutes(1); 
} 

Результат:

Localtime 29.03.2015 01:58:00 converted to UTC is 29.03.2015 00:58:00 
Localtime 29.03.2015 01:59:00 converted to UTC is 29.03.2015 00:59:00 
Localtime 29.03.2015 02:00:00 converted to UTC is 29.03.2015 01:00:00 
Localtime 29.03.2015 02:01:00 converted to UTC is 29.03.2015 01:01:00 
Localtime 29.03.2015 02:02:00 converted to UTC is 29.03.2015 01:02:00 
... 
Localtime 29.03.2015 02:58:00 converted to UTC is 29.03.2015 01:58:00 
Localtime 29.03.2015 02:59:00 converted to UTC is 29.03.2015 01:59:00 
Localtime 29.03.2015 03:00:00 converted to UTC is 29.03.2015 01:00:00 
Localtime 29.03.2015 03:01:00 converted to UTC is 29.03.2015 01:01:00 
Localtime 29.03.2015 03:02:00 converted to UTC is 29.03.2015 01:02:00 

Итак, .Net не имеет никаких проблем преобразования несуществующие временные метки из местного времени в UTC. Кроме того, добавление минуты к существующей локальной временной отметке не является локальным и дает несуществующую временную метку.

В результате добавление 64-минутных минут дает после преобразования временную метку UTC, которая на 4 минуты больше, чем раньше.

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

Чтобы сократить длинный рассказ: как я правильно обрабатываю предполагаемый путь (согласно .Net)? В чем смысл DateTimeKind, если он не принят правильно? Я даже не осмелился спросить, как обрабатываются секундные секунды (в 23:59:60) ;-)

+6

Здесь Джон Скит должен прыгать и давать диссертацию с использованием [Noda Time] (http://blog.nodatime.org/2011/08/what-wrong-with-datetime-anyway.html) – paqogomez

+1

Hmya , почему бы вам использовать время, которое на самом деле никогда не происходило на практике? Вы осуществляете GIGO, вывоз мусора. Не удивляйся, что ты мусор. Строго используйте UTC, если вам нравится, что ваш код действует предсказуемо, а только конвертируйте его в локальное время в самый последний момент, как раз перед тем, как человек посмотрит на него. –

+0

В соответствующей заметке Джон действительно упоминает в этой статье, что мысль о прыжковых секундах заставляет его плакать. Таким образом, вы не одиноки. :) – paqogomez

ответ

5

ответ Майка хорошо. Да, DateTimeOffset почти всегда предпочитают более DateTime (но не для всех сценариев), а Noda Time значительно превосходит по многим параметрам. Однако я могу добавить несколько подробностей для решения ваших вопросов и наблюдений.

Во-первых, MSDN has this to say:

время UTC подходит для вычислений, сравнения и хранения даты и времени в файлах. Местное время подходит для отображения в пользовательских интерфейсах настольных приложений. Приложения с поддержкой часовых поясов (например, многие веб-приложения) также должны работать с рядом других часовых поясов. Операции

...

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

Из этого можно сделать вывод, что указанный вами тест недействителен, поскольку он выполняет вычисления с использованием местного времени. Это полезно только в том, что в нем подчеркивается, как API позволяет вам нарушать собственные документированные рекомендации. В общем, поскольку с 02:00 до 03:00 не существует в локальном часовом поясе в эту дату, это вряд ли встретится в реальном мире, если оно не будет получено математически, например, путем ежедневного повторения который не учитывал DST.

BTV, часть Noda времени, который обращается к этому является ZoneLocalMappingResolver, который используется при преобразовании LocalDateTime к ZonedDateTime с помощью метода localDateTime.InZone. Существуют некоторые разумные значения по умолчанию, такие как InZoneStrictly, или InZoneLeniently, но это не просто сдвинуто, как показано на рисунке DateTime.

Что касается Вашего утверждения:

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

На самом деле, это не биекция. (К the definition of bijection on Wikipedia, он не удовлетворяет критериям 3 или 4.) Функция является только преобразованием в UTC-локальном направлении. Преобразование в направлении локального к UTC имеет разрыв во время перехода DST с переходом вперед и имеет двусмысленность во время переходного периода DST. Вы можете просмотреть графики in the DST tag wiki.

Чтобы ответить на конкретные вопросы:

Как справиться с этим правильно предполагаемым путем (в соответствии с .Net)?

DateTime dt = new DateTime(2015, 03, 29, 01, 58, 00, DateTimeKind.Local); 
DateTime dtEnd = new DateTime(2015, 03, 29, 03, 03, 00, DateTimeKind.Local); 

// I'm putting this here in case you want to work with a different time zone 
TimeZoneInfo tz = TimeZoneInfo.Local; // you would change this variable here 

// Create DateTimeOffset wrappers so the offset doesn't get lost 
DateTimeOffset dto = new DateTimeOffset(dt, tz.GetUtcOffset(dt)); 
DateTimeOffset dtoEnd = new DateTimeOffset(dtEnd, tz.GetUtcOffset(dtEnd)); 

// Or, if you're only going to work with the local time zone, you can use 
// this constructor, which assumes TimeZoneInfo.Local 
//DateTimeOffset dto = new DateTimeOffset(dt); 
//DateTimeOffset dtoEnd = new DateTimeOffset(dtEnd); 

while (dto < dtoEnd) 
{ 
    Log(" Localtime " + dto + " converted to UTC is " + dto.ToUniversalTime()); 

    // Math with DateTimeOffset is safe in instantaneous time, 
    // but it might not leave you at the desired offset by local time. 
    dto = dto.AddMinutes(1); 

    // The offset might have changed in the local zone. 
    // Adjust it by either of the following (with identical effect). 
    dto = TimeZoneInfo.ConvertTime(dto, tz); 
    //dto = dto.ToOffset(tz.GetUtcOffset(dto)); 
} 

Какой смысл иметь DateTimeKind, если оно не учитывается правильно?

Первоначально, DateTime не имели вид. Он вел себя так, как будто этот вид был неуказан. DateTimeKind был добавлен в .NET 2.0.

Основной защитный чехол, используемый для предотвращения двойного преобразования. Например:

DateTime result = DateTime.UtcNow.ToUniversalTime(); 

или

DateTime result = DateTime.Now.ToLocalTime(); 

Перед .NET 2.0, они будут как результат плохих данных, так как ToUniversalTime и ToLocalTime методы должны были сделать предположение о том, что входное значение было не преобразованный. Он будет слепо применять смещение часового пояса, даже если значение было уже в желательном часовом поясе.

Есть еще несколько случаев кромки, но это главный. Кроме того, существует скрытый четвертый вид, который используется таким образом, что в течение переходного периода все равно будут сохраняться неоднозначные значения.

DateTime now = DateTime.Now; 
Assert.True(now.ToUniversalTime().ToLocalTime() == now); 

Джон Скит имеет a good blog post about this, и вы также можете теперь видеть, что это обсуждается в комментариях в the .NET Reference sources или в the new coreclr sources.

Я даже не смею спросить, как високосные секунды (в 23:59:60) обрабатываются ;-)

високосные секунды на самом деле не поддерживается .NET на всех, в том числе текущая версия Noda Time. Они также не поддерживаются ни одним из Win32 API, и вы никогда не заметите скачок секунды на часах Windows.

В Windows скачки секунд применяются через синхронизацию NTP. Часы тикают так, как если бы второй прыжок не происходил, а во время следующей синхронизации часов время корректируется и поглощается. Вот что следующий скачок второй будет выглядеть следующим образом:

Real World    Windows 
-------------------- -------------------- 
2015-06-30T23:59:58Z 2015-06-30T23:59:58Z 
2015-06-30T23:59:59Z 2015-06-30T23:59:59Z 
2015-06-30T23:59:60Z 2015-07-01T00:00:00Z <-- one sec behind 
2015-07-01T00:00:00Z 2015-07-01T00:00:01Z 
2015-07-01T00:00:01Z 2015-07-01T00:00:02Z 
2015-07-01T00:00:02Z 2015-07-01T00:00:02Z <-- NTP sync 
2015-07-01T00:00:03Z 2015-07-01T00:00:03Z 

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

Кроме того, приведенная выше диаграмма не совсем точна. Я продемонстрировал жесткий сдвиг в секундах, но на самом деле ОС часто вводит незначительные корректировки, распространяя эффект изменения в несколько подсеточных приращений в течение более длительного периода в несколько секунд (несколько миллисекунд за раз).

На уровне API ни один из API не будет поддерживать более 59 секунд в поле. Если они были, чтобы поддержать его вообще, это, вероятно, было бы просто во время разбора.

DateTime.Parse("2015-06-30T23:59:60Z") 

Это вызовет исключение.Если бы были для работы, ему пришлось бы выполнить дополнительный прыжок в секунду и либо вернуться в предыдущую секунду (2015-06-30T23:59:59Z), либо в следующую секунду (2015-07-01T00:00:00Z).

+0

Большое спасибо за ваши проницательные и подробные объяснения. Тема остается сложной, но интересной :-) – Stefan

3

Да, тип DateTime в .NET - главный беспорядок, как вы можете наблюдать, потому что он не поддерживает концепции временных зон, различные календари и много других полезных концепций, таких как интервалы и т.д.

немного лучше это DateTimeOffset тип, который добавляет временную зону смещения информации. DateTimeOffset позволит вам более точно представлять время, которое вы показываете в своем вопросе, и в сравнении учитывается смещение часового пояса. Но этот тип тоже не идеален. Он по-прежнему не поддерживает настоящую информацию о часовом поясе, а только смещение. Таким образом, невозможно выполнить сложные вычисления DST или поддерживать расширенные календари.

Для гораздо более тщательного решения вы можете использовать NodaTime

+0

Исходя из Unix и Python, я привык иметь довольно солидный API с датой и временем, всегда внутренне работая с тиками UTC и имея надежное преобразование между универсальным и локальным временем ввода/вывода. Мой вопрос был результатом оценки возможностей .Net в этом отношении ... Спасибо, Майк, а также @paqogomez за то, что указали мне на Noda Time – Stefan

+0

. Добро пожаловать. Рад, что ты нашел это полезным! –

Смежные вопросы