Обложка канала

.NET Разработчик

Опытный разработчик не так давно зашёл в .Net и поставил цель получить сертификат Microsoft. Свой ежедневный прогресс он описывает на канале .Net Разработчик. Заметки об изученном материале, советы по повышению производительности и поддержке мотивации, ин

.NET Разработчик

3 года назад
Открыть в
День 1619. #ЗаметкиНаПолях Время в .NET. Начало Время – важная часть наших программ. Отслеживание часовых поясов и тестирование кода, зависящего от времени, - вечная головная боль разработчиков. История DateTime была основной структурой для хранения даты и времени в .NET, начиная с версии 1.1. У неё есть большой недостаток - отсутствие часового пояса. Чтобы решить эту проблему, было добавлено свойство Kind с возможными значениями: Local (по умолчанию), Utc или Unspecified. Для конвертации в UTC нужно вызвать метод ToUniversalTime() или использовать свойство DateTime.UtcNow. Как .NET понимает разницу в часовых поясах, если DateTime не предоставляет эту информацию? Метод ToUniversalTime берёт часовой пояс из операционной системы, что может приводить к проблемам. Если создать локальное время на сервере в одном часовом поясе, а потом переместить его на другой сервер (например, на клиента API), то результат вызова ToUniversalTime на этих серверах будут разным. Поэтому Microsoft выпустили «Рекомендации по написанию кода с использованием даты и времени в платформе .NET Framework», переложив всю ответственность на разработчиков: «Разработчик несёт ответственность за отслеживание информации о часовом поясе, связанным со значением DateTime, с помощью некоторого внешнего механизма. Обычно это достигается путём определения другого поля или переменной, которые вы используете для записи информации о часовом поясе при сохранении значения типа DateTime.» .NET ожидает, что информация о часовом поясе будет связана со свойством Kind во время восстановления даты и времени: DateTimeKind.Local с TimeZoneInfo.Local, DateTimeKind.Utc с TimeZoneInfo.Utc и DateTimeKind.Unspecified в остальных случаях. Пользовательский часовой пояс, как рекомендует Microsoft, требует использования DateTimeKind.Unspecified:
var nowInNY = DateTime.Now;
var nyTZ = TimeZoneInfo
 .FindSystemTimeZoneById("Eastern Standard Time");
nowInNY = DateTime
 .SpecifyKind(nowInNY, DateTimeKind.Unspecified);
var utc = TimeZoneInfo
 .ConvertTimeToUtc(nowInNY, nyTZ);

Альтернативным решением будет создавать и хранить дату только в UTC:
var utc = DateTime.UtcNow;
и преобразовывать её в локальную:
var localTime = utc.ToLocalTime();
или в нужный часовой пояс:
var nyTime = TimeZoneInfo
 .ConvertTimeFromUtc(utc, nyTZ);

Это требует дополнительных проверок на предмет случайного использования DateTime с локальной инициализацией через DateTime.Now, а также не исключает таких нюансов, как изменение правил перехода на летнее время. В .NET 2 была введена структура DateTimeOffset. Она состоит из: - структуры DateTime - свойства Offset, хранящего смещение относительно UTC. Однако проблема с серверами в разных часовых поясах никак не решается через DateTimeOffset.Now. Всё дело в переходе на летнее время. Для разных дат смещение в одном и том же часовом поясе будет разным. Кроме того, правила перехода на летнее время могут меняться (нам ли в РФ не знать). А значит невозможно определить происхождение даты и времени, просто взглянув на значение смещения. Поэтому для международного ПО лучше хранить часовой пояс для возможного пересчёта смещения по новым правилам. Для этого подходит класс TimeZoneInfo. Окончание следует… Источник: https://www.infoq.com/articles/dotnet-unit-tests-time-timezone/