Дата, время и часовые пояса

⏱ 35 мин · 🧩 заданий: 5

Типы модуля 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. Поэтому этот пример показан без кнопки запуска.

Правила для бэкенда

  1. Храните в UTC, в базе — тип timestamp with time zone.
  2. Получайте текущее время как datetime.now(timezone.utc), а не datetime.now() или устаревший datetime.utcnow() (он возвращает наивное время).
  3. Показывайте в поясе пользователя: dt.astimezone(ZoneInfo(user.tz)).
  4. В API передавайте ISO 8601 со смещением или Z.
  5. Не храните «локальное время сервера» — сервер могут перенести.

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

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Программа на Python

Напишите 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
Задание 2 Программа на Python

Напишите 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"
Задание 3 Программа на Python

Напишите 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
Задание 4 Один вариант ответа

Что не так с datetime.now() на сервере?

Задание 5 Один вариант ответа

Какой функцией правильно измерять, сколько выполнялся код?

Войдите, чтобы отмечать пройденные уроки.