Как проверить, является ли объект относительной отрицательности отрицательным

У меня есть некоторые проблемы с использованием объектов относительной дельты - не могу определить, является ли "дельта отрицательным". Что я пытаюсь это:

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
Другие вопросы по тегам