Класс timedelta. Операции с отрезками времени в Python

Мы уже сталкивались с объектами timedelta, выполняя операции с экземплярами date и datetime, а также при установке часового пояса. Сами по себе промежутки времени не имеют привязки к какой-либо дате или времени суток. Они лишь представляют собой отрезок или вектор времени, который может быть не только абсолютным, но и направленным в прошлое или будущее. Аналогично фразам: "через четыре дня", "пять минут назад", "пробежал дистанцию за 50 секунд".

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

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

from datetime import timedelta
 
a = timedelta(days=100)
b = timedelta(days=1, hours=15, minutes=30)
c = timedelta(weeks=3, days=5)
d = timedelta(seconds=5,
              milliseconds=1230,
              microseconds=590)
 
print(a)  # 100 days, 0:00:00
print(b)  # 1 day, 15:30:00
print(c)  # 26 days, 0:00:00
print(d)  # 0:00:06.230590

Когда таймдельта выводится с помощью print(), неявно вызывается метод __str__() объекта, который приводит данные в такой формат, в котором есть дни, часы, минуты и секунды (микросекунды выражаются дробной частью секунды).


Однако, если мы посмотрим как данные хранятся в самом объекте, например, командой print(a.__repr__(), то увидим, что там есть только свойства days, seconds, microseconds:

datetime.timedelta(days=100)
datetime.timedelta(days=1, seconds=55800)
datetime.timedelta(days=26)
datetime.timedelta(seconds=6, microseconds=230590)

Дело в том, что аргументы, переданные в конструктор при создании экземпляра, в классе преобразуются и сводятся в эти три свойства. Количество микросекунд не может превышать 999999. Если получается больше, то из микросекунд выделяются секунды. Количество секунд не должно быть больше 86399 (в часе 3600 секунд, в сутках 24 часа). Иначе происходит конвертация в дни.

from datetime import timedelta
 
a = timedelta(seconds=86399)
b = timedelta(seconds=86400)
 
print(a)  # 23:59:59
print(b)  # 1 day, 0:00:00
 
print(repr(a))  # datetime.timedelta(seconds=86399)
print(repr(b))  # datetime.timedelta(days=1)

Что касается свойства days, то его пределы от -999999999 до 99999999 включительно (девять девяток).

Столько дней – это огромный промежуток времени – более 2 миллионов лет (а сейчас только начало третьего тысячелетия нашей эры). Поэтому если добавить слишком большую таймдельту к дате, произойдет выброс OverflowError, так как максимальный год в объектах типа date и datetime – это 9999. Кроме того, год не может равняться нулю или быть отрицательным (нельзя уйти в период до нашей эры).

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

from datetime import datetime
 
a = datetime.today()
new_year = datetime(a.year, 1, 1)
 
print(f'Событие произошло {new_year - a} назад')

Пример выполнения:

Событие произошло -143 days, 23:52:31.081623 назад

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

from datetime import timedelta
 
a = timedelta(hours=-8, minutes=30, seconds=-20)
print(a)  # -1 day, 16:29:40
 
print(a.__repr__())
# datetime.timedelta(days=-1, seconds=59380)

Это возможно из-за того, что аргументы не сразу присваиваются полям, а происходит их преобразование.

Если пренебречь аргументом seconds в примере выше, то -8 часов +30 минут дают -7 с половиной часов. Чтобы получить такой промежуток времени во внутреннем представлении таймдельты, надо к минус одному дню (-24 часа) прибавить +59400 секунд, то есть +16 с половиной часов.

Характерной чертой типа timedelta являются арифметические операции, в которых могут участвовать его объекты.

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

Теперь обратимся к "взаимодействиям" таймдельт между собой и с числами.

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

from datetime import timedelta
 
a = timedelta(hours=17)
b = timedelta(days=1)
 
plus = a + b
minus = a - b
print(plus)  # 1 day, 17:00:00
print(minus)  # -1 day, 17:00:00
print(plus == timedelta(hours=41))  # True
print(minus == timedelta(hours=-7))  # True

В примере выше значения переменных plus и minus только на первый взгляд выглядят обратными друг другу. На самом деле положительное значение соответствует количеству часов 41, отрицательное – минус семи часам: 17 часов - 24 часа (1 день) = -7 часов.

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

from datetime import timedelta
 
a = timedelta(days=-2, hours=-9)
print(-a, end='\n\n')  # 2 days, 9:00:00
 
b = timedelta(hours=20, minutes=50)
c = -b
print(c)  # -1 day, 3:10:00
print(b)  # 20:50:00
print(abs(b) == abs(c))  # True

Допустим, мы хотим узнать, сколько раз один промежуток времени укладывается в другой. Например, сколько отрезков в 45 минут содержится в 7 часах, или какую долю составляют 40 минут от 3 часов. Для решения подобных задач используются операции деления, как истинного, так и целочисленного, а также нахождение остатка.

from datetime import timedelta
 
total = timedelta(hours=7)
unit = timedelta(minutes=45)
 
qty = total / unit
print(qty)  # 9.333333333333334
print(type(qty))  # <class 'float'>
 
qty = total // unit
print(qty)  # 9
print(type(qty))  # <class 'int'>
 
r = total % unit
print(r)  # 0:15:00
print(type(r))  # <class 'datetime.timedelta'>
 
qty, r = divmod(total, unit)
print(qty, r)  # 9 0:15:00
 
 
print(timedelta(minutes=30) / timedelta(hours=1.5))
# 0.3333333333333333

Обратите внимание, что класс timedelta умеет работать с вещественными числами. В последнем делении мы передаем аргументу hours вещественное число. В данном случае hours=1.5 соответствует hours=1, minutes=30. Что касается результата, то полчаса от полутора часов составляют одну треть, то есть 0.333….

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

from datetime import timedelta
 
a = timedelta(days=2, hours=6, minutes=30)
b = a * 3
print(b)  # 6 days, 19:30:00
 
c = a * 0.5
print(c)  # 1 day, 3:15:00

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

from datetime import timedelta
 
a = timedelta(seconds=-100)
zero = timedelta(seconds=0)
b = timedelta(seconds=100)
 
print(a < zero < b)  # True

Даты и время в Python. Курс




Все разделы сайта