Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
32 lines
1.1 KiB
Python
32 lines
1.1 KiB
Python
import calendar
|
|
from datetime import date
|
|
|
|
STEP = {"monthly": 1, "quarterly": 3, "yearly": 12}
|
|
|
|
|
|
def _clamp(y: int, m: int, day: int) -> date:
|
|
return date(y, m, min(day, calendar.monthrange(y, m)[1]))
|
|
|
|
|
|
def due_in_month(month: date, due_day: int) -> date:
|
|
"""Fälligkeitstag `due_day` im Monat von `month`, auf die Monatslänge
|
|
geklemmt (gleiche Regel wie occurrences)."""
|
|
return _clamp(month.year, month.month, due_day)
|
|
|
|
|
|
def occurrences(rhythm: str, due_day: int, window_start: date, window_end: date,
|
|
item_start: date | None = None, item_end: date | None = None) -> list[date]:
|
|
step = STEP[rhythm]
|
|
lo = max(window_start, item_start) if item_start else window_start
|
|
hi = min(window_end, item_end) if item_end else window_end
|
|
anchor = item_start or window_start
|
|
d = _clamp(anchor.year, anchor.month, due_day)
|
|
out: list[date] = []
|
|
while d <= hi:
|
|
if d >= lo:
|
|
out.append(d)
|
|
total = d.year * 12 + d.month - 1 + step
|
|
y, m0 = divmod(total, 12)
|
|
d = _clamp(y, m0 + 1, due_day)
|
|
return out
|