【问题标题】:Is the rrule YEARLY broken for leap days?每年闰日的规则是否被打破?
【发布时间】:2018-10-03 15:12:10
【问题描述】:

假设我想知道什么时候用 rrule 庆祝生日。然后频率 YEARLY 工作正常,除了闰日。实际上只有每 4 年一次。

有没有办法直接用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)]

in the docs 甚至没有提到闰日这一事实让我怀疑这是否只是一个错误。

按年

这可能会有所帮助,但仅限于 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)]

【问题讨论】:

  • 看起来像这样。你的回答是“是”。
  • 它坏了。您可以在github.com/dateutil/dateutil/issues 帮助检查是否已经存在问题,否则请填写新问题。如果您这样做,请将问题编号发回 cmets 中。
  • 被质疑的第3方代码行为确实被破解了。没有答案可以帮助项目的公关。
  • 我把它作为一个问题发布在这里:github.com/dateutil/dateutil/issues/823

标签: python python-dateutil rrule


【解决方案1】:

这是设计使然,实际上在 the rrule documentation 中突出提到,在说明中说:

根据 RFC 第 3.3.10 节,重复实例落在无效日期 并且时间被忽略而不是强制:

循环规则可能会生成无效的循环实例 日期(例如,2 月 30 日)或不存在的当地时间(例如,凌晨 1:30 当地时间在凌晨 1:00 向前移动一个小时的日期)。这样的 重复实例必须被忽略并且不能被算作一部分 的重复集。

自 1991 年 2 月 29 日以来从未存在过,这是一个无效的日期并被跳过。

这是过时的RFC 2445 的限制,后来被RFC 5545 取代,RFC 7529 更新了它。 RFC 7529 将SKIP 参数添加到重复规则中,允许您指定OMIT(默认)、BACKWARDFORWARDdateutil 早于 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

请注意,我在我的规则中使用 base datetime (dtstart),而不是在之前的结果中添加 1 年。原因是relativedelta是有损的,所以将relativedelta(years=1)添加到datetime(1995, 2, 28)会得到datetime(1996, 2, 28)

【讨论】:

  • 啊,是RFC-7529
  • @MartinThoma 我的错误,我从记忆中的 5545 和 7529。7529 是对 5545 的更新,它是 2445 的替代品。我已经更新了帖子。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-17
  • 1970-01-01
  • 2018-08-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多