Timedelta и работа с интервалами времени в Python

В Python timedelta – это тип данных в модуле datetime, используемый для представления длительности времени или разницы между двумя точками во времени.

Многие наборы данных реального мира содержат даты и время, и одной из распространенных операций в науке о данных является вычисление разницы во времени между двумя точками.

Единицы, которые мы используем для измерения даты и времени (годы, месяцы, дни, часы, минуты, секунды), – не самые простые для работы, особенно когда они имеют неравномерную длину.

Но тип данных timedelta, входящий в состав модуля datetime в Python, справляется со сложностями, связанными с определением временных интервалов.

Давайте начнем с очень краткого ответа на вопрос о том, что собой представляет timedelta в Python, а затем продолжим более подробно.

Что такое timedelta в Python

Два наиболее важных и часто используемых типа данных из модуля datetime – timedelta и datetime. Объекты datetime представляют конкретные точки во времени, например, 1 января 2024 года в 10:30 утра. А объекты timedelta представляют длительности или интервалы между двумя точками. Разница между этими типами аналогична разнице между ответами на вопросы «Когда?» (datetime) и «Как долго?» (timedelta).

Давайте рассмотрим две даты, которые мы можем представить с помощью двух объектов datetime. Вычитание одной даты из другой создает объект timedelta, представляющий временной интервал между датами:

import datetime

first_date = datetime.datetime(year=2024, month=1, day=1)
second_date = datetime.datetime(year=2024, month=5, day=27)
interval = second_date - first_date
print(interval)

# Вывод:
# 147 days, 0:00:00

Результат показывает, что две даты находятся на расстоянии ровно 147 дней друг от друга. В следующем примере мы используем дату и время выполнения кода, вызывая datetime.datetime.now():

first_date = datetime.datetime(year=2024, month=1, day=1)
second_date = datetime.datetime.now()
interval = second_date - first_date

print(second_date)
print(interval)

# Вывод:
# 2024-05-28 08:32:44.871646
# 148 days, 8:32:44.871646

Теперь объект timedelta показывает время, прошедшее между началом 2024 года и датой и временем выполнения нашего кода.

Создание объектов timedelta

В предыдущем разделе мы создали объект timedelta из двух объектов datetime. Но мы также можем создать временной интервал напрямую, вызвав конструктор timedelta():

interval = datetime.timedelta(days=10, seconds=3600)
print(interval)

# Вывод:
# 10 days, 1:00:00

Интервал составляет десять дней и один час. При создании экземпляра timedelta можно использовать и другие единицы времени:

interval = datetime.timedelta(weeks=1, days=3, hours=1)
print(interval)

# Вывод:
# 10 days, 1:00:00

При вызове timedelta() допустимы любые из следующих единиц времени:

  • weeks (недели)
  • days (дни)
  • hours (часы)
  • minutes (минуты)
  • seconds (секунды)
  • milliseconds (миллисекунды)
  • microseconds (микросекунды)

Базовая арифметика с timedelta

С объектами timedelta также можно выполнять арифметические операции. Рассмотрим следующие три объекта datetime, представляющие даты выхода Python 1.0, Python 2.0 и Python 3.0. Вычитание объектов datetime друг из друга создает объект timedelta:

release_date_python_1 = datetime.datetime(year=1991, month=2, day=20)
release_date_python_2 = datetime.datetime(year=2000, month=10, day=16)
release_date_python_3 = datetime.datetime(year=2008, month=12, day=3)

​time_between_1_and_2 = release_date_python_2 - release_date_python_1
time_between_2_and_3 = release_date_python_3 - release_date_python_2

​print(time_between_1_and_2)
print(time_between_2_and_3)

# Вывод:
# 3526 days, 0:00:00
# 2970 days, 0:00:00

Результат показывает количество дней между релизами Python 1.0 и 2.0 и между релизами Python 2.0 и 3.0. Временные интервалы представляют собой объекты timedelta.

Также можно выполнять сложение и вычитание между объектами timedelta и объектами datetime. Давайте к release_date_python_3 прибавим timedelta с days=100:

print(release_date_python_3 + datetime.timedelta(days=100))

# Вывод:
# 2009-03-13 00:00:00

