2013-06-04 2 views
2

В следующем фрагменте кода есть свойство round-trip, которое, как я утверждать, гарантированно удерживается для любого значения DateTime?Имеет ли DateTime roundtrip относительно ToUniversalTime и ToLocalTime?

DateTime input = GetAnyDateTime(); 
DateTime roundtripped = input.ToUniversalTime().ToLocalTime(); 
Assert.IsTrue(input == roundtripped); 

ли утверждение справедливы также для обратного вида редиректа (input.ToLocalTime().ToUniversalTime())?

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

ответ

5

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

Когда вы посмотрите на DateTimeKind, вы увидите три варианта: Unspecified, Utc и Local. Эта информация упакована в два бита внутреннего 64-битного представления. Поскольку существует четыре возможных значения, которые могут быть представлены двумя битами, это оставляет место для четвертого рода.

И как Джон Скит раскрыл и описал in this blog post, действительно есть скрытый четвертый вид! В основном, это Local, но при решении двусмысленных времен обрабатывается по-разному.

Конечно, вне .Net, DateTime с Local вид не круглый поездка в любом случае. Он обращается как Unspecified с возвратом - если не указано иное. Я писал об этом here. Лучшей альтернативой является DateTimeOffset.

Конечно, это всего лишь одна из многих шуточных вещей с DateTime в .Net. Еще одна замечательная статья от Jon Skeet here обсуждает некоторые из них.

Лучшим решением является прекращение использования любых встроенных типов даты и времени и знакомство с Noda Time. Возможно, вам придется использовать DateTime или DateTimeOffset при взаимодействии с другими системами, но вы можете использовать Noda Time внутренне и позволить ему делать все конверсии для вас.

Дополнительная информация

Вы спрашивали о кругооборот через другой формат, например, клещами или строку.

  • DateTime.Ticks в .NET не большой формат сериализации, потому что он не прилипает к одной точке отсчета. Они представляют собой целое число 100-наносекундных интервалов с полуночи 1 января 0001 года. Но они не соответствуют UTC - скорее, они совпадают с используемым Kind. Другими словами:

    var utcNow = DateTime.UtcNow; 
    var now = utcNow.ToLocalTime(); 
    var equal = utcNow.Ticks == now.Ticks; // false 
    

    Сравните это с JavaScript, который использует Jan 1 1970 точка отсчета - в полночь по Гринвичу. Каждый раз, когда вы получаете количество тиков, например, с .getTime(), оно отражает UTC. Вы не можете получать тики по местному времени с помощью простого вызова метода, потому что они не имеют смысла в JavaScript. Другие языки тоже работают.

    Кроме того, григорианский календарь, который мы используем, не вступил в силу до 1582 года, поэтому он является отвратительным, что 1/1/0001 является их точкой отсчета.Даты до 1582 года бессмысленны в наших нынешних масштабах и должны быть переведены.

  • Строки могут быть отличным способом передачи значений даты и времени, поскольку они являются читабельными для человека. Но вы также должны убедиться, что они читаются машиной без какой-либо двусмысленности. Например, не используйте значение 1/4/2013, потому что без дополнительной информации о культуре вы не узнаете, что это 4 января или 1 апреля. Вместо этого используйте один из форматов ISO8601.

    При использовании их с DateTime вы можете использовать строку формата "o", которая может перемещаться по кругу. Он добавляет Z для Utc видов или локальное смещение для Local видов.

    var dt = new DateTime(2013,6,4,8,56,0); // Unspecified Kind 
    var iso = dt.ToString("o");    // 2013-06-04T08:56:00.0000000 
    
    var dt = DateTime.UtcNow;    // Utc Kind 
    var iso = dt.ToString("o");    // 2013-06-04T15:56:00.0000000Z 
    
    var dt = DateTime.Now;     // Local Kind 
    var iso = dt.ToString("o");    // 2013-06-04T08:56:00.0000000-07:00 
    

    При разборе от этого формата, если вы не имеете смещение, то вид будет Unspecified. Но если у вас есть Z или любое смещение, то по умолчанию будет Local. Он также будет применять любое смещение, которое вы предоставляете, чтобы результат был эквивалентным местным времени. Поэтому, если вы хотите применить его правильно, вы должны явно указать его на округление.

    var dt = DateTime.Parse("2013-01-04T15:56:00.0000000Z"); 
    var kind = dt.Kind; // Local - incorrect! 
    var s = dt.ToString("o"); // "2013-01-04T08:56:00.0000000-07:00" (ouch!) 
    

    Вместо:

    var dt = DateTime.Parse("2013-01-04T15:56:00.0000000Z", 
             CultureInfo.InvariantCulture, 
             DateTimeStyles.RoundtripKind); 
    var kind = dt.Kind; // Utc - that's better. 
    var s = dt.ToString("o"); // "2013-01-04T15:56:00.0000000Z" (nice!) 
    

    Конечно, вы гораздо лучше работать с DateTimeOffset. Когда вы сериализуете в формате ISO8601, вы всегда получите полное представление:

    var dto = DateTimeOffset.Now; 
    var iso = dto.ToString("o"); // 2013-06-04T08:56:00.0000000-07:00 
    

    Этот формат совпадал с RFC3339, который описывает этот профиль ISO8601 спецификации, и быстро становится де-факто стандартом для сериализации метки времени между разнородные системы. ИМХО - вы должны использовать этот формат, когда это возможно. Это значительно превосходит другие форматы, такие как RFC1123, которые вы обычно видите в Интернете. Here are some more details on various date/time formats.

