← Назад к вопросам

Какие знаешь инструменты для подсчета даты и времени через несколько часов?

2.0 Middle🔥 131 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Инструменты для работы с датой и временем в Python

Работать с датами и временем нужно аккуратно. Правильный выбор инструмента избавляет от множества ошибок.

1. datetime (встроенный модуль)

datetime — основной модуль стандартной библиотеки для работы с датами и временем.

from datetime import datetime, timedelta, timezone

# Текущее время (осторожно — naive datetime!)
now_naive = datetime.now()  # ❌ Без временной зоны
print(f"Naive now: {now_naive}")

# Правильно — с временной зоной
now_aware = datetime.now(timezone.utc)  # ✅ С UTC
print(f"Aware now: {now_aware}")

# Добавить часов
future = now_aware + timedelta(hours=5)
print(f"Through 5 hours: {future}")

# Также можно:
future = now_aware + timedelta(
    days=1,
    hours=3,
    minutes=30,
    seconds=45,
    milliseconds=100,
    microseconds=50
)

# Разница между датами
difference = future - now_aware
print(f"Difference: {difference}")
print(f"Days: {difference.days}")
print(f"Seconds: {difference.total_seconds()}")

Проблемы с datetime:

  • Naive datetimes (без timezone) — источник ошибок
  • Нет встроенной работы с временными зонами
  • Сложная парсинг форматов

2. pytz (работа с временными зонами)

import pytz
from datetime import datetime

# Список доступных зон
print(pytz.all_timezones)  # Сотни зон

# Создание datetime в конкретной зоне
from pytz import timezone
moscow_tz = timezone('Europe/Moscow')
now_moscow = datetime.now(moscow_tz)
print(f"Moscow time: {now_moscow}")

# Конвертация между зонами
us_tz = timezone('America/New_York')
now_us = now_moscow.astimezone(us_tz)
print(f"New York time: {now_us}")

# Добавить часов в конкретной зоне
from datetime import timedelta
future = now_moscow + timedelta(hours=5)
print(f"Moscow + 5 hours: {future}")

# Пример: ночной перерыв в компании
class BusinessHours:
    def __init__(self, tz_name: str):
        self.tz = timezone(tz_name)
    
    def add_business_hours(self, dt: datetime, hours: int) -> datetime:
        """Добавить рабочие часы (9:00-17:00), пропускающие выходные"""
        current = dt.replace(tzinfo=self.tz)
        
        while hours > 0:
            current += timedelta(hours=1)
            # Если ночь (раньше 9:00) — пропускаем
            if current.hour < 9:
                current = current.replace(hour=9, minute=0, second=0)
            # Если выходной или после 17:00 — следующий день
            if current.hour >= 17 or current.weekday() >= 5:
                current = current.replace(hour=9, minute=0, second=0) + timedelta(days=1)
            
            hours -= 1
        
        return current

bh = BusinessHours('Europe/Moscow')
current = datetime.now(timezone('Europe/Moscow'))
future = bh.add_business_hours(current, 5)
print(f"After 5 business hours: {future}")

3. dateutil (расширения datetime)

from dateutil import parser, rrule, relativedelta
from dateutil.tz import tzlocal, gettz
from datetime import datetime

# Парсинг разных форматов (очень гибко)
dates = [
    "2024-03-22",
    "22/03/2024",
    "March 22, 2024",
    "22-Mar-2024 15:30:00",
    "2024-03-22T15:30:00+03:00",
]

for date_str in dates:
    dt = parser.parse(date_str)
    print(f"{date_str} -> {dt}")

# relativedelta — более удобно для смещений
from dateutil.relativedelta import relativedelta

now = datetime.now()

# Через 5 часов
through_hours = now + relativedelta(hours=5)

# Через 1 месяц
through_month = now + relativedelta(months=1)

# Следующий день в 9:00
next_day_9am = (now + relativedelta(days=1)).replace(hour=9, minute=0, second=0)

