Модуль datetime — это модуль стандартной библиотеки Python для работы с датами, временем и промежутками между ними. Ставить ничего не нужно: достаточно import datetime или from datetime import .... Примеры ниже проверены на Python 3.14 (сентябрь 2026).
Содержание
- Минимальный пример: все классы сразу
- Класс date: календарная дата
- Класс time: время суток
- Класс datetime: конкретный момент
- Класс timedelta: арифметика дат
- strftime и strptime: дата в строку и обратно
- Часовые пояса: naive и aware, модуль zoneinfo
- Если не получилось: частые симптомы
- Выводы
- Где применяется / связь с практикой
- FAQ
В модуле несколько классов, и их легко спутать. Короткий словарь:
- date — календарная дата без времени: 24 сентября 2026;
- time — время суток без даты: 14:30;
- datetime — дата и время вместе, то есть конкретный момент: 24.09.2026 14:30;
- timedelta — длительность, «сколько прошло»: 7 дней и 3 часа. Это не момент, а разница между моментами;
- tzinfo / timezone — сведения о часовом поясе, которые можно прикрепить к
timeилиdatetime.
Разберем каждый класс, форматирование строк, арифметику дат и часовые пояса, а в конце — типичные ошибки.
Минимальный пример: все классы сразу
from datetime import date, time, datetime, timedelta
d = date(2026, 9, 24) # календарная дата
t = time(14, 30) # время суток без даты
moment = datetime(2026, 9, 24, 14, 30) # дата + время
week = timedelta(days=7) # длительность
print(d, t, moment, sep=" | ")
print("через неделю:", moment + week)
print("день недели:", d.isoweekday()) # 1 = понедельник ... 7 = воскресенье
print("как строка:", moment.strftime("%d.%m.%Y %H:%M"))
Результат:
2026-09-24 | 14:30:00 | 2026-09-24 14:30:00
через неделю: 2026-10-01 14:30:00
день недели: 4
как строка: 24.09.2026 14:30
По умолчанию print выводит даты в формате ISO 8601: год-месяц-день. Сложение datetime + timedelta дает новый datetime, а strftime превращает объект в строку нужного вида.
| Класс | Что хранит | Пример создания | Типичная задача |
|---|---|---|---|
date |
год, месяц, день | date(2026, 9, 24) |
дни рождения, дедлайны без времени |
time |
часы, минуты, секунды, микросекунды | time(14, 30) |
время начала занятия |
datetime |
дата + время (+ пояс) | datetime(2026, 9, 24, 14, 30) |
момент события, логи |
timedelta |
дни, секунды, микросекунды | timedelta(hours=36) |
«через 3 дня», «сколько осталось» |
timezone |
фиксированное смещение от UTC | timezone.utc |
хранение времени в UTC |
Класс date: календарная дата
Конструктор принимает три обязательных аргумента: год, месяц, день.
from datetime import date
d = date(2026, 9, 24)
print(d.year, d.month, d.day) # 2026 9 24
print(d.weekday(), d.isoweekday()) # 3 4 -> четверг
print(d.replace(day=1)) # первое число того же месяца
print(date.fromisoformat("2026-12-31"))
print(d.isoformat())
2026 9 24
3 4
2026-09-01
2026-12-31
2026-09-24
Обратите внимание на два способа узнать день недели: weekday() считает понедельник нулем, а isoweekday() — единицей. Метод replace не меняет исходный объект: объекты datetime неизменяемы, он возвращает новую дату.
Несуществующую дату создать нельзя — модуль проверяет календарь, включая високосные годы:
from datetime import date
d = date(2026, 2, 30)
ValueError: day 30 must be in range 1..28 for month 2 in year 2026
Если дата приходит от пользователя, оборачивайте создание в try/except ValueError и показывайте понятное сообщение. Текущую дату дает date.today() — подробно про получение «сейчас» разобрано в статье о текущей дате и времени в Python.
Класс time: время суток
time хранит часы, минуты, секунды и микросекунды. Все аргументы необязательны, пропущенные равны нулю.
from datetime import time
t = time(9, 5, 30, 250000)
print(t) # 09:05:30.250000
print(t.hour, t.minute, t.second, t.microsecond)
print(t.isoformat(timespec="minutes"))
print(time()) # все поля по умолчанию равны нулю
09:05:30.250000
9 5 30 250000
09:05
00:00:00
Важная граница: к time нельзя прибавить timedelta, потому что без даты непонятно, что делать при переходе через полночь. Для арифметики используйте datetime.
Класс datetime: конкретный момент
datetime объединяет дату и время. Обязательны год, месяц и день, остальные поля по умолчанию нулевые.
from datetime import date, time, datetime
start = datetime(2026, 9, 24, 19, 0)
print(start.date(), "|", start.time()) # разложить на части
print(datetime.combine(date(2026, 10, 1), time(10, 0))) # собрать из частей
print(datetime.fromisoformat("2026-09-24T19:00:00+03:00"))
print(datetime(2026, 9, 24)) # время по умолчанию 00:00
2026-09-24 | 19:00:00
2026-10-01 10:00:00
2026-09-24 19:00:00+03:00
2026-09-24 00:00:00
Текущий момент дают datetime.now() (локальное время компьютера) и datetime.now(timezone.utc) (время в UTC с поясом). С Unix-временем, то есть числом секунд от 1 января 1970 года UTC, работают timestamp() и fromtimestamp():
from datetime import datetime, timezone
ts = 1790000000
print(datetime.fromtimestamp(ts, tz=timezone.utc))
moment = datetime(2026, 9, 24, 14, 30, tzinfo=timezone.utc)
print(moment.timestamp())
2026-09-21 14:13:20+00:00
1790260200.0
Без аргумента tz метод fromtimestamp вернет локальное время машины, поэтому результат будет зависеть от настроек компьютера.
Класс timedelta: арифметика дат
Разность двух datetime — это timedelta. К моменту можно прибавить или вычесть длительность.
from datetime import datetime, timedelta
deadline = datetime(2026, 10, 1, 18, 0)
now = datetime(2026, 9, 24, 14, 30)
left = deadline - now
print(left) # timedelta
print(left.days, left.seconds) # 7 дней и остаток в секундах
print(left.total_seconds() / 3600) # всего часов
print(now + timedelta(hours=36))
print(now - timedelta(weeks=2))
print(timedelta(hours=-1)) # отрицательная длительность
7 days, 3:30:00
7 12600
171.5
2026-09-26 02:30:00
2026-09-10 14:30:00
-1 day, 23:00:00
Здесь две ловушки. Первая: left.seconds — это не все секунды, а только остаток после целых дней (12600 = 3,5 часа). Полную длительность дает total_seconds(). Вторая: отрицательная длительность хранится как «минус один день плюс 23 часа» — это не ошибка, а внутреннее представление.
Почему нельзя прибавить месяц
timedelta умеет дни, секунды, микросекунды, а также миллисекунды, минуты, часы и недели, которые пересчитываются в них. Месяцев и лет нет, потому что их длина плавает.
from datetime import date, timedelta
print(date(2026, 1, 31) + timedelta(months=1))
TypeError: __new__() got an unexpected keyword argument 'months'
Исправление — решить, что значит «плюс месяц» для 31 января, и записать это правило явно. Частый вариант — прижимать день к концу месяца:
import calendar
from datetime import date
def add_months(d: date, months: int) -> date:
total = d.month - 1 + months
year, month = d.year + total // 12, total % 12 + 1
last_day = calendar.monthrange(year, month)[1]
return d.replace(year=year, month=month, day=min(d.day, last_day))
print(add_months(date(2026, 1, 31), 1)) # 2026-02-28
print(add_months(date(2028, 1, 31), 1)) # 2028-02-29, високосный год
print(add_months(date(2026, 11, 15), 3)) # 2027-02-15
print(add_months(date(2026, 3, 31), -1)) # 2026-02-28
Это одно из возможных правил, а не стандарт: биллинг или банковский календарь могут считать иначе. В проектах для такой арифметики часто берут сторонний пакет python-dateutil с relativedelta.
strftime и strptime: дата в строку и обратно
Два метода легко перепутать по названию: strftime (f — format) превращает объект в строку, strptime (p — parse) разбирает строку в datetime.
from datetime import datetime
moment = datetime(2026, 9, 24, 14, 5, 9)
print(moment.strftime("%d.%m.%Y")) # 24.09.2026
print(moment.strftime("%Y-%m-%d %H:%M:%S")) # 2026-09-24 14:05:09
print(moment.strftime("%j-й день года, неделя ISO %V"))
parsed = datetime.strptime("24.09.2026 14:05", "%d.%m.%Y %H:%M")
print(parsed, type(parsed).__name__)
24.09.2026
2026-09-24 14:05:09
267-й день года, неделя ISO 39
2026-09-24 14:05:00 datetime
| Код | Значение | Пример |
|---|---|---|
%Y |
год, 4 цифры | 2026 |
%m |
месяц, 01-12 | 09 |
%d |
день месяца, 01-31 | 24 |
%H |
час, 00-23 | 14 |
%M |
минуты, 00-59 | 05 |
%S |
секунды, 00-59 | 09 |
%j |
день года, 001-366 | 267 |
%A, %B |
название дня недели и месяца | зависит от локали |
%z |
смещение от UTC | +0300 |
Названия дней и месяцев (%A, %B) берутся из текущей локали системы, поэтому на разных машинах могут выйти на английском или на русском. Для стабильного результата в логах и API используйте числовые коды или ISO-формат.
Если строка не совпадает с шаблоном, strptime падает:
from datetime import datetime
datetime.strptime("2026-09-24", "%d.%m.%Y")
ValueError: time data '2026-09-24' does not match format '%d.%m.%Y'
Исправление — шаблон под фактический вид строки: "%Y-%m-%d", а для ISO-строк проще datetime.fromisoformat. Опаснее тихая ошибка, когда исключения нет:
from datetime import datetime
moment = datetime(2026, 9, 24, 14, 5)
print(moment.strftime("%d.%M.%Y")) # ошибка: %M - минуты, а не месяц
print(moment.strftime("%d.%m.%Y")) # исправлено: %m - месяц
24.05.2026
24.09.2026
Код не упал, но вместо сентября в строке стоят минуты. Строчная m — месяц, заглавная M — минуты.
Часовые пояса: naive и aware, модуль zoneinfo
У datetime есть два режима. Naive (наивный) объект не знает пояса: datetime(2026, 9, 24, 19, 0) — это просто «19:00», неясно где. Aware (осведомленный) объект хранит tzinfo и однозначно указывает момент времени.
С Python 3.9 в стандартной библиотеке есть модуль zoneinfo, который берет правила поясов из базы IANA (Europe/Moscow, America/New_York и т. д.):
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
meeting = datetime(2026, 9, 24, 19, 0, tzinfo=ZoneInfo("Europe/Moscow"))
print(meeting) # 2026-09-24 19:00:00+03:00
print(meeting.astimezone(ZoneInfo("Asia/Novosibirsk")))
print(meeting.astimezone(ZoneInfo("America/New_York")))
print(meeting.astimezone(timezone.utc))
now_utc = datetime.now(timezone.utc) # aware-время "сейчас" в UTC
print(now_utc.tzinfo)
2026-09-24 19:00:00+03:00
2026-09-24 23:00:00+07:00
2026-09-24 12:00:00-04:00
2026-09-24 16:00:00+00:00
UTC
Метод astimezone не меняет момент, а показывает его на часах другого пояса. На Linux и macOS база поясов обычно есть в системе; на Windows ее ставят пакетом pip install tzdata, иначе ZoneInfo выдаст ZoneInfoNotFoundError. Сторонний pytz, который часто встречается в старых статьях, для нового кода не нужен.
Сравнивать naive и aware на больше/меньше и вычитать их друг из друга нельзя:
from datetime import datetime, timezone
naive = datetime(2026, 9, 24, 19, 0)
aware = datetime(2026, 9, 24, 16, 0, tzinfo=timezone.utc)
print(naive.tzinfo, aware.tzinfo)
print(naive < aware)
None UTC
TypeError: can't compare offset-naive and offset-aware datetimes
Коварнее проверка на равенство: naive == aware не падает, а молча возвращает False (так с Python 3.3), даже если часы совпадают, — условие просто никогда не сработает. Разность aware - naive тоже дает TypeError: can't subtract offset-naive and offset-aware datetimes.
Исправление — привести оба значения к одному виду: прикрепить пояс к наивному (naive.replace(tzinfo=ZoneInfo("Europe/Moscow")), если точно известно, что это московское время) или везде работать с aware-объектами.
Переход на летнее время
Арифметика внутри одного пояса идет «по настенным часам». В поясах с переходом на летнее время это расходится с реально прошедшим временем:
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo
ny = ZoneInfo("America/New_York")
before = datetime(2026, 11, 1, 0, 30, tzinfo=ny) # за 1,5 часа до перехода на зимнее время (в 2:00)
after = before + timedelta(days=1)
print(before, "->", after)
print("по часам на стене:", after - before)
real = after.astimezone(timezone.utc) - before.astimezone(timezone.utc)
print("реально прошло:", real)
2026-11-01 00:30:00-04:00 -> 2026-11-02 00:30:00-05:00
по часам на стене: 1 day, 0:00:00
реально прошло: 1 day, 1:00:00
1 ноября 2026 года в Нью-Йорке часы переводят назад, поэтому «сутки по календарю» длились 25 часов. Практическое правило: хранить и сравнивать время в UTC, а в местный пояс переводить только для показа пользователю.
Если не получилось: частые симптомы
| Симптом | Причина | Что сделать |
|---|---|---|
ValueError: day ... must be in range |
несуществующая дата | проверить ввод, ловить ValueError |
ValueError: time data ... does not match format |
шаблон strptime не совпадает со строкой |
сверить разделители и порядок полей |
| в дате стоят минуты вместо месяца | %M вместо %m |
месяц — %m, минуты — %M |
TypeError: can't compare offset-naive and offset-aware или == всегда False |
смешаны объекты с поясом и без | привести все к aware |
TypeError: ... unexpected keyword argument 'months' |
в timedelta нет месяцев |
своя функция или relativedelta |
ZoneInfoNotFoundError на Windows |
нет базы поясов | pip install tzdata |
DeprecationWarning у datetime.utcnow() |
метод устарел с Python 3.12 | datetime.now(timezone.utc) |
Выводы
- Модуль datetime встроен в Python:
date— дата,time— время суток,datetime— момент,timedelta— длительность,timezone/ZoneInfo— пояс. - Арифметика идет через
timedelta; месяцев и лет в нем нет, правило «плюс месяц» нужно задавать явно. strftimeформатирует объект в строку,strptimeразбирает строку;%m— месяц,%M— минуты.- Для часовых поясов в новом коде —
zoneinfo(Python 3.9+); время лучше хранить в UTC и переводить в местное только для показа. - Naive и aware объекты нельзя сравнивать между собой, а арифметика в поясе с переходом на летнее время считает по настенным часам.
Где применяется / связь с практикой
Освойте тему на практике
Даты встречаются почти в любом прикладном коде: сроки задач и подписок, расписания, отчеты за период, разбор логов, хранение событий в базе и API с ISO-строками. Ошибки с поясами и форматами — частая причина багов, которые проявляются только у части пользователей или раз в год. Работу со стандартной библиотекой, типами данных и исключениями на практических задачах разбирают на курсе «Python-разработчик. Базовый уровень». Посмотреть формат обучения можно на открытых уроках Otus.
FAQ
Чем import datetime отличается от from datetime import datetime?
В первом случае вы импортируете модуль и пишете datetime.datetime.now(), во втором — сразу класс и пишете datetime.now(). Путаница возникает, когда в одном файле смешаны оба варианта: имя datetime перезаписывается.
Как посчитать возраст в полных годах?
Вычтите годы и отнимите единицу, если день рождения в этом году еще не наступил: today.year - born.year - ((today.month, today.day) < (born.month, born.day)). Деление timedelta.days на 365 дает погрешность из-за високосных лет.
Можно ли хранить дату в базе строкой?
Можно, если это ISO 8601 в одном смещении, лучше в UTC (2026-09-24T14:30:00+00:00): такие строки сортируются правильно и читаются через fromisoformat. Строки с разными смещениями сортируются как текст, а не по моменту: 2026-09-24T02:00:00+03:00 (23:00 UTC накануне) окажется после 2026-09-24T00:30:00+00:00. Но если СУБД поддерживает собственный тип даты и времени, лучше использовать его.