DateTimeOffset значения всегда будут в обе стороны, поскольку они несут всю соответствующую информацию в последовательном формате. Так будет Unspecified и Utc видов DateTime. Просто избегайте Local видов DateTime. Те легко могут попасть в неприятности.

Ответ Пожалуйста?

Просто прочитав это снова, и понял, что я предоставил много деталей, я не сделал напрямую ответ на ваш вопрос. Тесты потерпят неудачу, если входной вид уже первого типа, к которому вы конвертируете. Давайте посмотрим на двух тестовых условиях:

  • someDateTime == someDateTime.ToUniversalTime().ToLocalTime()

    Это будет ошибкой, если исходное значение имеет уже Utc рода.

    Этот тест также потерпит неудачу, если исходное значение недействительно в локальном часовом поясе во время перехода к летнему переходу DST.Например, 2013-03-10 02:00:00 не существует в тихоокеанском времени США. Однако, поскольку он не существует, вы, вероятно, не столкнетесь с этим в своих данных. Таким образом, это, вероятно, не является допустимым условием тестирования.

  • someDateTime == someDateTime.ToLocalTime().ToUniversalTime()

    Это будет ошибкой, если исходное значение имеет уже Local рода.

Также обратите внимание, что свойство Kind не участвует в проверке равенства. Таким образом, хотя вход любого из них может быть Unspecified, выход теста 1 всегда будет иметь вид Local, а выход теста 2 всегда будет иметь вид Utc - , но испытания пройдут в любом случае.

+0

Хорошо знать. Эта информация применима к моему варианту использования. Но что, если я должен отбросить эту информацию (возможно, путем кругового отключения над строковым представлением или «Ticks»)? – usr

+0

@usr - См. Обновление. –

+0

Спасибо. Мое дело, по сути, сериализация со стилем «o». Мне было досадно, что при разборе его нужно указать специальные параметры (из-за постфикса «z»). Я ожидал получить неизмененное значение DateTime типа Utc или Unspecified. DateTime.Parse конвертируется в локальный, поэтому я попытался использовать ToUniversalTime. Вот откуда мой вопрос. Это, по-видимому, недостаток дизайна. Я не вижу причин, почему он должен это делать. – usr

1

reference source подтверждает принятый ответ, связывающую объяснению Джона Скита и могут быть интересны:

// 
// There is also 4th state stored that is a special type of Local value that 
// is used to avoid data loss when round-tripping between local and UTC time. 
// See below for more information on this 4th state, although it is 
// effectively hidden from most users, who just see the 3-state DateTimeKind 
// enumeration. 
[...] 
// For a description of various calendar issues, look at 
// 
// Calendar Studies web site, at 

// http://serendipity.nofadz.com/hermetic/cal_stud.htm.

[...] 
    // The data is stored as an unsigned 64-bit integeter 
    // Bits 01-62: The value of 100-nanosecond ticks where 0 represents 1/1/0001 12:00am, up until the value 
    //    12/31/9999 23:59:59.9999999 
    // Bits 63-64: A four-state value that describes the DateTimeKind value of the date time, with a 2nd 
    //    value for the rare case where the date time is local, but is in an overlapped daylight 
    //    savings time hour and it is in daylight savings time. This allows distinction of these 
    //    otherwise ambiguous local times and prevents data loss when round tripping from Local to 
    //    UTC time. 
    private UInt64 dateData;