Сохранение даты / времени в формате UTC в базе данных - PullRequest
10 голосов
/ 05 апреля 2010

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

01/04/2010 00:00

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

31/03/2010 23:00

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

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

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

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

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());

Ответы [ 6 ]

31 голосов
/ 05 апреля 2010

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

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

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

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

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

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 голосов
/ 05 апреля 2010

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

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

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

1 голос
/ 06 апреля 2010

Метод 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).

1 голос
/ 05 апреля 2010

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

31/12/2009 23:00 +0100

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

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

0 голосов
/ 05 апреля 2010

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

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

0 голосов
/ 05 апреля 2010

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

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

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

...