Search code examples
pythondatetimepython-dateutil

dateutil rruleset: How to combine EXRULE and RDATE correctly?


I have a rruleset with a daily recurrence rule and now I am trying to combine an RDATE with an EXRULE.

from dateutil.rrule import rruleset, rrule, DAILY, FR

rules = rruleset()
daily = rrule(freq=DAILY, dtstart=datetime(2022, 10, 12))
rules.rrule(daily)
not_on_friday = rrule(freq=DAILY, byweekday=FR, dtstart=datetime(2022, 10, 12))
but_on_friday_21th = datetime(2022, 10, 21)
rules.exrule(not_on_friday)
rules.rdate(but_on_friday_21th)
rules.between(datetime(2022,10,12), datetime(2022,10,24))

>>
[datetime.datetime(2022, 10, 13, 0, 0), 
 datetime.datetime(2022, 10, 15, 0, 0), # the 14th is excluded as expected
 datetime.datetime(2022, 10, 16, 0, 0),
 datetime.datetime(2022, 10, 17, 0, 0),
 datetime.datetime(2022, 10, 18, 0, 0),
 datetime.datetime(2022, 10, 19, 0, 0),
 datetime.datetime(2022, 10, 20, 0, 0), 
 datetime.datetime(2022, 10, 22, 0, 0), # but the 21th is also excluded
 datetime.datetime(2022, 10, 23, 0, 0)]

Now, confusingly, when I combine my EXRULE with an EXDATE it works:

rules = rruleset()
daily = rrule(freq=DAILY, dtstart=datetime(2022, 10, 12))
rules.rrule(daily)
not_on_friday = rrule(freq=DAILY, byweekday=FR, dtstart=datetime(2022, 10, 12))
but_also_not_on_the_22th_a_saturday = datetime(2022, 10, 22)
rules.exrule(not_on_friday)
rules.exdate(but_also_not_on_the_22th_a_saturday)
rules.between(datetime(2022,10,12), datetime(2022,10,24))

>>
[datetime.datetime(2022, 10, 13, 0, 0),
 datetime.datetime(2022, 10, 15, 0, 0), # the 14th still excluded
 datetime.datetime(2022, 10, 16, 0, 0),
 datetime.datetime(2022, 10, 17, 0, 0),
 datetime.datetime(2022, 10, 18, 0, 0),
 datetime.datetime(2022, 10, 19, 0, 0),
 datetime.datetime(2022, 10, 20, 0, 0), # the 22th also excluded as expected
 datetime.datetime(2022, 10, 23, 0, 0)]

So, if possible at all, how to combine RDATE and EXRULE in my rruleset?


Solution

  • In your answer you note that exrule is applied last, after all other inclusive rules which actually does appear to be in the RFC. However, at least in dateutil, you can use an rruleset as the argument to exrule, so to accomplish what you want, you can try filtering out the date that you want included from the rule that gets passed to exrule, like so:

    from datetime import datetime
    from dateutil.rrule import rruleset, rrule, DAILY, WEEKLY, FR
    
    # Create an rruleset that defaults to every day
    rules = rruleset()
    daily = rrule(freq=DAILY, dtstart=datetime(2022, 10, 12))
    rules.rrule(daily)
    
    # Create an rruleset corresponding to the days we want to *exclude*: every
    # Friday, except 2022-10-21
    ex_set = rruleset()
    ex_set.rrule(rrule(freq=WEEKLY, byweekday=FR, dtstart=datetime(2022, 10, 14)))
    ex_set.exdate(datetime(2022, 10, 21))
    
    # Use our second rule set as an exrule
    rules.exrule(ex_set)
    
    rules.between(datetime(2022,10,12), datetime(2022,10,24))
    

    Since the date you want to include never appears in the exrule, it is not filtered out:

    >>> print("\n".join(map(str,
    ...                     map(datetime.date,
    ...                         rules.between(datetime(2022, 10, 12),
    ...                                       datetime(2022, 10, 24))))))
    2022-10-13
    2022-10-15
    2022-10-16
    2022-10-17
    2022-10-18
    2022-10-19
    2022-10-20
    2022-10-21
    2022-10-22
    2022-10-23