$ sudo teach IT
Модуль 7 · Инструменты Python · Урок 7.4

Дата и время: datetime

Как хранить дату и время не строкой, а объектом, который умеет сравнивать себя с другими датами, форматироваться в любой вид и складываться с промежутками времени

Теория~30 минутНовичокdatetimetimedeltastrftimestrptimedatetime

Рано или поздно в любой настоящей программе появляются даты: когда пользователь зарегистрировался, до какого числа действует подписка, сколько дней осталось до дедлайна. Самый простой способ, который приходит в голову — хранить дату обычной строкой, например "2024-01-15". Но со строкой почти ничего нельзя сделать по-настоящему: нельзя корректно узнать, какая из двух дат раньше, нельзя прибавить к ней 7 дней, нельзя посчитать, сколько дней прошло между двумя датами — придётся вручную резать строку и складывать числа, рискуя ошибиться с количеством дней в месяце или февралём в високосном году.

Python решает эту проблему модулем datetime: он встроен в язык, ничего дополнительно устанавливать не нужно. Модуль даёт готовые объекты, которые умеют сравнивать себя с другими такими же объектами, форматироваться в любую строку и складываться с промежутками времени, как обычные числа с помощью + и -.

Три главных класса модуля

Представьте три разных бланка: один только для даты, второй только для времени и третий — сразу для того и другого. В модуле datetime устроено так же: под разные задачи есть разные объекты, и вы выбираете подходящий, не таская лишние данные там, где они не нужны.

Официально это три отдельных класса внутри модуля datetime: date хранит только дату (год, месяц, день), time хранит только время (час, минута, секунда), а datetime объединяет их и хранит дату и время сразу — это и есть главный, самый ходовой класс, с которым вы будете работать чаще всего. Чтобы использовать любой из них, его нужно подключить командой import, с которой вы уже знакомы:

from datetime import date, time, datetime

Эта строка забирает из модуля datetime сразу три класса: date, time и datetime. После неё к каждому из них можно обращаться напрямую по имени, без точки впереди, и создавать через них объекты — так же, как вы создаёте список через [].

У модуля и у одного из его классов совпадает название — оба называются datetime. Путаницы это не создаёт: вы почти всегда пишете from datetime import datetime и дальше работаете с классом.

date: когда время не важно

Представьте день рождения или дату сдачи отчёта — в обоих случаях важен именно день, а не конкретный час и минута. Хранить время в такой ситуации незачем — лишние данные, которые никто не будет использовать.

Для этого в модуле есть класс date. Он хранит ровно три числа: год, месяц и день. Создать объект можно двумя способами — указать конкретную дату вручную или спросить у Python сегодняшнюю.

from datetime import date

birthday = date(1995, 6, 23)
print(birthday)

today = date.today()
print(today.year)
print(today.month)
print(today.day)

Разберём построчно:

  • birthday = date(1995, 6, 23) — создаём объект вручную: в скобках через запятую передаём год, месяц и день, именно в таком порядке. Месяцы и дни нумеруются как у людей — январь это 1, а не 0.
  • print(birthday) — выведет 1995-06-23: класс date сам превращает себя в строку год-месяц-день через дефис.
  • today = date.today() — today здесь не переменная, а метод самого класса date, который спрашивает у операционной системы сегодняшнюю дату и собирает из неё готовый объект. Вызывается без аргументов.
  • print(today.year), .month, .day — у любого объекта date есть три таких атрибута для отдельного года, месяца и дня.

time: когда важно только время

Теперь наоборот — представьте расписание звонков в школе: "урок начинается в 09:00". Дата тут не нужна, важен только момент внутри суток, который повторяется каждый день.

Для этого есть класс time. Он хранит час, минуту и секунду, но ничего не знает про год, месяц или день. Используется реже, чем date и datetime.

from datetime import time

lunch = time(12, 30, 0)
alarm = time(7)

print(lunch)
print(alarm)

Разберём построчно:

  • lunch = time(12, 30, 0) — создаём время: час, минута, секунда через запятую, в таком порядке.
  • alarm = time(7) — у time обязателен только час, минуты и секунды необязательны: если их не указать, Python сам подставит 0.
  • print(lunch) выведет 12:30:00, а print(alarm) выведет 07:00:00 — класс сам форматирует себя в вид часы:минуты:секунды. Как и у date, у объекта time есть атрибуты .hour, .minute, .second для отдельных частей.

datetime: дата и время вместе

А теперь пример, где нужны оба сразу: момент регистрации пользователя — "15 января в 12:30", а не просто "15 января".

Именно для этого существует класс datetime — он объединяет всё, что умеют date и time, в одном объекте. Это самый используемый класс модуля, и дальше речь в основном пойдёт о нём.

