Класс 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