Bezdepbonusu7Программирование: статьи и ответы

Вопрос

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

Автор вопроса: JamesОтветов: 6

Я сохраняю дату / время в базе данных как 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());
  • Для справки: Поддержка значений индекса часовых поясов Microsoft. microsoft.com/en-gb/help/973627/ Tony 15.04.2020

datetimeutctimezone

Ответы

Ответ 1 · рейтинг 38 · Jon Skeet

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

Однако есть проблема - иногда местное время неоднозначно. Например, 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.

Ответ 2 · рейтинг 2 · Jeffrey L Whitledge

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

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

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

Ответ 3 · рейтинг 1 · Thomas

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

31/12/2009 23:00 +0100

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

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

Ответ 4 · рейтинг 1 · Hans Passant

Метод 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 · dawg

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

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

Ответ 6 · MK.

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

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

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы