1. Знакомство с типом DateTime
Дата и время — это такой базовый кирпичик любой бизнес-логики, что его нельзя игнорировать даже в самых простых проектах. Бывает часто нужно узнать, когда пользователь зарегистрировался, когда отправить письмо, правильно обработать расписание рейсов, временные зоны — везде даты! Но, как показывает практика, работа с датой и временем — одна из самых коварных тем в программировании. 😅
C# предлагает два особенно важных типа для работы со временем: DateTime и DateTimeOffset. Если говорить в двух словах — один для простых сценариев, другой для задач, связанных с временными зонами и точным хранением времени.
Что такое DateTime?
Тип DateTime — это структура, определённая в пространстве имён System. Она предназначена для хранения даты и времени с точностью до 100 наносекунд (1/10-ти миллиардная секунды).
- Тип значения (struct), то есть копируется по значению.
- Содержит как дату, так и время.
- Позволяет хранить как просто дату (2025-05-24), так и дату с временем (2025-05-24 15:17:42).
- «Не знает» о временных зонах, если явно не сказано.
2. Создаем DateTime
Вариантов несколько, рассмотрим главные.
1. Текущая дата и время
DateTime now = DateTime.Now; // Локальное время компьютера
DateTime utcNow = DateTime.UtcNow; // Время по Гринвичу (UTC)
- Now — это "как на вашем компьютере", с учётом локальной часовой зоны Windows.
- UtcNow — "универсальное", то есть мировое координированное время. Очень удобно для хранения времени, например, в базах данных или системах, которые работают по всему миру.
2. Конструкторы
// Год, месяц, день
var date1 = new DateTime(2025, 5, 24);
// Год, месяц, день, часы, минуты, секунды
var date2 = new DateTime(2025, 5, 24, 12, 0, 0);
// Год, месяц, день, часы, минуты, секунды, миллисекунды
var date3 = new DateTime(2025, 5, 24, 12, 0, 0, 500);
Месяцы не нумеруются с нуля! То есть январь — это 1, декабрь — 12.
3. Парсинг из строки
Очень часто дата приходит в виде строки! Например, пользователь вводит дату вручную.
string userInput = "2025-05-24 18:30";
DateTime parsed = DateTime.Parse(userInput); // Может бросить исключение!
Для безопасного парсинга — используем TryParse:
string input = "2025-05-24 18:30";
if (DateTime.TryParse(input, out DateTime result))
{
Console.WriteLine($"Преобразовано успешно: {result}");
}
else
{
Console.WriteLine("Не удалось преобразовать строку в дату.");
}
4. Получение даты "без времени"
DateTime today = DateTime.Today; // Текущий день, но время = 00:00:00
Извлечение компонентов даты и времени
Все свойства можно получить быстро:
var dt = new DateTime(2025, 5, 24, 15, 17, 42);
int year = dt.Year; // 2025
int month = dt.Month; // 5 (май)
int day = dt.Day; // 24
int hour = dt.Hour; // 15
int minute = dt.Minute; // 17
int second = dt.Second; // 42
DayOfWeek dow = dt.DayOfWeek; // Friday
3. Работа с типом DateTime
Сложение и вычитание дат
Время в программировании — штука быстрая, и чтобы не считать "а сколько же часов до дедлайна", используем методы класса:
DateTime now = DateTime.Now;
DateTime tomorrow = now.AddDays(1); // Завтра
DateTime afterFiveHours = now.AddHours(5); // Через 5 часов
DateTime inTenMinutes = now.AddMinutes(10); // Через 10 минут
Разность двух дат: TimeSpan
Разница между двумя датами — это отдельная величина, тип TimeSpan:
DateTime deadline = new DateTime(2026, 6, 1);
DateTime now = DateTime.Now;
TimeSpan untilDeadline = deadline - now;
Console.WriteLine($"До дедлайна осталось дней: {untilDeadline.Days}");
Console.WriteLine($"До дедлайна осталось часов: {untilDeadline.TotalHours:N2}");
TimeSpan — это "разница", а не "конкретная дата/время"!
Проверка даты в будущем/прошлом
if (deadline > now)
Console.WriteLine("Дедлайн ещё впереди!");
else
Console.WriteLine("Дедлайн уже прошёл! Срочно сдаём работу!");
4. Как хранятся даты под капотом
Для удобства сложения и вычитания дат, программисты решили хранить все даты как количество секунд прошедших с начала нашей эры. Значения диапазона:
| Свойство | Значение |
|---|---|
| Минимальная дата | = 0001-01-01 00:00:00 |
| Максимальная дата | = 9999-12-31 23:59:59.9999999 |
| Единица измерения | 1 тик = 100 наносекунд |
DateTime сохраняет не просто количество секунд, а количество "тиков" (ticks) с особой даты — 1 января 0001 года (да-да, программисты не знают про динозавров, их летоисчисление строже). Один тик = 100 наносекунд.
5. Тип DateTimeOffset
Иногда типа DateTime оказывается мало. Рассмотрим классический случай: компания работает в двух странах, у них разные временные зоны, а все встречи запланированы "по-моему времени". Как быть?
Время, записанное как DateTime, зачастую "абстрактное": это просто набор циферок и тик, пока вы не укажете явно, в какой зоне это было. Например, если вы получили "2025-05-24 15:00", вы не можете быть уверены — это по времени Найроби? Багдада? Берлина? (а если ещё и летнее время — держитесь крепче!).
Поэтому для задач, связанных с точным моментом во времени, а не просто с "датой календаря", появился второй тип — DateTimeOffset. Создание DateTimeOffset с указанием смещения (например, Найроби +3 к UTC):
var localTime = new DateTimeOffset(2025, 5, 24, 15, 0, 0, TimeSpan.FromHours(3));
Используйте DateTimeOffset, если:
- Нужно знать точный момент времени (например, для событий в логе, привязанных к серверному времени).
- Программа работает с пользователями в разных временных зонах.
- Надо хранить/передавать время с указанием зоны (например, для транзакций, событий, расписаний).
6. Как создать DateTimeOffset?
- Текущий момент времени
DateTimeOffset now = DateTimeOffset.Now; // Локальное время + смещение DateTimeOffset utc = DateTimeOffset.UtcNow; // UTC (смещение 0) - Явное создание
// Год, месяц, день, час, минута, секунда, смещение (например, +2 часа) DateTimeOffset custom = new DateTimeOffset(2025, 5, 24, 20, 0, 0, TimeSpan.FromHours(2)); - Конвертация из DateTime
DateTime dt = DateTime.Now; DateTimeOffset dto = new DateTimeOffset(dt); // Локальное смещение подхватится автоматически
Извлечение компонентов
DateTimeOffset dto = DateTimeOffset.Now;
var datePart = dto.Date; // Только дата
var timePart = dto.TimeOfDay; // Только время
var offset = dto.Offset; // Смещение относительно UTC, например, 03:00:00
Преобразование между зонами времени
Магия смещений:
// Получаем дату в UTC
var now = DateTimeOffset.Now;
var utc = now.ToUniversalTime();
Console.WriteLine(utc); // Время по Гринвичу
// Переводим в другую зону
var berlinTime = utc.ToOffset(TimeSpan.FromHours(2));
Console.WriteLine(berlinTime); // Для Берлина (CET +2)
7. Когда использовать DateTime, а когда — DateTimeOffset?
Философия выбора
Когда речь идёт о чём-то простом и локальном — например, дне рождения, дате публикации статьи или дедлайне на опрос до 15:00, чаще всего достаточно DateTime. В таких случаях абсолютное время не критично, и важно лишь, что событие произошло в определённый день и час, без привязки к часовой зоне.
А вот если нужно зафиксировать конкретный момент времени с учётом часового пояса, например, в логах, транзакциях или распределённых системах — лучше использовать DateTimeOffset. Он не даст запутаться при передаче времени между системами, особенно если они находятся в разных часовых поясах.
| Сценарий | Тип | Почему |
|---|---|---|
| День рождения | |
Важна только дата, зона не важна (обычно) |
| Время логина | |
Важно знать точный момент и смещение |
| Срок действия товара | |
Дата окончания, зона не важна |
| Время события в логе | |
Желательно всегда хранить со смещением |
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