Menu

DateTime и TimeSpan в C#: Now, UtcNow, сложение, разница, сравнение и Parse

Работа с датами и временем в C#: создание значений DateTime, Now, UtcNow и Today, прибавление дней и месяцев, вычитание с получением TimeSpan, TotalHours и Hours, сравнение дат, DayOfWeek, разбор через ParseExact и TryParse, DateTimeOffset и DateOnly.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

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 и для дат без осмысленного часового пояса.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