datetime — работа с датами и временем
Основные типы
Модуль datetime предоставляет четыре основных типа для работы с датами и временем:
| Тип | Описание | Пример |
|---|---|---|
date |
Только дата | 2026-03-30 |
time |
Только время | 14:30:00 |
datetime |
Дата и время | 2026-03-30 14:30:00 |
timedelta |
Разница между датами | 7 days, 3:00:00 |
from datetime import date, time, datetime, timedelta
# date — year, month, day
today = date.today()
print(today) # 2026-03-30
print(today.year) # 2026
print(today.month) # 3
print(today.weekday()) # 0=Monday ... 6=Sunday
# Create specific date
birthday = date(1990, 5, 15)
print(birthday.isoformat()) # 1990-05-15
# time — hour, minute, second
t = time(14, 30, 0)
print(t) # 14:30:00
# datetime — date + time
now = datetime.now()
print(now) # 2026-03-30 14:30:45.123456
# Create specific datetime
event = datetime(2026, 12, 31, 23, 59, 59)
# Extract date and time parts
print(now.date()) # 2026-03-30
print(now.time()) # 14:30:45.123456
timedelta — арифметика дат
from datetime import datetime, timedelta, date
now = datetime.now()
# Create timedelta
one_day = timedelta(days=1)
one_week = timedelta(weeks=1)
timeout = timedelta(minutes=30, seconds=15)
# Date arithmetic
tomorrow = now + one_day
next_week = now + one_week
yesterday = now - one_day
print(f"Завтра: {tomorrow.date()}")
print(f"Через неделю: {next_week.date()}")
# Difference between dates
birthday = date(2026, 5, 15)
today = date.today()
days_until = (birthday - today).days
print(f"До дня рождения: {days_until} дней")
# Duration calculation
start = datetime(2026, 3, 30, 9, 0, 0)
end = datetime(2026, 3, 30, 17, 30, 0)
duration = end - start
print(f"Рабочий день: {duration}") # 8:30:00
print(f"В часах: {duration.total_seconds() / 3600:.1f}") # 8.5
# Compare timedeltas
print(one_week > one_day) # True
print(one_week == timedelta(days=7)) # True
Практические примеры
from datetime import date, timedelta
def is_weekend(d: date) -> bool:
"""Check if date is Saturday or Sunday."""
return d.weekday() >= 5
def next_business_day(d: date) -> date:
"""Get next business day (skip weekends)."""
next_day = d + timedelta(days=1)
while is_weekend(next_day):
next_day += timedelta(days=1)
return next_day
def business_days_between(start: date, end: date) -> int:
"""Count business days between two dates."""
count = 0
current = start
while current < end:
if not is_weekend(current):
count += 1
current += timedelta(days=1)
return count
today = date.today()
print(f"Следующий рабочий день: {next_business_day(today)}")
Форматирование и парсинг строк
strftime — datetime в строку
from datetime import datetime
now = datetime(2026, 3, 30, 14, 30, 45)
# Common format codes
print(now.strftime("%Y-%m-%d")) # 2026-03-30
print(now.strftime("%d.%m.%Y")) # 30.03.2026
print(now.strftime("%H:%M:%S")) # 14:30:45
print(now.strftime("%Y-%m-%dT%H:%M:%S")) # 2026-03-30T14:30:45
# Russian-friendly format
MONTHS_RU = {
1: "января", 2: "февраля", 3: "марта", 4: "апреля",
5: "мая", 6: "июня", 7: "июля", 8: "августа",
9: "сентября", 10: "октября", 11: "ноября", 12: "декабря",
}
def format_date_ru(dt: datetime) -> str:
"""Format date in Russian."""
return f"{dt.day} {MONTHS_RU[dt.month]} {dt.year} г."
print(format_date_ru(now)) # 30 марта 2026 г.
strptime — строка в datetime
from datetime import datetime
# Parse string to datetime
dt1 = datetime.strptime("2026-03-30", "%Y-%m-%d")
dt2 = datetime.strptime("30.03.2026 14:30", "%d.%m.%Y %H:%M")
print(dt1) # 2026-03-30 00:00:00
print(dt2) # 2026-03-30 14:30:00
# ISO format (preferred)
dt_iso = datetime.fromisoformat("2026-03-30T14:30:00")
print(dt_iso) # 2026-03-30 14:30:00
# Format codes:
# %Y = 4-digit year %m = month %d = day
# %H = 24-hour %M = minute %S = second
# %A = weekday name %B = month name
Часовые пояса (zoneinfo)
Начиная с Python 3.9, модуль zoneinfo входит в стандартную библиотеку:
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
# UTC — universal time
utc_now = datetime.now(timezone.utc)
print(f"UTC: {utc_now}")
# Timezone-aware datetime
moscow = ZoneInfo("Europe/Moscow")
new_york = ZoneInfo("America/New_York")
tokyo = ZoneInfo("Asia/Tokyo")
now_moscow = datetime.now(moscow)
now_ny = datetime.now(new_york)
now_tokyo = datetime.now(tokyo)
print(f"Москва: {now_moscow.strftime('%H:%M %Z')}")
print(f"Нью-Йорк: {now_ny.strftime('%H:%M %Z')}")
print(f"Токио: {now_tokyo.strftime('%H:%M %Z')}")
# Convert between timezones
meeting_moscow = datetime(2026, 3, 30, 15, 0, tzinfo=moscow)
meeting_ny = meeting_moscow.astimezone(new_york)
meeting_tokyo = meeting_moscow.astimezone(tokyo)
print(f"Встреча:")
print(f" Москва: {meeting_moscow.strftime('%H:%M')}")
print(f" Нью-Йорк: {meeting_ny.strftime('%H:%M')}")
print(f" Токио: {meeting_tokyo.strftime('%H:%M')}")
# Naive vs Aware
naive = datetime(2026, 3, 30, 12, 0) # No timezone info
aware = datetime(2026, 3, 30, 12, 0, tzinfo=timezone.utc)
print(naive.tzinfo) # None
print(aware.tzinfo) # UTC
# IMPORTANT: Don't compare naive and aware — TypeError!
Правила работы с часовыми поясами
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
# RULE 1: Always store in UTC
def store_event(local_time: datetime) -> datetime:
"""Convert local time to UTC for storage."""
if local_time.tzinfo is None:
raise ValueError("Передайте aware datetime")
return local_time.astimezone(timezone.utc)
# RULE 2: Convert to local only for display
def display_event(utc_time: datetime, user_tz: str) -> str:
"""Convert UTC to user's local time for display."""
local = utc_time.astimezone(ZoneInfo(user_tz))
return local.strftime("%d.%m.%Y %H:%M %Z")
# RULE 3: Use zoneinfo, NOT manual UTC offsets
# BAD: timezone(timedelta(hours=3)) # Doesn't handle DST
# GOOD: ZoneInfo("Europe/Moscow") # Handles DST automatically
Практический пример: планировщик задач
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
from dataclasses import dataclass, field
@dataclass
class ScheduledTask:
"""Task scheduled for execution."""
name: str
scheduled_at: datetime # Always UTC
repeat_every: timedelta | None = None
@property
def is_due(self) -> bool:
"""Check if task should run now."""
now = datetime.now(ZoneInfo("UTC"))
return now >= self.scheduled_at
def next_run(self) -> datetime | None:
"""Calculate next run time."""
if self.repeat_every is None:
return None
return self.scheduled_at + self.repeat_every
@dataclass
class Scheduler:
"""Simple task scheduler."""
tasks: list[ScheduledTask] = field(default_factory=list)
def add(
self,
name: str,
run_at: datetime,
repeat: timedelta | None = None,
) -> None:
if run_at.tzinfo is None:
raise ValueError("Используйте aware datetime")
task = ScheduledTask(
name=name,
scheduled_at=run_at.astimezone(ZoneInfo("UTC")),
repeat_every=repeat,
)
self.tasks.append(task)
def due_tasks(self) -> list[ScheduledTask]:
"""Get all tasks that should run now."""
return [t for t in self.tasks if t.is_due]
def display(self, tz: str = "Europe/Moscow") -> None:
"""Display all tasks in given timezone."""
user_tz = ZoneInfo(tz)
for task in sorted(self.tasks, key=lambda t: t.scheduled_at):
local_time = task.scheduled_at.astimezone(user_tz)
status = "ГОТОВО" if task.is_due else "ожидание"
repeat = f" (каждые {task.repeat_every})" if task.repeat_every else ""
print(f" [{status}] {task.name}: {local_time:%d.%m %H:%M}{repeat}")
# Usage
scheduler = Scheduler()
msk = ZoneInfo("Europe/Moscow")
scheduler.add(
"Бэкап БД",
datetime(2026, 3, 30, 3, 0, tzinfo=msk),
repeat=timedelta(days=1),
)
scheduler.add(
"Отправка отчёта",
datetime(2026, 3, 30, 9, 0, tzinfo=msk),
repeat=timedelta(weeks=1),
)
scheduler.display("Europe/Moscow")