MidТеория5 min

datetime и время

date, datetime, timedelta, zoneinfo, strftime/strptime и работа с часовыми поясами

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")

Проверь себя

Какой метод возвращает текущую дату и время?

Как получить количество дней между двумя датами?

Как преобразовать строку '30.03.2026' в объект datetime?

Почему рекомендуется хранить даты в UTC, а не в локальном времени?