如何检查 relativedelta 对象是否为负数

How to check if relativedelta object is negative

我在使用 relativedelta 对象时遇到一些问题 - 无法确定 "delta is negative"。我正在尝试的是:

from dateutil.relativedelta import relativedelta
print relativedelta(seconds=-5) > 0

这给了我 True 这是违反直觉的。

print relativedelta(seconds=5) > 0

还有returnTrue。有没有办法检查 relativedata 对象表示的 "delta" 是否为负数?

我目前正在使用单独函数形式的解决方法来检查 delta 是否为负数,但我希望有更优雅的解决方案。这是我正在使用的代码:

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

relativedelta() 对象没有实现必要的比较方法。在 Python 2 中,这意味着它们因此被 通过它们的类型名称 进行比较,并且数字总是排在任何其他对象之前;这使得这些对象无论其值如何都大于整数值。在 Python 3 中,你会得到一个 TypeError

您的变通方法没有考虑绝对正值,relativedelta(years=1, seconds=-5) 会将您的约会时间 几乎 整整一年 向前,所以它很难被命名为负增量。

您必须比较各个属性(因此 yearsmonthsdayshoursminutessecondsmicroseconds)。根据您的用例,您可能必须将这些转换为总秒数:

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()函数产生近似值;相对增量处理闰年和每月的正确天数,因此它们对日期时间对象的实际影响将根据该日期时间值而有所不同。但是,对于大多数情况,上述内容应该足够好了。它确实完全忽略了相对增量的 绝对 组件(houryear、单数名称,表示固定值而不是增量)。

TL;DRrelativedelta对象之间的比较没有明确直观的定义,所以dateutil中没有实现比较。如果你想比较它们,你需要对排序做出任意选择。

问题

relativedelta 之间比较的语义未定义,因为 relativedelta 对象本身并不代表固定的时间段。您可以查看 this issue on github 为什么这是一个问题。

relativedelta 对象之间的比较存在两个主要问题。更直接的一种是 relativedelta 有 "absolute" 个组成部分(单数参数),例如 dayhour 等。所以考虑:

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 并不代表固定的时间量,因此比较哪个是 "bigger" 或 "smaller" 并不一定有意义。

另一个问题是,即使您将自己限制在 relativedelta 的 "relative" 部分,所有大于 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.

的 "less than" 进行半任意但一致的定义,则可以使用一些有意义的定义

一个有点受限的版本是说 "absolute" 组件会抛出错误并为 "relative" 组件设置一个固定值:

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)

如果你想把它作为排序的键(而不是成对比较),"less than" 的相同定义可用于将 relativedelta 转换为 timedelta(这是固定时间段):

def rd_to_td_at_dt(rd, dt=datetime(1970, 1, 1)):
    return (dt + rd1) - dt

注意 前两个定义是关于relativedelta对象之间比较更一般的操作。要知道其中一个是否为 negative,只需将结果与表示零的 relativedelta 进行比较,或者通过上述方法之一转换为 timedelta 并与 timedelta(0).

最后,我会注意到在即将发布的 dateutil 2.7.0 版本中,relativedelta 将定义 __abs__ (GH PR #472),因此您的原始定义积极性可以降低到 abs(rd) == rd。然而,正如 Martijn 指出的那样,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