规则在YEARLY打破了吗?

时间:2018-10-03 15:12:11

标签: python python-dateutil rrule

假设我想得到什么时候用规则来庆祝生日。然后,除leap日外,YEARLY的频率工作正常。实际上只有每四年一次。

有什么办法可以直接用rrule处理吗?

from datetime import datetime
from dateutil.rrule import rrule, YEARLY

n = 1
print(list(rrule(freq=YEARLY, count=n + 1, dtstart=datetime(1990, 4, 28))))
print(list(rrule(freq=YEARLY, count=n + 1, dtstart=datetime(1992, 2, 29))))

给予

[datetime.datetime(1990, 4, 28, 0, 0), datetime.datetime(1991, 4, 28, 0, 0)]
[datetime.datetime(1992, 2, 29, 0, 0), datetime.datetime(1996, 2, 29, 0, 0)]

leap日甚至没有提到的事实in the docs让我想知道这是否仅仅是个错误。

byyearday

这可能会有所帮助,但仅限2月28日:

from datetime import datetime
from dateutil.rrule import rrule, YEARLY

n = 5

bday = datetime(1990, 4, 28)
print(list(rrule(freq=YEARLY,
                 byyearday=bday.timetuple().tm_yday,
                 count=n + 1,
                 dtstart=bday)))

bday = datetime(1992, 2, 29)
print(list(rrule(freq=YEARLY,
                 byyearday=bday.timetuple().tm_yday,
                 count=n + 1,
                 dtstart=bday)))

给予

[datetime.datetime(1990, 4, 28, 0, 0), datetime.datetime(1991, 4, 28, 0, 0), datetime.datetime(1992, 4, 27, 0, 0), datetime.datetime(1993, 4, 28, 0, 0), datetime.datetime(1994, 4, 28, 0, 0), datetime.datetime(1995, 4, 28, 0, 0)]
[datetime.datetime(1992, 2, 29, 0, 0), datetime.datetime(1993, 3, 1, 0, 0), datetime.datetime(1994, 3, 1, 0, 0), datetime.datetime(1995, 3, 1, 0, 0), datetime.datetime(1996, 2, 29, 0, 0), datetime.datetime(1997, 3, 1, 0, 0)]

1 个答案:

答案 0 :(得分:4)

这是设计使然,实际上在the rrule documentation的注释中被突出提及:

  

根据RFC第3.3.10节,重复发生日期属于无效日期   时间会被忽略而不是被强迫:

     
    

重复规则可能会生成无效的重复实例     日期(例如2月30日)或不存在的本地时间(例如,美国东部时间上午1:30)     将当地时间提前1个小时的一天)。这样     重复发生的实例必须被忽略,并且不能被视为一部分     重复集。

  

由于1991年2月29日从不存在,因此它是无效日期,将被跳过。

这是过时的RFC 2445的局限性,后来已被RFC 5545取代,而RFC 7529issue #285更新。 RFC 7529除其他外,将SKIP参数添加到重复规则,该参数允许您指定OMIT(默认),BACKWARDFORWARDdateutil早于RFC 7529(甚至RFC 5545),并且仍在更新中。您可以在PR #522上跟踪进度。

{{3}}中已解决此特定问题,但PR仍缺少对一个后备案例的支持,并且尚未合并(截至2018年10月)。

对于一个简单的函数,它返回每年的同一天,又回到每月的最后一天的简单情况,我建议改用relativedelta(直到发布具有SKIP功能的版本): / p>

from dateutil import relativedelta
from datetime import datetime

def yearly_rule(dtstart, count=None):
    n = 0
    while count is None or n < count:
        yield dtstart + relativedelta.relativedelta(years=n)
        n += 1

if __name__ == "__main__":
    for dt in yearly_rule(datetime(1992, 2, 29), count=5):
        print(dt)

    # Prints:
    # 1992-02-29 00:00:00
    # 1993-02-28 00:00:00
    # 1994-02-28 00:00:00
    # 1995-02-28 00:00:00
    # 1996-02-29 00:00:00

请注意,我在规则中使用的是基准日期时间dtstart),而不是在以前的结果中加上1年。这样做的原因是relativedelta有损,因此在relativedelta(years=1)上加上datetime(1995, 2, 28)会得到datetime(1996, 2, 28)