Дата, время и часовые пояса
Типы модуля datetime
| Класс | Что хранит | Пример |
|---|---|---|
date |
дату | date(2026, 10, 25) |
time |
время суток | time(14, 30) |
datetime |
дату и время (+ часовой пояс) | datetime(2026, 10, 25, 14, 30) |
timedelta |
промежуток времени | timedelta(days=3, hours=2) |
timezone |
фиксированное смещение от UTC | timezone.utc, timezone(timedelta(hours=3)) |
from datetime import date, time, datetime, timedelta
d = date(2026, 10, 25)
print(d, d.year, d.month, d.day, d.weekday(), d.isoweekday()) # weekday: пн=0 … вс=6
print(d.isocalendar()) # год, номер недели, день недели
dt = datetime(2026, 10, 25, 14, 30, 15)
print(dt, dt.date(), dt.time(), dt.hour)
print(datetime.combine(d, time(9, 0)))
print(date.today() > date(2000, 1, 1))
Арифметика
from datetime import date, datetime, timedelta
deadline = date(2026, 12, 31)
start = date(2026, 10, 25)
left = deadline - start # date - date = timedelta
print(left, left.days)
print(start + timedelta(weeks=2)) # date + timedelta = date
print(datetime(2026, 3, 1, 0, 30) - timedelta(hours=1))
meeting = timedelta(hours=1, minutes=45)
print(meeting.total_seconds() / 60, "минут", meeting * 3)
print(sorted([date(2026, 5, 1), date(2025, 12, 31), date(2026, 1, 15)]))
timedelta не знает про месяцы
timedelta(months=1) не существует: в месяце бывает 28–31 день. Для «через месяц» используют dateutil.relativedelta или считают вручную, учитывая конец месяца.
import calendar
from datetime import date
def add_month(d: date) -> date:
year, month = (d.year + 1, 1) if d.month == 12 else (d.year, d.month + 1)
last_day = calendar.monthrange(year, month)[1] # сколько дней в месяце
return d.replace(year=year, month=month, day=min(d.day, last_day))
print(add_month(date(2026, 1, 31)), add_month(date(2026, 12, 15)), add_month(date(2028, 1, 31)))
print(calendar.isleap(2028), calendar.monthrange(2026, 2))
Форматирование и разбор
from datetime import datetime
dt = datetime(2026, 10, 5, 9, 7, 3)
print(dt.strftime("%d.%m.%Y %H:%M")) # datetime → строка
print(dt.strftime("%Y-%m-%d"), dt.strftime("%A, %d %B"), dt.strftime("%j-й день года"))
print(f"{dt:%d.%m.%y}") # формат прямо в f-строке
parsed = datetime.strptime("05.10.2026 09:07", "%d.%m.%Y %H:%M") # строка → datetime
print(parsed, parsed == dt.replace(second=0))
print(dt.isoformat(), datetime.fromisoformat("2026-10-05T09:07:03+03:00"))
| Код | Значение | Пример |
|---|---|---|
%Y / %y |
год | 2026 / 26 |
%m |
месяц | 10 |
%d |
день | 05 |
%H / %I |
час 24 / 12 | 09 |
%M / %S |
минуты / секунды | 07 / 03 |
%A / %a |
день недели | Monday / Mon |
%B / %b |
месяц | October / Oct |
%z |
смещение | +0300 |
Названия месяцев зависят от локали — для русских названий надёжнее свой словарь или библиотека babel.
ISO 8601
Для API, логов и обмена данными используйте ISO 8601: 2026-10-05T09:07:03+03:00. Он однозначный, сортируется как строка и понимается всеми языками. isoformat() и fromisoformat() — его родные методы.
Наивные и осведомлённые даты
- Наивный (naive)
datetime— без часового пояса.14:00— но где? - Осведомлённый (aware) — с
tzinfo.14:00 UTC+3— однозначный момент времени.
from datetime import datetime, timezone, timedelta
naive = datetime(2026, 10, 25, 14, 0)
utc_now = datetime.now(timezone.utc)
msk = timezone(timedelta(hours=3), "MSK")
moscow = datetime(2026, 10, 25, 14, 0, tzinfo=msk)
print(naive.tzinfo, moscow.tzinfo, moscow.isoformat())
print(moscow.astimezone(timezone.utc)) # тот же момент в UTC
print(utc_now.tzinfo is timezone.utc)
try:
print(naive < moscow)
except TypeError as e:
print("TypeError:", e) # наивные и aware не сравниваются
Часовые пояса: zoneinfo
Фиксированное смещение не учитывает летнее время. Для настоящих поясов есть zoneinfo (Python 3.9+) с базой IANA:
from datetime import datetime
from zoneinfo import ZoneInfo
meeting = datetime(2026, 3, 28, 15, 0, tzinfo=ZoneInfo("Europe/Berlin"))
print(meeting.astimezone(ZoneInfo("Europe/Moscow"))) # 2026-03-28 17:00:00+03:00
print(meeting.astimezone(ZoneInfo("America/New_York"))) # 2026-03-28 10:00:00-04:00
after_dst = datetime(2026, 3, 30, 15, 0, tzinfo=ZoneInfo("Europe/Berlin"))
print(after_dst.utcoffset()) # 2:00:00 — в Берлине перешли на летнее время
На Windows и в браузерном Python базы поясов может не быть — тогда ставят пакет tzdata. Поэтому этот пример показан без кнопки запуска.
Правила для бэкенда
- Храните в UTC, в базе — тип
timestamp with time zone. - Получайте текущее время как
datetime.now(timezone.utc), а неdatetime.now()или устаревшийdatetime.utcnow()(он возвращает наивное время). - Показывайте в поясе пользователя:
dt.astimezone(ZoneInfo(user.tz)). - В API передавайте ISO 8601 со смещением или
Z. - Не храните «локальное время сервера» — сервер могут перенести.
Django при USE_TZ = True делает это сам: в базе UTC, в шаблонах — текущий пояс.
Unix-время и замеры
import time
from datetime import datetime, timezone
ts = time.time() # секунды с 1970-01-01 UTC
print(int(ts))
print(datetime.fromtimestamp(1_700_000_000, tz=timezone.utc))
print(int(datetime(2026, 1, 1, tzinfo=timezone.utc).timestamp()))
start = time.perf_counter() # для измерения длительности
sum(range(300_000))
print(f"{time.perf_counter() - start:.4f} c")
time.time() может прыгнуть назад при синхронизации часов (NTP). Для длительностей — time.perf_counter() или time.monotonic().
Вопрос на собеседовании
«Как хранить время в базе данных?» — В UTC с часовым поясом (timestamptz), в коде использовать aware-объекты (datetime.now(timezone.utc)), а в пояс пользователя переводить только при показе. Наивные даты приводят к ошибкам при переходе на летнее время и переносе серверов.
Итог
date,time,datetime,timedelta; разность дат —timedelta.strftime— в строку,strptime— из строки; для обмена — ISO 8601 (isoformat/fromisoformat).- Наивные даты без пояса — источник ошибок; на сервере
datetime.now(timezone.utc). - Часовые пояса —
zoneinfo.ZoneInfo; храните в UTC, показывайте локально. - Замеры времени —
time.perf_counter().
Практика решено 0 из 5
Напишите age(birthday, today): полное число лет человека на дату today
(обе — datetime.date). День рождения в этом году ещё не наступил — год не засчитывается.
Какие тесты проверяют код (3)
assert age(date(2000, 5, 20), date(2026, 5, 20)) == 26 assert age(date(2000, 5, 20), date(2026, 5, 19)) == 25 assert age(date(2000, 12, 31), date(2026, 1, 1)) == 25
from datetime import date
def age(birthday, today):
years = today.year - birthday.year
if (today.month, today.day) < (birthday.month, birthday.day):
years -= 1
return years
Напишите parse_ru(text): превратить строку "25.10.2026 14:05" в объект datetime,
а format_iso(dt) — превратить datetime обратно в строку ISO 8601 без микросекунд:
"2026-10-25T14:05:00".
Какие тесты проверяют код (2)
assert parse_ru("25.10.2026 14:05") == datetime(2026, 10, 25, 14, 5)
assert format_iso(datetime(2026, 10, 25, 14, 5, 0, 123456)) == "2026-10-25T14:05:00"
from datetime import datetime
def parse_ru(text):
return datetime.strptime(text, "%d.%m.%Y %H:%M")
def format_iso(dt):
return dt.replace(microsecond=0).isoformat()
Напишите working_days(start, end): количество рабочих дней (пн–пт) в интервале
от start до end включительно (обе — date). Праздники не учитываем.
Какие тесты проверяют код (3)
assert working_days(date(2026, 10, 19), date(2026, 10, 25)) == 5 assert working_days(date(2026, 10, 24), date(2026, 10, 25)) == 0 assert working_days(date(2026, 10, 1), date(2026, 10, 31)) == 22
from datetime import date, timedelta
def working_days(start, end):
count = 0
day = start
while day <= end:
if day.weekday() < 5:
count += 1
day += timedelta(days=1)
return count
Что не так с datetime.now() на сервере?
Какой функцией правильно измерять, сколько выполнялся код?
Войдите, чтобы отмечать пройденные уроки.