Python 3.0 был выпущен 3 декабря 2008 года. Добавив сто дней к этой дате, мы получим 13 марта 2009 года.

Однако мы также можем выполнять арифметические операции непосредственно с объектами timedelta:

print("Difference between gap 1 and gap 2")
print(time_between_1_and_2 - time_between_2_and_3)

print("Sum of gap 1 and gap 2")
print(time_between_1_and_2 + time_between_2_and_3)

Результат:

Difference between gap 1 and gap 2
556 days, 0:00:00

Sum of gap 1 and gap 2
6496 days, 0:00:00

Вывод подтверждает, что объекты timedelta можно вычитать и складывать. Также возможны умножение и деление:

print("Multiply time interval by 3:")
print(time_between_1_and_2 * 3)

print("Divide time interval by 3:")
print(time_between_1_and_2 / 3)

Результат:

Multiply time interval by 3:
10578 days, 0:00:00

Divide time interval by 3:
1175 days, 8:00:00

Еще объекты timedelta можно сравнивать:

print("Check if gap 1 is greater than gap 2")
print(time_between_1_and_2 > time_between_2_and_3)

# Вывод:
# True

Вывод подтверждает, что интервал между датами выпуска Python 1.0 и Python 2.0 больше, чем интервал между Python 2.0 и Python 3.0.

Ключевые атрибуты и методы timedelta

Объект timedelta представляет временной интервал, храня количество дней, секунд и микросекунд. Эти три единицы – единственные единицы времени, представленные атрибутами в классе timedelta:

interval = datetime.timedelta(
    weeks=1,
    hours=10,
    minutes=22,
    milliseconds=1042,
)

print(f"{interval = }")
print(f"{interval.days = }")
print(f"{interval.seconds = }")
print(f"{interval.microseconds = }")

Результат:

interval = datetime.timedelta(days=7, seconds=37321, microseconds=42000)
interval.days = 7
interval.seconds = 37321
interval.microseconds = 42000

Несмотря на то, что при вызове timedelta() в качестве аргументов можно передавать недели, часы, минуты и миллисекунды (weeks, hours, minutes и milliseconds), конструктор преобразует эти единицы в дни, секунды и микросекунды. Вызовы print() включают f-строки со знаком равенства = для отображения имени переменной и ее значения.

Еще одним полезным инструментом класса timedelta является метод total_seconds(). Он возвращает общий временной интервал в секундах:

print("Total time interval in seconds:")
print(interval.total_seconds())

# Вывод:
# Total time interval in seconds:
# 642121.042

Весь временной интервал в секундах – не то же самое, что значение .seconds, которое показывает только секундную составляющую интервала без учета дней и микросекунд.

Мы можем использовать значение, возвращаемое функцией .total_seconds(), для преобразования временного интервала в любую другую единицу:

print("Total time interval in days:")
print(interval.total_seconds() / 3600 / 24)

# Вывод:
# Total time interval in days:
# 7.431956504629629

Вывод показывает временной интервал в днях, поскольку значение, возвращаемое функцией .total_seconds(), делится на количество секунд в часе и количество часов в сутках.

Пример использования timedelta

Давайте рассмотрим пример. Интернет-магазин компании, продающей книги по Python, регистрирует все продажи. Вот данные по одному покупателю:

data = {
    "customer_id": 3542,
    "sales": [
        {"date": "2024-04-21T13:23:45", "price": 24.99, "quantity": 1},
        {"date": "2024-02-13T10:54:12", "price": 24.99, "quantity": 2},
        {"date": "2024-01-08T20:32:24", "price": 18.99, "quantity": 1},
    ],
}

Нам нужно рассчитать разницу во времени между первым и последним заказами покупателя. В списке, содержащем все продажи, заказы всегда располагаются в хронологическом порядке.

Первым делом нам нужно извлечь даты из этой вложенной структуры и преобразовать их в объекты datetime. К счастью, временные метки имеют стандартный формат ISO 8601 (YYYY-MM-DDTHH:MM:SS), поэтому мы можем использовать удобный метод fromisoformat():

sales = data["sales"]
dates = []
for item in sales:
    date = datetime.datetime.fromisoformat(item["date"])
    dates.append(date)

print(dates)

