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

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

01/04/2010 00:00

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

31/03/2010 23:00

Поскольку дата будет скорректирована -1 час на летнее время. Это отлично работает, когда вы наблюдаете летнее время во время отправки. Однако что происходит, когда часы возвращаются обратно? Когда я извлекаю эту дату из базы данных и конвертирую ее в местное время, эта конкретная дата-время будет отображаться как 31/03/2010 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());

person James    schedule 05.04.2010    source источник
comment
Для справки: Поддержка значений индекса часовых поясов Microsoft. microsoft.com/en-gb/help/973627/   -  person Tony    schedule 15.04.2020


Ответы (6)


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

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

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

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

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

РЕДАКТИРОВАТЬ:

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

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.

person Jon Skeet    schedule 05.04.2010
comment
Извините, мой вопрос был немного поспешным! Я имел в виду, что если вы конвертируете время с помощью ConvertTimeFromUtc, оно автоматически настраивается на летнее время. Однако, поскольку я храню DateTimes в базе данных как UTC, он удалит корректировку (при необходимости). Итак, когда мне пришлось вытащить эту дату и время в будущем (когда мы уже не во времени, в котором соблюдается летнее время), дата и время не было бы повторно настроено обратно ... если это имеет смысл? Я думаю, что сохранение смещения было бы лучшим решением, тогда я точно знаю, требует ли дата корректировки или нет. - person James; 06.04.2010
comment
@ Джеймс: Нет, так не работает. Применяется ли летнее время или нет, зависит от даты преобразования, а не от текущей даты. Поэтому, когда мы больше не живем во времени, летнее время не имеет значения. - person Jon Skeet; 06.04.2010
comment
А, ладно, так что, если я вытащу эту конкретную дату и сделаю преобразование, она все равно должна рассчитать, как ожидалось? - person James; 06.04.2010
comment
@James: Да, если вы точно сохранили его как UTC, вы сможете точно отобразить его в любом другом часовом поясе. - person Jon Skeet; 06.04.2010
comment
@Jon, хорошо, что я делаю, это всегда конвертирую текущую дату / время в местное время (для этого конкретного часового пояса) через ConvertFromTimeUtc и ToLocalTime. Затем, когда я сохраняю его в базе данных, я использую метод ToUniversalTime для преобразования времени в UTC. - person James; 06.04.2010
comment
@James: Когда вы говорите ToUniversalTime и ToLocalTime, вы имеете в виду DateTime или DateTimeOffset? - person Jon Skeet; 06.04.2010
comment
@ Джеймс: Не делай этого. Фактически, по возможности не используйте DateTime. Он всегда будет конвертироваться в местное / UTC в зависимости от часового пояса компьютера, на котором он работает, что, вероятно, не то, что вы имеете в виду. DateTimeOffset, вероятно, то, что вы хотите здесь использовать. - person Jon Skeet; 06.04.2010
comment
@Jon, да, я знал, что это так. Однако то, что я делаю, - это вызов DateTime.UtcNow (который, как вы сказали, дает мне локальное время в формате UTC), затем я вызываю ConvertTimeFromUtc для определенного часового пояса и использую ToLocalTime. Я проверял время с помощью некоторых онлайн-сайтов, и мне показалось, что оно встроено. Разве это не действительный способ сделать это? - person James; 06.04.2010
comment
@ Джеймс: Это зависит от того, что именно вы пытаетесь сделать ... это не очень ясно из вашего описания. Но если вы не хотите задействовать часовой пояс, локальный для компьютера (чего вы, вероятно, не хотите), вам не следует не использовать DateTime.ToLocalTime. Было бы очень полезно, если бы вы либо начали новый вопрос, либо изменили существующий, чтобы показать некоторый полный код, и объяснили бы в точности, что вы пытаетесь с ним делать. - person Jon Skeet; 06.04.2010
comment
Я обновил свой пост, чтобы показать вам, как я это делаю. - person James; 06.04.2010
comment
@Jon, я провел небольшое исследование прямо здесь, на SO, и обнаружил, что то, что я делаю, на самом деле является способом сделать это. Спасибо, что предупредили нас о DST. - person James; 06.04.2010
comment
@ Джеймс: Нет, то, что ты делаешь, неправильно. Я покажу вам, почему, в редактировании. - person Jon Skeet; 06.04.2010
comment
Хорошо, я думаю, что запутался, так как думал, что он вернет время UTC для этого часового пояса. Значит, возвращает местное время? Так что звонить .ToLocalTime вообще не нужно. Однако правильно ли сохранять .ToUniversalTime в базе данных из преобразованного времени, т.е. в этом случае вызов aussieTime.ToUniversalTime()? Это должно дать мне UTC для австралийского времени. - person James; 06.04.2010
comment
@jon, я просмотрел свой код и оказалось, что я действительно делаю ConvertTimeFromUtc(DateTime.UtcNow, tzi).ToUniversalTime().ToLocalTime(), поэтому я думаю, что именно поэтому я получил правильный результат. - person James; 06.04.2010
comment
@James: В этом случае вы получаете местное время на сервере. По сути, все это эквивалентно просто DateTime.Now. Опять же, это все еще не UTC. Если вы все равно генерируете время на сервере, почему вы просто не используете DateTime.UtcNow? При чем тут австралийское время? UTC здесь и сейчас совпадает с UTC там и сейчас, если вы понимаете, что я имею в виду. - person Jon Skeet; 06.04.2010
comment
@Jon, я думал, мне придется преобразовать его в австралийское время, прежде чем я сохраню UTC? Когда я конвертирую его в австралийское время, я делаю ToUniversalTime, который отличается от DateTime.UtcNow, поскольку DateTime.UtcNow дает мне UTC текущего серверного времени, а не австралийского времени. Или мне что-то здесь не хватает? Вы в основном говорите, что DateTime.UtcNow - это то же самое, что преобразовать дату и время в австралийское время и вызвать ToUniveralTime? - person James; 07.04.2010
comment
@James: Я думаю, вам не хватает того, что такое UTC или что означают часовые пояса. UTC одинаково во всем мире - вот в чем суть. Например, нет разницы между UTC в Австралии и UTC в США. - person Jon Skeet; 07.04.2010
comment
@Jon, поэтому, если у меня есть дата и время (независимо от того, преобразовано ли оно в другой часовой пояс или нет), как я могу сохранить эквивалент этой даты в формате UTC? Приношу извинения за это, я новичок во всем, что касается UTC, и во всем, как это работает. - person James; 07.04.2010
comment
@ Джеймс: Боюсь, тебе нужно быть более точным. Проблема с DateTime заключается в том, что он может представлять время в формате UTC, неопределенное время или местное время (где local часто считается локальным для машины). Откуда вы берете дату / время для начала? Если вы его создаете, DateTime.UtcNow всегда будет означать этот момент в формате UTC, и это должно давать то же значение, где бы вы его ни запустили. (По модулю неточные часы компьютера :) - person Jon Skeet; 07.04.2010
comment
@Jon: Дата извлекается из нескольких мест. Однако наиболее важные из них извлекаются из писем (получено datetime). Скажем, я получил электронное письмо из Австралии, которое примерно на +10 часов опережает текущее системное время (поскольку я нахожусь в Великобритании). Можно ли позвонить в .ToUniveralTime(), чтобы узнать время в формате UTC этого мгновенного ? - person James; 07.04.2010
comment
@James: электронное письмо должно включать смещение часового пояса, поэтому вам не нужно получать сам часовой пояс (я считаю). Самым безопасным вариантом было бы проанализировать его как DateTimeOffset вместо DateTime, что устраняет большую часть двусмысленности. По сути, все это довольно сбивает с толку - это одна из причин, по которой я начал Noda Time :) - person Jon Skeet; 07.04.2010
comment
@Jon, я буду вторым !! Подводя меня к стене ... Я действительно не понимаю смысла ToUniversalTime() & ToLocalTime(), тогда, если они не дают вам UTC / местное время фактического экземпляра datetime, тогда это действительно не имеет никакого смысла. Когда выйдет NodaTime? - person James; 07.04.2010
comment
@James: Я надеюсь, что к концу года выйдет первый выпуск Noda Time. Проблема с DateTime заключается в том, что он может представлять время в формате UTC или локальное время или неуказанное время ... и local используется для обозначения локального для сервера, а не локального в этот конкретный часовой пояс. Какой бардак :( - person Jon Skeet; 07.04.2010
comment
@Jon, да, все это кажется немного двусмысленным, и вы можете видеть, как люди, не знакомые с этой областью (я тому доказательство), могут запутаться! Надеюсь, я смогу найти хорошее решение. Спасибо за помощь. - person James; 07.04.2010
comment
@Jon отправил еще один вопрос, интересно, можете ли вы взглянуть. Спасибо. stackoverflow.com/questions/2647472/ - person James; 16.04.2010

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

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

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

person Jeffrey L Whitledge    schedule 05.04.2010
comment
@Jeff, я конвертирую даты в формате UTC обратно на основе метода ConvertTimeFromUtc на основе часового пояса. Поэтому я не пытаюсь вручную настроить DST, это фактический метод, который настраивается автоматически. Я изменю дату, так как считаю это сбивающим с толку народом, это был просто пример. - person James; 06.04.2010

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

31/12/2009 23:00 +0100

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

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

person Thomas    schedule 05.04.2010
comment
+1 Ага, думаю, это действительно решит проблему. Значит, когда я рассчитаю его обратно, я смогу сказать, отрегулирован он или нет. - person James; 06.04.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).

person Hans Passant    schedule 05.04.2010
comment
@nobugz, я действительно использую этот метод для преобразования. Мой вопрос был просто основан на теории путем наблюдения за информацией, хранящейся в моей базе данных. - person James; 06.04.2010
comment
@ Джеймс, я показал вам результат теории. Работает на моей машине. Вы ознакомились с требованиями внизу моего сообщения? У вас разные результаты? Это способ продолжать задавать вопрос ... - person Hans Passant; 06.04.2010
comment
нет, я не тестировал это, я просто думал (глядя на то, как дата / время, где хранятся в базе данных), что произойдет, если я не в настоящее время нахожусь во время наблюдения летнего времени. Правильно ли были отрегулированы дата / время. Однако, как упомянул @Jon, DST принимается во внимание на основе самого фактического datetime, а не текущего datetime. Спасибо. - person James; 06.04.2010

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

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

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

person dawg    schedule 05.04.2010

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

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

person MK.    schedule 05.04.2010