DateTime или DateTimeOffset в C#: Полный гайд для разработчика

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему у вас болит голова от дат?

Если вы когда-нибудь писали код на C# и сталкивались с задачей передать дату и время между сервером в Москве и клиентом в Нью-Йорке, вы наверняка замечали странные баги: «А почему у меня завтра?», «Откуда взялось 3 часа?». Знакомая ситуация?

В .NET есть два главных инструмента для работы с датами: DateTime и DateTimeOffset. На первый взгляд они похожи — оба хранят дату и время. Но разница между ними ключевая, и неправильный выбор может привести к катастрофическим ошибкам в бизнес-логике.

В этом туториале мы разберем DateTime vs DateTimeOffset на реальных примерах, поймем, когда что использовать, и навсегда забудем о путанице с часовыми поясами. Поехали!

Что такое DateTime? (И почему это ловушка)

DateTime — это структура, которая хранит дату и время. Она существует в .NET с самой первой версии. Но у неё есть одна особенность: она не хранит информацию о часовом поясе. Точнее, хранит, но только в виде флага Kind:

  • DateTimeKind.Unspecified — пояс не указан (по умолчанию).
  • DateTimeKind.Utc — время UTC.
  • DateTimeKind.Local — местное время системы.

Проблема в том, что этот флаг — просто информация. Он не конвертирует время автоматически. Представьте, что вы сохраняете в базу данных DateTime с Kind = Local на сервере в Москве. Потом вы читаете эту дату на сервере во Владивостоке. Что произойдет? Правильно — программа увидит, что Kind = Local, и подумает, что это местное время Владивостока. А на самом деле это Москва. Абсолютная катастрофа.

// Пример проблемы с DateTime
DateTime moscowTime = new DateTime(2025, 3, 15, 14, 0, 0, DateTimeKind.Local);
Console.WriteLine(moscowTime); // 15.03.2025 14:00:00 (предполагается, что это MSK)

// Если этот код запустить на сервере с часовым поясом UTC+10 (Владивосток)
// DateTime будет интерпретировано как местное время Владивостока!
// Что приведет к ошибке в 7 часов.

Что такое DateTimeOffset? (Спаситель разработчиков)

DateTimeOffset — это более умная структура, которая появилась в .NET Framework 3.5. Она хранит дату, время и смещение относительно UTC (Offset). Смещение — это число часов и минут, на которое ваше локальное время отличается от UTC.

Например: 2025-03-15 14:00:00 +03:00 — это время в Москве (UTC+3). Если вы передадите это значение на сервер во Владивостоке (UTC+10), .NET поймет: «Ага, это +03, значит UTC = 11:00. Теперь я могу легко пересчитать в местное время: 21:00». Никакой магии — чистая математика.

// Пример с DateTimeOffset
DateTimeOffset moscowOffset = new DateTimeOffset(2025, 3, 15, 14, 0, 0, TimeSpan.FromHours(3));
Console.WriteLine(moscowOffset); // 15.03.2025 14:00:00 +03:00

// Теперь, даже на сервере во Владивостоке, мы знаем точное смещение
// и можем корректно отобразить время для пользователя

Ключевые различия: DateTime vs DateTimeOffset

Давайте сведем всё в наглядную таблицу. Это поможет вам быстро понять, что выбрать в конкретной ситуации.

1. Хранение информации о часовом поясе

  • DateTime: Хранит только флаг Kind (Unspecified, Utc, Local). Не хранит само смещение.
  • DateTimeOffset: Хранит точное смещение от UTC (например, +03:00, -05:00).

2. Сравнение значений

  • DateTime: Сравнивает по «голому» значению. Если у вас 10:00 UTC и 10:00 MSK (UTC+3), то DateTime.Equals() скажет, что они равны. Хотя на самом деле это разные моменты времени.
  • DateTimeOffset: Сравнивает по моменту времени (UTC). 10:00 UTC и 13:00 MSK будут считаться равными, так как это один и тот же момент.
// Сравнение DateTime vs DateTimeOffset
DateTime utcTime = new DateTime(2025, 3, 15, 10, 0, 0, DateTimeKind.Utc);
DateTime localTime = new DateTime(2025, 3, 15, 10, 0, 0, DateTimeKind.Local); // Предположим, UTC+3

Console.WriteLine(utcTime == localTime); // True! Но это неправильно, разница 3 часа!

// А теперь с DateTimeOffset
DateTimeOffset utcOffset = new DateTimeOffset(2025, 3, 15, 10, 0, 0, TimeSpan.Zero);
DateTimeOffset mskOffset = new DateTimeOffset(2025, 3, 15, 13, 0, 0, TimeSpan.FromHours(3));

Console.WriteLine(utcOffset == mskOffset); // True! Так как оба момента времени — 10:00 UTC

3. Сериализация и передача данных

  • DateTime: При сериализации в JSON или XML, если Kind не указан, клиент может неправильно интерпретировать время.
  • DateTimeOffset: Идеально подходит для API и обмена данными. Смещение всегда передается вместе с временем, что исключает двусмысленность.

4. Работа с SQL

  • DateTime: В SQL Server есть тип datetime и datetime2. Они не хранят смещение.
  • DateTimeOffset: В SQL Server есть тип datetimeoffset. Он полностью совместим с .NET DateTimeOffset и хранит смещение.

Когда использовать DateTime, а когда DateTimeOffset?

Вот простая шпаргалка, которая спасет вас от головной боли.

Используйте DateTimeOffset, если:

  • Вы храните или передаете абсолютный момент времени (например, время создания записи в логе, дата транзакции).
  • Ваше приложение работает с пользователями из разных часовых поясов.
  • Вы используете REST API или обмениваетесь данными с другими системами.
  • Вы работаете с SQL Server и используете тип datetimeoffset.

Используйте DateTime, если:

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

Блоги

Рекомендаций по книгам