from datetime import datetime

dt = datetime(2024, 1, 15, 12, 30, 0)
print(dt)

dt2 = datetime(2024, 6, 1)
print(dt2)

now = datetime.now()
print(now.year, now.hour, now.minute)

Разберём построчно:

  • dt = datetime(2024, 1, 15, 12, 30, 0) — создаём объект вручную: сначала год, месяц, день (как у date), затем час, минуту, секунду (как у time) — всё через запятую в одном вызове. print(dt) выведет 2024-01-15 12:30:00.
  • dt2 = datetime(2024, 6, 1) — если после дня ничего не передать, Python сам подставит время 00:00:00, так же, как time(7) подставил нулевые минуты. Выведется 2024-06-01 00:00:00.
  • now = datetime.now() — ещё один метод самого класса, как date.today(), только сразу забирает у компьютера и текущую дату, и текущее время.
  • print(now.year, now.hour, now.minute) — у datetime доступны все атрибуты date и time сразу: year, month, day, hour, minute, second. Выведутся текущие год, час и минута.

Совет: если важна только дата — берите date, если дата и время вместе — datetime. Отдельный time нужен редко, обычно для расписания без привязки к дню.

strftime: превращаем дату в красивую строку

Когда вы печатаете объект datetime напрямую, он всегда выглядит одинаково: 2024-03-08 09:05:00. Но людям чаще привычен другой вид, например "08.03.2024". Формат вывода нужно задавать самостоятельно.

Для этого у date, time и datetime есть метод strftime() (от string format time — "отформатировать время в строку"). Ему передают строку-шаблон со специальными кодами со знаком процента, а метод возвращает готовую строку по этому шаблону.

from datetime import datetime

now = datetime(2024, 3, 8, 9, 5, 0)

print(now.strftime("%d.%m.%Y"))
print(now.strftime("%Y-%m-%d"))
print(now.strftime("%H:%M"))

Разберём построчно:

  • now = datetime(2024, 3, 8, 9, 5, 0) — уже знакомое создание объекта: 8 марта 2024 года, 9 часов 5 минут.
  • now.strftime("%d.%m.%Y") — вызываем метод strftime у объекта now и передаём шаблон: %d означает "день, две цифры", %m — "месяц, две цифры", %Y — "год, четыре цифры". Точки между кодами — обычные символы, они попадут в результат как есть. Выведется 08.03.2024.
  • now.strftime("%Y-%m-%d") — те же коды, но в другом порядке и с дефисами. Выведется 2024-03-08.
  • now.strftime("%H:%M") — %H означает час в 24-часовом формате, %M — минуты. Выведется 09:05.

Основных кодов немного, вот самые ходовые:

КОД ЧТО ОЗНАЧАЕТ ПРИМЕР
%YГод, 4 цифры2024
%mМесяц, число (01–12)03
%BМесяц, полное названиеMarch
%dДень месяца (01–31)08
%AДень недели, полное названиеFriday
%HЧас, 24-часовой (00–23)09
%MМинуты (00–59)05

Мнемоника: большая буква — полная версия (%B = March), маленькая — сокращённая. У года наоборот: %Y — четыре цифры, %y — две.

strptime: превращаем строку в дату

Часто бывает наоборот: дата приходит строкой — например, пользователь ввёл её в форме — а работать с ней нужно как с настоящим объектом datetime, чтобы сравнивать её или прибавлять дни.

Для этого есть метод strptime() (от string parse time — "разобрать строку как время"). В отличие от strftime, это метод самого класса datetime, а не объекта: ему передают строку с датой и шаблон, по которому её читать, а он возвращает готовый объект.

from datetime import datetime

s = "15.01.2024"
dt = datetime.strptime(s, "%d.%m.%Y")
print(dt)
print(dt.year)

Разберём построчно:

  • s = "15.01.2024" — обычная строка с датой, как будто её только что ввёл пользователь.
  • dt = datetime.strptime(s, "%d.%m.%Y") — вызываем strptime у самого класса datetime, передаём два аргумента: саму строку и шаблон, описывающий её формат — день, точка, месяц, точка, год. Шаблон должен в точности совпадать со строкой: если бы шаблон остался "%d.%m.%Y", а строка была "2024-01-15", Python выбросил бы ValueError.
  • print(dt) выведет 2024-01-15 00:00:00 — получился полноценный datetime, а время подставилось нулевым, раз в строке его не было.
  • print(dt.year) — у результата strptime доступны все обычные атрибуты datetime. Выведется 2024.

Главная ловушка strptime: шаблон должен совпадать со строкой символ в символ — те же разделители, тот же порядок частей, то же количество цифр. Если они не совпадают хотя бы в одном месте, вы получите ValueError, а не тихо неправильный результат.

