Skip to content

Fix rrule byweekno for days in the previous year's last ISO week - #1537

Open
vineethsaivs wants to merge 1 commit into
dateutil:masterfrom
vineethsaivs:fix/rrule-byweekno-prev-year-weeks
Open

Fix rrule byweekno for days in the previous year's last ISO week#1537
vineethsaivs wants to merge 1 commit into
dateutil:masterfrom
vineethsaivs:fix/rrule-byweekno-prev-year-weeks

Conversation

@vineethsaivs

@vineethsaivs vineethsaivs commented Jul 15, 2026

Copy link
Copy Markdown

Summary

rrule(..., byweekno=...) misattributes the first days of a year that actually belong to the last ISO week of the previous year.

_iterinfo.rebuild() computes lnumweeks, the last week number of the previous year, to decide whether early-January spillover days should be marked for a given byweekno. It computed that value from the current year's length with a hardcoded 52:

if lno1wkst >= 4:
    lno1wkst = 0
    lnumweeks = 52+(lyearlen + (lyearweekday-rr._wkst) % 7) % 7//4
else:
    lnumweeks = 52+(self.yearlen-no1wkst) % 7//4   # uses THIS year's yearlen/no1wkst

The parallel current-year block a few lines above already does it correctly, using divmod on the week-adjusted length. Because the previous-year block used the wrong year's variables (and hardcoded 52 instead of the real div), lnumweeks came out one too high in some years.

Reproduction

2011-01-01 and 2011-01-02 are ISO week 52 of 2010 (2010 has no week 53), confirmed by date.isocalendar():

>>> from dateutil.rrule import rrule, YEARLY, SA, SU
>>> from datetime import datetime
>>> list(rrule(YEARLY, count=2, byweekno=52, byweekday=(SA, SU),
...            dtstart=datetime(2011, 1, 1, 9, 0)))
# before: [datetime(2011, 12, 31, 9, 0), datetime(2012, 1, 1, 9, 0)]   # skips Jan 1/2
# after:  [datetime(2011, 1, 1, 9, 0),  datetime(2011, 1, 2, 9, 0)]    # correct
>>> list(rrule(YEARLY, byweekno=53, dtstart=datetime(2011, 1, 1, 9, 0),
...            until=datetime(2011, 1, 10, 9, 0)))
# before: [datetime(2011, 1, 1, 9, 0), datetime(2011, 1, 2, 9, 0)]     # week 53 does not exist
# after:  []                                                            # correct

Fix

Compute the previous year's week count the same way the current-year block does, from the previous year's lyearlen / lno1wkst:

if lno1wkst >= 4:
    lno1wkst = 0
    lwyearlen = lyearlen+(lyearweekday-rr._wkst) % 7
else:
    lwyearlen = lyearlen-lno1wkst
ldiv, lmod = divmod(lwyearlen, 7)
lnumweeks = ldiv+lmod//4

Validation

  • New regression test testYearlyByWeekNoLastWeekOfPrevYear (fails before, passes after).
  • A systematic cross-check of byweekno=N against Python's own date.isocalendar() over every week of 1998-2034 had mismatches only in 2011 and 2022 before this change and zero after.
  • The full tests/test_rrule.py suite passes: 562 passed, 1 xfailed (the 561 pre-existing tests plus the new regression test). The documented testYearlyByWeekNoAndWeekDay53 still returns its exact expected values.

Includes an AUTHORS.md entry and a changelog.d news fragment.

The first days of a year can belong to the last ISO week of the
previous year. _iterinfo.rebuild() derived that last week number from
the current year's length (self.yearlen / no1wkst) with a hardcoded
52, instead of the previous year's length, so it was off by one in some
years. For example byweekno=52 skipped 2011-01-01/02 and byweekno=53
wrongly included them, even though those days are ISO week 52 of 2010
(which has no week 53).

Compute the previous year's week count the same way the current-year
block already does (divmod on the week-adjusted length), using the
previous year's lyearlen and lno1wkst. A byweekno-vs-date.isocalendar
cross-check over 1998-2034 now matches for every week, and the full
rrule suite is unchanged.
@vineethsaivs
vineethsaivs force-pushed the fix/rrule-byweekno-prev-year-weeks branch from 5fcc5dc to 993c2ff Compare July 15, 2026 22:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant