非常奇怪的规则集行为

Very weird rruleset behavior

使用 pip 上可用的最新 dateutil,在使用循环 DAILY 规则调用 count 方法时,我遇到了奇怪的时间和依赖于顺序的行为。

>>> import dateutil
>>> dateutil.__version__
'2.4.2'
>>> from dateutil import rrule
>>> import datetime

>>> rules = rrule.rruleset()
>>> rules.rrule(rrule.rrule(rrule.DAILY, until=datetime.datetime(2038,1,1,0,0,0)))
>>> rules.count()
8179
>>> rules.exrule(rrule.rrule(rrule.DAILY, until=datetime.datetime(2038,1,1,0,0,0)))
>>> rules.count()
8179  # ??? Expected 0

>>> rules = rrule.rruleset()
>>> rules.exrule(rrule.rrule(rrule.DAILY, until=datetime.datetime(2038,1,1,0,0,0)))
>>> rules.rrule(rrule.rrule(rrule.DAILY, until=datetime.datetime(2038,1,1,0,0,0)))
>>> rules.count()
8179  # ??? Expected 0

>>> rules = rrule.rruleset()
>>> rules.exrule(rrule.rrule(rrule.DAILY, until=datetime.datetime(2038,1,1,0,0,0)))
>>> rules.count()
0
>>> rules.rrule(rrule.rrule(rrule.DAILY, until=datetime.datetime(2038,1,1,0,0,0)))
>>> rules.count()
0  # Now its working???

>>> rules = rrule.rruleset()
>>> rules.exrule(rrule.rrule(rrule.DAILY, until=datetime.datetime(2038,1,1,0,0,0)))
>>> rules.rrule(rrule.rrule(rrule.DAILY, until=datetime.datetime(2038,1,1,0,0,0)))
>>> rules.count()
8179  # ??? Expected 0

>>> rules = rrule.rruleset()
>>> rules.count()
0
>>> rules.rrule(rrule.rrule(rrule.DAILY, until=datetime.datetime(2038,1,1,0,0,0)))
>>> rules.count()
0  # WHAT???
>>> rules.count()
0

>>> rules = rrule.rruleset()
>>> rules.rrule(rrule.rrule(rrule.DAILY, until=datetime.datetime(2038,1,1,0,0,0)))
>>> rules.count()
8179  # IM DONE... WTF

答案很简单,因为您在创建规则集时没有包含 dtstart 参数,如果不包含它,则默认为 datetime.datetime.now() ,即当前时间,并且它包含直到当前微秒的组件。

因此,当您第一次使用 -

创建规则集时
>>> rules = rrule.rruleset()
>>> rules.rrule(rrule.rrule(rrule.DAILY, until=datetime.datetime(2038,1,1,0,0,0)))
>>> rules.count()
8179

您从当前时间开始获取条目,最多微秒。

一段时间后,当您再次尝试时 -

rules.exrule(rrule.rrule(rrule.DAILY, until=datetime.datetime(2038,1,1,0,0,0)))

您再次创建一个 rrule.rrule 对象,从当前时间开始,因此它与您在 rules.

中创建的上一个对象不同

要解决此问题,您可以指定 dtstart 属性以确保它同时启动。

例子-

>>> rules = rrule.rruleset()
>>> rules.rrule(rrule.rrule(rrule.DAILY, until=datetime.datetime(2038,1,1,0,0,0), dtstart=datetime.datetime(now.year,now.month,now.day,0,0,0)))
>>> rules.count()
8179
>>> rules.exrule(rrule.rrule(rrule.DAILY, until=datetime.datetime(2038,1,1,0,0,0), dtstart=datetime.datetime(now.year,now.month,now.day,0,0,0)))
>>> l3 = list(rules)
>>> len(l3)
0
>>> rules.count()
0

类似的问题出现在你的其他例子中。


鉴于以上情况,我认为 dateutil 代码中存在一个问题,当您第一次调用 count() 时,它们实际上缓存了规则集的计数(长度),然后它的正确长度仅为当你遍历它时重新计算等等

问题出现在 rrulebase class 中,这是 ruleset 的基础 class。其中的代码是 (source - https://github.com/dateutil/dateutil/blob/master/dateutil/rrule.py) -

def count(self):
    """ Returns the number of recurrences in this set. It will have go
        trough the whole recurrence, if this hasn't been done before. """
    if self._len is None:
        for x in self:
            pass
    return self._len

因此,如果您之前调用过 .count(),即使在应用 exrule() 之后,它也会继续返回相同的计数。

我不是 100% 确定它是否是一个错误,或者它是否打算表现得像那样,很可能它是一个错误。

我为此打开了 issue