timedelta: складываем и сравниваем даты

Раз объекты datetime ведут себя как числа, к ним напрашивается арифметика: "через 7 дней", "сколько дней осталось до дедлайна". Но прибавить к дате просто число нельзя — непонятно, число чего это: дней, часов, секунд?

Поэтому для промежутков времени есть класс timedelta ("разница во времени"). Вы явно указываете, из чего состоит промежуток — дни, часы, минуты — и получившийся объект можно прибавлять к datetime или вычитать из него, тем же оператором + и -, каким складывают числа.

from datetime import datetime, timedelta

now = datetime(2024, 3, 1, 12, 0, 0)

in_7_days = now + timedelta(days=7)
print(in_7_days)

yesterday = now - timedelta(days=1)
print(yesterday)

Разберём построчно:

  • now = datetime(2024, 3, 1, 12, 0, 0) — отправная точка: 1 марта 2024 года, 12:00.
  • timedelta(days=7) — создаём промежуток в 7 дней. Аргумент передан по имени (days=), поэтому Python точно понимает, что это дни, а не часы или минуты — у timedelta есть такие же именованные аргументы hours, minutes, seconds, их можно комбинировать в одном вызове.
  • in_7_days = now + timedelta(days=7) — складываем datetime и timedelta обычным оператором +. Результат снова datetime, только на 7 дней позже: 2024-03-08 12:00:00.
  • yesterday = now - timedelta(days=1) — то же вычитанием: дата на день раньше. Выведется 2024-02-29 12:00:00 — Python сам учёл, что в феврале 2024 года 29 дней, потому что год високосный.

А что если вычесть не промежуток, а одну дату из другой? Получится объект timedelta — так удобно узнавать разницу между двумя датами, а даты дополнительно можно сравнивать обычными операторами.

start = datetime(2024, 1, 1)
end = datetime(2024, 3, 14)

diff = end - start
print(diff.days)
print(start < end)

Разберём построчно:

  • diff = end - start — вычитаем один datetime из другого. Результат — не число, а объект timedelta, представляющий промежуток между ними.
  • print(diff.days) — у timedelta есть атрибут .days с количеством полных дней в промежутке. Выведется 73.
  • print(start < end) — объекты datetime сравниваются теми же операторами, что и числа: <, >, ==, !=, <=, >=. Более ранняя дата считается "меньше". Выведется True, потому что январь раньше марта.

Частые ошибки

Шаблон strptime не совпадает со строкой

datetime.strptime("15.01.2024", "%Y-%m-%d") вызовет ValueError: строка написана через точки и день впереди, а шаблон ждёт дефисы и год впереди. Формат в шаблоне и в строке должен совпадать буквально.

Прибавление обычного числа вместо timedelta

now + 7 вызовет TypeError: datetime не умеет складываться с обычным числом, только с timedelta. Нужно писать now + timedelta(days=7), явно указывая, что это дни.

Перепутанный порядок аргументов в date() и datetime()

date(23, 6, 1995) вместо date(1995, 6, 23) создаст дату с годом 23, а не ошибку — Python не проверяет, похоже ли число на настоящий год. Порядок всегда один: год, месяц, день.

.days у разницы дат округляет вниз, а не до целого

У результата вычитания двух datetime атрибут .days отбрасывает остаток в часах: разница в 1 день 23 часа даст .days равным 1, а не 2. Это нормальное поведение, а не ошибка.

Что важно запомнить

  • Модуль datetime встроен в Python, устанавливать ничего не нужно.
  • date хранит только дату, time — только время, datetime — и то и другое вместе. datetime используется чаще всего.
  • date.today() и datetime.now() — методы самих классов, возвращают текущий момент без аргументов.
  • strftime() превращает объект в строку по шаблону. strptime() делает наоборот: строку в объект. Шаблон обязан совпадать с форматом строки.
  • timedelta — промежуток времени. Его прибавляют к дате или вычитают из неё оператором + и -, а разница двух дат сама возвращает timedelta с атрибутом .days.
  • Даты сравниваются обычными операторами <, >, == — более ранняя дата считается "меньше".

Проверьте себя

7 вопросов

Дней до следующего дня рождения

Напишите функцию days_until(target_day, target_month, from_date), которая принимает день и месяц целевой даты и опорную дату from_date (объект date), и возвращает количество дней от from_date до ближайшей даты с этим числом и месяцем.

Если такая дата в текущем году уже прошла (раньше from_date), считайте её в следующем году. Если дата совпадает с from_date — верните 0.

Например, days_until(5, 3, date(2024, 3, 1)) должна вернуть 4: от 1 марта до 5 марта ровно 4 дня.

Конвертер форматов даты

Premium