System.DateTime представляет дату и время суток, от 1 года до 9999 года, с точностью 100 наносекунд (один «тик»). System.TimeSpan представляет длительность: разницу между двумя значениями DateTime. Оба являются неизменяемыми типами значений (структурами), поэтому каждая операция возвращает новое значение.
Вывод:
2026-09-24 00:00:00
2026-09-24 14:30:00
14:30:05.250
2026 9 24
14:30
Thursday
267
2026-09-24 00:00
14:30:00
Во всех примерах на этой странице даты выводятся с явной строкой формата. ToString() по умолчанию следует текущей культуре (9/24/2026 2:30:00 PM в США, 24.09.2026 14:30:00 в Германии), поэтому его вывод зависит от машины. Коды форматирования описаны на странице формат DateTime.
Недопустимая дата вызывает исключение: new DateTime(2026, 2, 30) выбрасывает ArgumentOutOfRangeException, как и 13-й месяц или 24-й час.
Now, UtcNow и Today
Три статических свойства читают часы:
Пример вывода:
Now: 2026-09-24 18:20:41 (Local)
UtcNow: 2026-09-24 16:20:41 (Utc)
Today: 2026-09-24 00:00:00
В этом примере локальный часовой пояс на два часа опережает UTC, поэтому первые две строки различаются на два часа; на машине с настройкой UTC они совпадут. Свойство Kind записывает, является ли значение Local, Utc или Unspecified (по умолчанию для дат, которые вы создаёте сами). Используйте DateTime.UtcNow для всего, что вы храните, пишете в лог, сравниваете или отправляете в другую систему: оно не прыгает при переходе на летнее время и обратно и означает один и тот же момент на любом сервере. Переводите в локальное время только при показе значения человеку.
Чтобы измерить, сколько выполняется код, используйте System.Diagnostics.Stopwatch, а не вычитание двух значений DateTime.Now: у него гораздо более высокое разрешение и на него не влияет подстройка часов.
Прибавление и вычитание времени
AddDays, AddHours, AddMinutes, AddSeconds, AddMonths и AddYears возвращают новый DateTime. Передайте отрицательное число, чтобы вернуться назад. Поскольку DateTime неизменяем, результат нужно присвоить:
Вывод:
2026-01-31
2026-02-03 09:00
2026-01-30 21:00
2026-02-28
2027-01-31
10:30
29
True
AddMonths ограничивает результат последним днём месяца, если такого дня нет: 31 января плюс один месяц даёт 28 февраля (или 29 в високосный год), а не 3 марта. Поэтому прибавление одного месяца дважды и прибавление двух месяцев сразу могут дать разные даты.
Вычитание дат: TimeSpan
Вычитание одного DateTime из другого даёт TimeSpan:
Вывод:
3.20:30:00
Days: 3, Hours: 20, Minutes: 30
TotalDays: 3.85
TotalHours: 92.5
TotalMinutes: 5550
Nights: 4
Это та часть API, в которой ошибаются чаще всего. Days, Hours, Minutes и Seconds это компоненты интервала (3 дня, 20 часов, 30 минут). TotalDays, TotalHours и TotalMinutes это вся длительность в одной единице в виде double. «Сколько часов пробыл гость?» это TotalHours (92.5), а не Hours (20).
Последняя строка показывает связанный момент: прошло 3.85 дня, но гость провёл 4 ночи. Сравнение частей .Date считает календарные дни, а обычно именно это нужно для счетов и отображения «дней до».
Создание и форматирование значений TimeSpan
Вывод:
02:15:00
01:30:00
1.12:00:00
True
03:45:00
True
02:15
36h 0m
00:00:00
TimeSpan поддерживает +, -, сравнения, Duration() (абсолютное значение) и Negate(). Пользовательские форматы вроде @"hh\:mm" требуют обратного слеша перед буквальными символами, а hh там показывает только компонент часов (от 0 до 23), поэтому для длительностей больше суток собирайте текст из TotalHours, как в предпоследней строке.
Сравнение дат
DateTime поддерживает ==, !=, <, >, <= и >=, а также CompareTo и DateTime.Compare. Чтобы сравнить только дату без времени, сравнивайте свойства .Date:
Вывод:
True
True
1
True
2026-09-01
Для вопроса «попадает ли эта метка времени в 30 сентября?» сравнивайте с началом следующего дня через <, как выше. Запись check <= end исключила бы всё после полуночи последнего дня, потому что end равно 2026-09-30 00:00:00.
Сравнения смотрят только на тики, а не на Kind: значение Local и значение Utc, которые печатаются одинаково, считаются равными, хотя это разные моменты. Ещё одна причина хранить время в UTC.
День недели и начало недели
DayOfWeek это перечисление от Sunday (0) до Saturday (6). Арифметика над ним находит будние дни и границы недели:
Вывод:
Thursday
4
Weekend: False
Week starts 2026-09-21 (Monday)
Next Friday: 2026-09-25
2026-09-01 to 2026-09-30
Названия дней, которые печатает DayOfWeek.ToString(), всегда английские. Для локализованного названия форматируйте дату через "dddd" с культурой.
Разбор дат из строк
Когда формат входных данных известен, используйте ParseExact или TryParseExact с CultureInfo.InvariantCulture. Строка формата использует те же коды, что и форматирование:
Вывод:
2026-09-24 00:00
2026-09-24 18:05
'2026-02-28' -> Saturday, February 28
'2026-02-30' -> invalid
'28.02.2026' -> invalid
'' -> invalid
2026-02-28
2026-09-24 10:00 Utc
DateTime.Parse(text) без формата пытается угадать его по текущей культуре. "03/04/2026" это 4 марта на американской машине и 3 апреля на британской, а дата, которая разбирается на вашем ноутбуке, может выбросить FormatException на сервере. Оставьте Parse для ввода, который набирает местный пользователь; для файлов, API и баз данных используйте ParseExact с инвариантной культурой. ParseExact выбрасывает FormatException, когда текст не соответствует формату; TryParseExact вместо этого возвращает false.
Вычисление возраста
Вычитать даты рождения и делить на 365 неправильно в районе дней рождения и високосных лет. Сравните годы, а затем поправьте, если день рождения в этом году ещё не наступил:
Вывод:
36
35
18
70 days to go
DateTimeOffset
DateTime не хранит часовой пояс, кроме расплывчатого флага Kind. DateTimeOffset хранит значение вместе со смещением от UTC, поэтому всегда обозначает один точный момент:
Вывод:
2026-09-24 14:00 +02:00
2026-09-24 12:00
2026-09-24 12:30
00:30:00
21:00 +09:00
Используйте DateTimeOffset (или значения DateTime в UTC) для меток времени: когда был оформлен заказ, когда отправлено сообщение. Базы данных и JSON-сериализаторы хорошо с ним работают. Для преобразования между именованными часовыми поясами с правилами летнего времени используйте TimeZoneInfo.ConvertTime; идентификаторы поясов в старых версиях .NET зависят от операционной системы («Europe/Paris» в Linux, «Romance Standard Time» в Windows), а .NET 6 и новее принимают оба варианта.
DateOnly и TimeOnly (.NET 6 и новее)
Многие значения это дата без времени (день рождения, срок оплаты) или время без даты (часы работы). .NET 6 добавил для них два типа:
// .NET 6 and later
DateOnly birthday = new DateOnly(1990, 9, 24);
DateOnly due = DateOnly.FromDateTime(DateTime.Today).AddDays(14);
int daysLeft = due.DayNumber - DateOnly.FromDateTime(DateTime.Today).DayNumber;
TimeOnly opens = new TimeOnly(9, 0);
TimeOnly closes = new TimeOnly(17, 30);
bool isOpen = TimeOnly.FromDateTime(DateTime.Now).IsBetween(opens, closes);
Они устраняют класс ошибок, при которых случайное время или часовой пояс сдвигает дату на один день. Старый код и код под .NET Framework или Unity использует DateTime со временем, оставленным на полночи.
Частые ошибки
- Отбрасывание результата
AddDays.DateTimeнеизменяем; присваивайте возвращённое значение. HoursвместоTotalHours. Компоненты против полной длительности.- Хранение
DateTime.Now. Храните UTC и преобразуйте для отображения. - Вызов
ToString()без формата в логах, файлах или тестах, где вывод зависит от культуры машины. - Разбор машинных данных через
DateTime.Parse. ИспользуйтеParseExactи инвариантную культуру. - Путаница
mmиMMв строках формата (минуты и месяцы). См. формат DateTime.
Часто задаваемые вопросы
Чем DateTime.Now отличается от DateTime.UtcNow?
DateTime.Now это текущее время в локальном часовом поясе компьютера со значением Kind, равным Local. DateTime.UtcNow это текущее время в UTC со значением Kind, равным Utc, и оно к тому же быстрее, потому что пропускает преобразование часового пояса. Храните и сравнивайте метки времени в UTC, а в локальное время переводите только для отображения.
Как получить разницу между двумя датами в C#?
Вычтите одну из другой: TimeSpan gap = end - start;. Затем читайте gap.TotalDays, gap.TotalHours или gap.TotalMinutes, чтобы получить всю длительность в виде double, или gap.Days для части из целых дней. Для календарных месяцев или лет встроенного свойства нет, потому что месяцы разной длины; сравнивайте поля года и месяца сами.
Чем TimeSpan.Hours отличается от TotalHours?
Hours это только компонент часов, от 0 до 23, после вычета целых дней. TotalHours это вся длительность, выраженная в часах, в виде double. Для интервала в 1 день и 3 часа Hours равно 3, а TotalHours равно 27. Использовать Hours там, где имелось в виду TotalHours, очень распространённая ошибка.
Как разобрать строку с датой в C#?
Когда формат известен, используйте DateTime.ParseExact(text, "yyyy-MM-dd", CultureInfo.InvariantCulture) или DateTime.TryParseExact, чтобы получить false вместо FormatException при плохом вводе. DateTime.Parse угадывает формат по текущей культуре, поэтому 03/04/2026 в США означает 4 марта, а в Великобритании 3 апреля.
Почему AddDays не изменяет мой DateTime?
DateTime это неизменяемый тип значения. AddDays, AddHours и другие методы возвращают новый DateTime и не меняют исходный, поэтому результат нужно присвоить: due = due.AddDays(7);.
Когда использовать DateTimeOffset вместо DateTime?
Используйте DateTimeOffset для меток времени, которые должны обозначать точный момент, например когда был оформлен заказ или записана строка лога, особенно если данные перемещаются между серверами и часовыми поясами. Он хранит вместе со значением смещение от UTC. DateTime подходит для меток времени только в UTC и для дат без осмысленного часового пояса.