# Вывод:
# [datetime.datetime(2024, 4, 21, 13, 23, 45), datetime.datetime(2024, 2, 13, 10, 54, 12), datetime.datetime(2024, 1, 8, 20, 32, 24)]

Список dates содержит объекты datetime с датами каждого заказа. Наконец, мы можем вычислить временной интервал между первым и последним заказом:

time_span = dates[0] - dates[-1]
print(time_span)
print(time_span.days)

# Вывод:
# 103 days, 16:51:21
# 103

Разница между первой и последней датами дает нам объект timedelta. Код отображает time_span, который показывает количество дней, часов, минут и секунд. Мы также выводим time_span.days, чтобы показать только целое число дней.

Краткие советы по использованию timedelta

Давайте рассмотрим несколько моментов, которые следует иметь в виду при использовании timedelta в Python:

1. При вызове timedelta() лучше использовать именованные аргументы, чтобы избежать ошибок с единицами измерения:

interval = datetime.timedelta(weeks=1, days=3, hours=1, milliseconds=1354)

2. При нахождении временного интервала между двумя датами в разных часовых поясах timedelta перед вычислением разницы преобразует даты в даты UTC:

import zoneinfo

some_date = datetime.datetime.fromisoformat("2024-01-01T00:00:00")

some_date_lon = some_date.replace(tzinfo=zoneinfo.ZoneInfo("Europe/London"))
some_date_nyc = some_date.replace(tzinfo=zoneinfo.ZoneInfo("America/New_York"))

print(some_date_lon)
print(some_date_nyc)
print(some_date_nyc - some_date_lon)

Результат:

2024-01-01 00:00:00+00:00
2024-01-01 00:00:00-05:00
5:00:00

Дата в some_date показывает начало нового года. Это наивный объект datetime, поскольку в нем нет информации о часовом поясе. А вот some_date_lon и some_date_nyc – это продуманные объекты datetime, поскольку они представляют начало нового года в Лондоне и Нью-Йорке соответственно.

Timedelta, полученная путем вычитания двух объектов с известным временем, показывает пятичасовой интервал, что является разницей во времени UTC.

3. В timedelta не учитывается переход на летнее время.

nyc = zoneinfo.ZoneInfo("America/New_York")

first_time = datetime.datetime.strptime("2024-03-10 1:00am", "%Y-%m-%d %I:%M%p")
first_time = first_time.replace(tzinfo=nyc)

second_time = datetime.datetime.strptime("2024-03-10 3:00am", "%Y-%m-%d %I:%M%p")
second_time = second_time.replace(tzinfo=nyc)

time_difference = second_time - first_time
print("Time difference between the first and second time:")
print(time_difference)

print("Checking the DST status of the first and second time:")
print(f"{first_time.dst() = }")
print(f"{second_time.dst() = }")

Результат:

Time difference between the first and second time:
2:00:00

Checking the DST status of the first and second time:
first_time.dst() = datetime.timedelta(0)
second_time.dst() = datetime.timedelta(seconds=3600)

Два времени представляют 1:00 и 3:00 утра 3 марта 2024 года в Нью-Йорке. Переход на летнее время начался в 2:00 в тот же день, когда часы перевели на 3:00. Разница между этими двумя временами дает timedelta, равную двум часам, хотя из-за перехода на летнее время между этими временными точками прошел только один час.

Если нам нужно реальное количество прошедшего времени, мы можем применить конвертацию в UTC:

first_time_utc = first_time.astimezone(zoneinfo.ZoneInfo("UTC"))
second_time_utc = second_time.astimezone(zoneinfo.ZoneInfo("UTC"))
time_difference_utc = second_time_utc - first_time_utc

print("Actual time difference between the first and second time:")
print(time_difference_utc)

Результат:

Actual time difference between the first and second time:
1:00:00

Преобразование в UTC перед вычитанием объектов datetime дает фактическое время между двумя объектами datetime.

Заключение

Класс timedelta в Python предлагает простые методы для работы с временными интервалами, облегчая работу с периодами времени в различных единицах. Мы рассмотрели, как создавать объекты timedelta и выполнять с ними арифметические операции, включая сложение, вычитание, умножение и деление.

Перевод статьи «Python timedelta: Working With Time Intervals in Python».

1 комментарий к “Timedelta и работа с интервалами времени в Python”

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *