Подтвердить что ты не робот

Хранение даты/времени как UTC в базе данных

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

01/04/2010 00:00

Скажите, что это для страны, например. Великобритания, которая соблюдает летнее время (летнее время), и в это конкретное время мы находимся в летних сбережениях. Когда я конвертирую эту дату в UTC и сохраняю ее в базе данных, она фактически сохраняется как:

31/03/2010 23:00

Поскольку дата будет скорректирована на 1 час для летнего времени. Это отлично работает, когда вы наблюдаете DST во время подачи. Однако что происходит, когда часы отрегулированы назад? Когда я вытаскиваю эту дату из базы данных и преобразую ее в локальное время, то конкретное время datetime будет считаться 31/03/2009 23:00, когда в действительности оно обрабатывается как 01/04/2010 00:00.

Исправьте меня, если я ошибаюсь, но разве это не является недостатком при хранении времени как UTC?

Пример преобразования часового пояса

В основном я занимаюсь хранением даты/времени, когда информация отправляется в мою систему, чтобы пользователи могли делать отчет диапазона. Вот как я храню дату/время:

public DateTime LocalDateTime(string timeZoneId)
{
    var tzi = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
    return TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tzi).ToUniversalTime().ToLocalTime(); 
}

Хранение как UTC:

var localDateTime = LocalDateTime("AUS Eastern Standard Time");
WriteToDB(localDateTime.ToUniversalTime());
4b9b3361

Ответ 1

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

Есть проблема, однако - некоторые локальные времена неоднозначны. Например, 1:30 утра 31 октября 2010 года в Великобритании могут быть представлены UTC 01:30 или UTC 02:30, потому что часы возвращаются с 2 до 1 часа. Вы можете получить из любого момента времени, представленного в формате UTC, в локальное время, которое будет отображаться в этот момент, но операция не обратима.

Точно так же очень возможно, чтобы у вас было местное время, которого никогда не было - 1:30 утра 28 марта 2010 года, например, не произошло в Великобритании, потому что в 1 час часы перепрыгивали вперед до 2 утра.

Долгое и короткое: если вы пытаетесь представить момент времени, вы можете использовать UTC и получить однозначное представление. Если вы пытаетесь представить время в определенном часовом поясе, вам понадобится сам часовой пояс (например, Европа/Лондон) и либо представление UTC момента, либо местная дата и время со смещением в это конкретное время (для устранения неоднозначности переходов DST). Другая альтернатива - хранить только UTC и смещение от него; что позволяет вам сообщить местное время в этот момент, но это означает, что вы не можете предсказать, какое время будет наступит через минуту, так как вы не знаете часовой пояс. (Это то, что хранит DateTimeOffset.)

Мы надеемся сделать это достаточно легко в Noda Time, но вам все равно нужно знать об этом как возможность.

EDIT:

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

var tzi = TimeZoneInfo.FindSystemTimeZoneById("AUS Eastern Standard Time");
var aussieTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tzi);
var serverLocalTime = aussieTime.ToLocalTime(); 
var utcTime = serverLocalTime.ToUniversalTime();

Итак, подумайте прямо сейчас - это 13:38 в мое местное время (UTC + 1, в Лондоне), 12:38 UTC, 22:39 в Сиднее.

Ваш код даст:

aussieTime = 22:39 (correct)
serverLocalTime = 23:39 (*not* correct)
utcTime = 22:39 (*not* correct)

Вы не должны вызывать ToLocalTime в результате TimeZoneInfo.ConvertTimeFromUtc - он будет считать, что он вызывается в UTC DateTime (если только он не получил DateTimeKind.Local, чего в этом случае не будет).

Итак, если вы аккуратно сохраняете 22:39 в этом случае, вы не точно сохраняете текущее время в UTC.

Ответ 2

Хорошо, что вы пытаетесь сохранить даты и время в формате UTC. Обычно лучше всего и проще всего думать о UTC, поскольку фактическая дата и время и местное время - это просто псевдонимы для этого. И UTC абсолютно критичен, если вам нужно сделать какую-либо математику по значениям даты/времени, чтобы получить временные интервалы. Обычно я обрабатываю даты внутри UTC и только конвертирую в локальное время при отображении значения пользователю (если это необходимо).

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

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

Ответ 3

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

31/12/2009 23:00 +0100

При отображении этого для пользователя вы можете использовать это же смещение для преобразования или их текущего локального смещения по своему усмотрению.

Этот подход также имеет свои проблемы. Время - беспорядочная вещь.

Ответ 4

Метод TimeZoneInfo.ConvertTimeFromUtc() решит вашу проблему:

using System;

class Program {
  static void Main(string[] args) {
    DateTime dt1 = new DateTime(2009, 12, 31, 23, 0, 0, DateTimeKind.Utc);
    TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
    Console.WriteLine(TimeZoneInfo.ConvertTimeFromUtc(dt1, tz));
    DateTime dt2 = new DateTime(2010, 4, 1, 23, 0, 0, DateTimeKind.Utc);
    Console.WriteLine(TimeZoneInfo.ConvertTimeFromUtc(dt2, tz));
    Console.ReadLine();
  }
}

Вывод:

12/31/2009 11:00:00 PM 
4/2/2010 12:00:00 AM

Вам понадобится .NET 3.5 или лучше и работать в операционной системе, которая сохраняет исторические изменения летнего времени (Vista, Win7 или Win2008).

Ответ 5

Исправьте меня, если я ошибаюсь, но не это немного недостаток при хранении как UTC?

Да, это так. Кроме того, дни корректировки будут либо 23, либо 25 часов, поэтому идиома предшествующего дня в то же время является местным временем - 24 часа является неправильным 2 дня в году.

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

Ответ 6

Это огромный недостаток, но это не является недостатком времени хранения в UTC (потому что это единственная разумная вещь - хранение локальных времен - это всегда катастрофа). Это недостаток - концепция летнего времени. Реальная проблема заключается в том, что информация о часовом поясе меняется. Правила DST являются динамичными и историческими. Время, когда DST, начинающийся в США в 2010 году, не совпадает, когда оно начиналось в 2000 году. До недавнего времени Windows даже не содержала этих исторических данных, поэтому было практически невозможно сделать что-то правильно. Вы должны были использовать tz database, чтобы все было правильно. Теперь я просто искал его, и похоже, что .NET 3.5 и Vista (я полагаю, Windows 2008 тоже) сделали некоторые улучшения, и System.TimeZoneInfo фактически обрабатывает исторические данные. Посмотрите этот.

Но в основном DST должен идти.