# Повторяющиеся события (напоминания)
for i, occurrence in enumerate(rrule.rrule(rrule.DAILY, count=5, dtstart=now)):
    print(f"Occurrence {i+1}: {occurrence}")

# Каждый вторник
for i, occurrence in enumerate(rrule.rrule(
    rrule.WEEKLY,
    byweekday=rrule.TU,  # Tuesday
    count=5,
    dtstart=now
)):
    print(f"Tuesday {i+1}: {occurrence}")

# Каждый месяц в 10 число
for i, occurrence in enumerate(rrule.rrule(
    rrule.MONTHLY,
    bymonthday=10,
    count=5,
    dtstart=now
)):
    print(f"10th of month {i+1}: {occurrence}")

4. Arrow (красивый API для дат)

import arrow

# Текущее время (удобнее, чем datetime)
now = arrow.now('Europe/Moscow')
print(now)  # 2024-03-22T15:30:45.123456+03:00

# Через 5 часов
future = now.shift(hours=5)
print(future)

# Множество способов:
future = now.shift(
    years=1,
    months=2,
    days=3,
    hours=4,
    minutes=5,
    seconds=6
)

# Красивое форматирование
print(now.format('YYYY-MM-DD HH:mm:ss'))  # 2024-03-22 15:30:45
print(now.format('dddd, MMMM Do YYYY'))   # Friday, March 22nd 2024

# Парсинг
arrow_dt = arrow.get('2024-03-22T15:30:00+03:00')
print(arrow_dt)

# Гуманизированные временные интервалы
from arrow import now as arrow_now

past = arrow.now().shift(days=-3)
print(past.humanize())  # 3 days ago

future = arrow.now().shift(hours=2)
print(future.humanize())  # in 2 hours

# Конвертация между зонами
moscow = arrow.now('Europe/Moscow')
newyork = moscow.to('America/New_York')
print(f"Moscow: {moscow}")
print(f"New York: {newyork}")

5. pendulum (ещё более удобный Arrow)

import pendulum

# Самый эргономичный API
now = pendulum.now('Europe/Moscow')
print(now)  # 2024-03-22T15:30:45.123456+03:00

# Через 5 часов (очень красиво)
future = now.add(hours=5)
print(future)

# Или так
future = now.add(days=1, hours=3, minutes=30)

# Гуманизированные интервалы (с русским языком!)
from pendulum import now as pnow

past = pnow().subtract(days=3)
print(past.diff_for_humans(locale='ru'))  # "3 дня назад"

future = pnow().add(hours=2)
print(future.diff_for_humans(locale='ru'))  # "в 2 часах"

# Красивое форматирование
print(now.format('dddd, MMMM D, YYYY HH:mm:ss'))  # Friday, March 22, 2024 15:30:45

# Period (интервалы времени)
start = pendulum.parse('2024-01-01')
end = pendulum.parse('2024-03-22')
period = start.period_for_humans(end)
print(period)  # 80 days

# Timezone aware by default
moscow_time = pendulum.now('Europe/Moscow')
newyork_time = moscow_time.in_timezone('America/New_York')

6. cron (планирование по расписанию)

from croniter import croniter
from datetime import datetime

# Парсинг cron выражений
cron_expr = "0 9 * * 1-5"  # Каждый рабочий день в 9:00
cron = croniter(cron_expr)

# Следующие 5 запусков
for i in range(5):
    next_run = cron.get_next(datetime)
    print(f"Next run {i+1}: {next_run}")

# Проверить, когда будет следующий запуск
now = datetime.now()
cron = croniter('0 */3 * * *', now)  # Каждые 3 часа
next_run = cron.get_next(datetime)
print(f"Next run in: {(next_run - now).total_seconds() / 3600:.1f} hours")

