Python-规则打破了leap年?
作者:互联网
假设我想得到什么时候用规则来庆祝生日.然后,除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的事实使我想知道这是否仅仅是一个bug.
年复一日
这可能会有所帮助,但仅适用于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)]
解决方法:
这是设计使然,实际上在the rrule documentation中的注释中被突出提及:
Per RFC section 3.3.10, recurrence instances falling on invalid dates
and times are ignored rather than coerced:Recurrence rules may generate recurrence instances with an invalid
date (e.g., February 30) or nonexistent local time (e.g., 1:30 AM on a
day where the local time is moved forward by an hour at 1:00 AM). Such
recurrence instances MUST be ignored and MUST NOT be counted as part
of the recurrence set.由于1991年2月29日从不存在,因此它是无效日期,将被跳过.
这是过时的RFC 2445的限制,之后的版本由RFC 5545取代,而RFC 5545由RFC 7529更新.RFC 7529除其他外,将SKIP参数添加到重复规则中,该规则允许您指定OMIT(默认),BACKWARD或FORWARD . dateutil早于RFC 7529(甚至RFC 5545),并且仍在更新中.您可以在issue #285上跟踪进度.
该特殊问题已在PR #522中得到解决,但PR仍然缺少对一个后备案例的支持,并且尚未合并(截至2018年10月).
对于简单的函数返回年份返回到每月的最后一天的简单情况,我建议改为使用relativedelta(直到发布具有SKIP功能的版本):
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是有损的,因此在datetime(1995,2,28)中加上relativedelta(years = 1)会得到datetime(1996,2,28).
标签:python-dateutil,rrule,python 来源: https://codeday.me/bug/20191025/1924732.html