Python dateutil - 使用 BYMONTHDAY 选择最近的一天时出现错误

Python dateutil - bug with choosing closest day with BYMONTHDAY

我正在使用 python 的 dateutil 模块来解析日历中的重复规则。以下规则出现问题:

从 dateutil.rrule 导入 rrulestr

def test():
    rrule = 'FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=30;UNTIL=20180331T2359'
    dtstart = datetime.datetime(2018, 1, 1, 18, 0)
    dates = list(rrulestr(rrule + ';UNTIL=', dtstart = dtstart ))

这导致以下输出(缺少二月):

datetime: 2018-01-30 18:00:00
datetime: 2018-03-30 18:00:00

这是 dateutil 模块中的错误吗?我应该如何修复它?还是我做错了什么?

根据我的回答 ,这是 dateutil 正在实施的 iCalendar RFC 的故意功能,因为 dateutil 实施 RFC 2445 并且不支持所有(或大多数)更新后的 RFC 5545 的功能。RFC 2445 的相关部分:

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.

2 月缺失,因为 2018-02-30 是无效日期(实际上是 RFC 中指定的示例)。

需要注意的一件事是 this pull request 实现了您想要的功能,但它(在撰写本文时)当前处于阻塞状态,等待 BYWEEKNOSKIP 的支持。合并后,您将能够修改您的 RRULE:

rrule = ('FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=30;UNTIL=20180331T2359;'+
         'SKIP=BACKWARD;RSCALE=GREGORIAN')

在那之前,您最好的选择可能是使用 BYMONTHDAY=28,然后在结果中添加 relativedelta(day=30),例如:

from dateutil.rrule import rrule, MONTHLY
from dateutil.relativedelta import relativedelta

def end_of_month(dtstart, until):
    rr = rrule(freq=MONTHLY, interval=1, bymonthday=28,
               dtstart=dtstart, until=until)

    for dt in rr:
        yield dt + relativedelta(day=30)

这是可行的,因为 28 号存在于所有月份(因此 rrule 将始终生成它)并且 relativedelta 具有您正在寻找的 "fall backwards at end of month" 行为。为了100%的安全,你可以选择bymonthday=1,在这种情况下是等价的。