Как проверить, является ли объект относительной отрицательности отрицательным
У меня есть некоторые проблемы с использованием объектов относительной дельты - не могу определить, является ли "дельта отрицательным". Что я пытаюсь это:
from dateutil.relativedelta import relativedelta
print relativedelta(seconds=-5) > 0
это дает мне True
что противоречит интуиции.
print relativedelta(seconds=5) > 0
также вернуть True
, Есть ли способ проверить, отрицательна ли "дельта", представленная объектом относительных данных?
В настоящее время я использую обходной путь в виде отдельной функции, чтобы проверить, отрицательна ли дельта, но я ожидал, что есть более элегантное решение. Вот код, который я использую:
def is_relativedelta_positive(rel_delta):
is_positive = True
is_positive &= rel_delta.microseconds >= 0
is_positive &= rel_delta.seconds >= 0
is_positive &= rel_delta.minutes >= 0
is_positive &= rel_delta.hours >= 0
is_positive &= rel_delta.days >= 0
return is_positive
2 ответа
relativedelta()
объекты не реализуют необходимые методы сравнения. В Python 2 это означает, что они, таким образом, сравниваются по имени типа, а числа всегда сортируются перед любыми другими объектами; это делает эти объекты больше целочисленных значений независимо от их значений. В Python 3 вы получите TypeError
вместо.
Ваш обход не учитывает абсолютную положительную ценность, relativedelta(years=1, seconds=-5)
переместит вашу дату и время почти на год вперед, так что вряд ли это можно назвать отрицательной дельтой.
Вы должны сравнить отдельные атрибуты вместо этого (так years
, months
, days
, hours
, minutes
, seconds
а также microseconds
). В зависимости от вашего варианта использования может потребоваться преобразовать их в общее количество секунд:
def total_seconds(rd, _yeardays=365.2425, _monthdays=365.2425/12):
"""approximation of the number of seconds in a relative delta"""
# year and month durations are averages, taking into account leap years
total_days = rd.years * _yeardays + (rd.months * _monthdays) + rd.days
total_hours = total_days * 24 + rd.hours
total_minutes = total_hours * 60 + rd.minutes
return total_minutes * 60 + rd.seconds + (rd.microseconds / 1000000)
затем используйте это, чтобы сделать ваши сравнения:
if total_seconds(relativedelta(seconds=-5)) > 0:
total_seconds()
функция производит приближение; Относительные дельты обрабатывают високосные годы и правильное количество дней в месяце, поэтому их фактическое влияние на объект datetime будет различаться в зависимости от этого значения datetime. Тем не менее, вышеупомянутое должно быть достаточно хорошо для большинства случаев. Он полностью игнорирует абсолютные компоненты относительной дельты (hour
, year
, единственные имена, которые устанавливают фиксированное значение, а не дельта).
TL;DR Нет четкого и интуитивного определения сравнения между relativedelta
объекты, поэтому сравнение не осуществляется в dateutil
, Если вы хотите сравнить их, вам нужно сделать произвольный выбор в отношении заказа.
Эта проблема
Семантика сравнений между relativedelta
не определены, потому что relativedelta
Сами объекты не представляют фиксированный период времени. Вы можете увидеть эту проблему на GitHub о том, почему это проблема.
Существуют две основные проблемы сравнения relativedelta
objeccts. Более простым является то, что relativedelta
имеет "абсолютные" компоненты (единственные аргументы), такие как day
, hour
и т. д. Итак, рассмотрим:
from dateutil.relativedelta import relativedelta
from datetime import datetime
rd1 = relativedelta(day=5, hours=5)
rd2 = relativedelta(hours=8)
for i in range(4, 7):
dt = datetime(2014, 1, i)
print((dt + rd1) > (dt + rd2))
# Result:
# True
# False
# False
Так как каждый relativedelta
не представляет фиксированное количество времени, не обязательно имеет смысл сравнивать, какой из них "больше" или "меньше", чем другой.
Другая проблема заключается в том, что даже если вы ограничиваете себя "относительными" компонентами relativedelta
все единицы больше чем week
зависит от того, к чему они добавляются, поэтому:
rd3 = relativedelta(months=1)
rd4 = relativedelta(days=30)
for i in range(1, 4):
dt = datetime(2015, i, 1)
print((dt + rd3) > (dt + rd4))
# Result:
# True
# False
# True
Возможные операции сравнения
Тем не менее, есть несколько возможных определений, которые вы можете осмысленно использовать, если вы хотите полу-произвольное, но непротиворечивое определение "меньше чем" для relativedelta
,
Одна несколько ограниченная версия этого заключается в том, чтобы сказать, что "абсолютные" компоненты вызовут ошибку, и установить фиксированное значение для "относительных" компонентов:
def rd_to_td(rd):
for comp in ['year', 'month', 'day', 'hour', 'minute', 'second',
'microsecond', 'weekday', 'leapdays']:
if getattr(rd, comp) is not None:
raise ValueError('Conversion not supported with component ' + comp)
YEAR_LEN = 365.25
MON_LEN = 30
days = (rd.years or 0) * YEAR_LEN
days += (rd.months or 0) * MON_LEN
return timedelta(days=days, hours=rd.hours, minutes=rd.minutes,
seconds=rd.seconds, microseconds=rd.microseconds)
Ближайшее к универсальному сравнению
Вышеприведенное работает для ограниченных случаев, но, вероятно, наиболее универсальный метод сравнения, который вы можете определить, это просто добавить оба к фиксированной дате и сравнить результаты:
from datetime import datetime
def lt_at_dt(rd1, rd2, dt=datetime(1970, 1, 1)):
return (dt + rd1) < (dt + rd2)
Если вы хотите использовать это в качестве ключа для сортировки (а не для парных сравнений), то же самое определение "меньше чем" можно использовать для преобразования relativedelta
в timedelta
(который является фиксированным периодом времени):
def rd_to_td_at_dt(rd, dt=datetime(1970, 1, 1)):
return (dt + rd1) - dt
Примечание. Два предыдущих определения относятся к более общей операции сравнения между relativedelta
объекты. Чтобы узнать, является ли один из них отрицательным, просто сравните результат с относительной дельтой, представляющей ноль, или преобразуйте в timedelta
одним из вышеперечисленных методов и сравнить с timedelta(0)
,
Наконец, я отмечу, что в следующем выпуске 2.7.0 dateutil
, relativedelta
определит __abs__
( GH PR # 472), так что ваше первоначальное определение положительности может быть уменьшено до abs(rd) == rd
, Однако, как отмечает Мартейн, abs(relativedelta(days=20, hours=-1)) != relativedelta(days=20, hours=-1)
, но по большинству разумных определений эта относительная дельта всегда является положительным смещением.
Элегантный и лаконичный способ проверить, является ли объект relativedelta отрицательным:
from datetime import datetime
from dateutil.relativedelta import relativedelta
def is_negative(rd: relativedelta) -> bool:
''' Check whether a relativedelta object is negative'''
try:
datetime.min + rd
return False
except OverflowError:
return True
Некоторые примеры:
is_negative(relativedelta(hours=1))
>> False
is_negative(relativedelta(hours=0))
>> False
is_negative(relativedelta(hours=-1))
>> True
is_negative(relativedelta(days=1, hours=-1))
>> False
is_negative(relativedelta(days=-1, hours=1))
>> True