# Обратное: сколько будет следующий запуск
cron = croniter('0 9 * * *', now)  # Каждый день в 9:00
sleep_time = (cron.get_next(datetime) - now).total_seconds()
print(f"Sleep {sleep_time} seconds until next job")

# APScheduler — для фоновых задач
from apscheduler.schedulers.background import BackgroundScheduler

scheduler = BackgroundScheduler()

def my_task():
    print("Task executed!")

# Запускать каждый день в 9:00
scheduler.add_job(my_task, 'cron', hour=9)

# Запускать каждые 3 часа
scheduler.add_job(my_task, 'interval', hours=3)

# Запускать один раз через 1 час
scheduler.add_job(my_task, 'date', run_date=datetime.now() + timedelta(hours=1))

scheduler.start()

Практический пример: система напоминаний

import pendulum
from typing import Optional
from enum import Enum

class NotificationPriority(Enum):
    LOW = 1
    NORMAL = 2
    HIGH = 3
    URGENT = 4

class ReminderSystem:
    def __init__(self, user_timezone: str = 'Europe/Moscow'):
        self.tz = user_timezone
    
    def schedule_reminder(self, message: str, delay_hours: int, 
                         priority: NotificationPriority) -> dict:
        """Расписать напоминание через N часов"""
        now = pendulum.now(self.tz)
        scheduled_time = now.add(hours=delay_hours)
        
        # Если ночь (раньше 8:00), отложить до 9:00
        if scheduled_time.hour < 8:
            scheduled_time = scheduled_time.start_of_day().add(hours=9)
        
        # Если выходной — отложить на понедельник
        if scheduled_time.weekday() == 6:  # Воскресенье
            scheduled_time = scheduled_time.next(pendulum.MONDAY).start_of_day().add(hours=9)
        
        return {
            "message": message,
            "scheduled_at": scheduled_time.isoformat(),
            "time_until": scheduled_time.diff_for_humans(now),
            "priority": priority.name,
        }
    
    def get_reminders_for_today(self) -> list:
        """Получить все напоминания на сегодня"""
        now = pendulum.now(self.tz)
        return self.reminders.filter(
            lambda r: pendulum.parse(r['scheduled_at']).date() == now.date()
        )

# Использование
reminder_sys = ReminderSystem('Europe/Moscow')

result = reminder_sys.schedule_reminder(
    "Отправить отчёт",
    delay_hours=5,
    priority=NotificationPriority.URGENT
)
print(result)

Сравнение инструментов

┌──────────────┬─────────────┬──────────────┬─────────────┬─────────┐
│ Библиотека   │ Встроенная  │ Удобство API │ Временные з │ Размер  │
├──────────────┼─────────────┼──────────────┼─────────────┼─────────┤
│ datetime     │ ✅          │ ⭐⭐        │ Плохо       │ 0KB     │
│ pytz         │ ✗           │ ⭐⭐        │ ⭐⭐⭐⭐⭐ │ ~2KB    │
│ dateutil     │ ✗           │ ⭐⭐⭐⭐    │ ⭐⭐⭐      │ ~50KB   │
│ arrow        │ ✗           │ ⭐⭐⭐⭐⭐ │ ⭐⭐⭐⭐⭐ │ ~100KB  │
│ pendulum     │ ✗           │ ⭐⭐⭐⭐⭐ │ ⭐⭐⭐⭐⭐ │ ~50KB   │
│ croniter     │ ✗           │ ⭐⭐⭐⭐    │ ⭐⭐        │ ~30KB   │
└──────────────┴─────────────┴──────────────┴─────────────┴─────────┘

Рекомендации

  • Простые операции: datetime + timedelta
  • Работа с зонами: pytz или pendulum
  • Парсинг: dateutil.parser
  • Красивый API: arrow или pendulum
  • Планирование: croniter или APScheduler
  • Production: Используй UTC внутри, конвертируй при отправке пользователю

Золотое правило: Всегда сохраняй UTC в БД, конвертируй в локальную зону только при отображении.