Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from .dates_add_days import add_days ← from dates.add-days ^1.0.0 · built alongside by fune
from .dates_day_of_week import day_of_week ← from dates.day-of-week ^1.0.0 · built alongside by fune
from .subscriptions_next_billing_date import next_billing_date ← from subscriptions.next-billing-date ^1.0.0 · built alongside by fune
from .todo_item import Recurrence ← from todo.item ^1.0.0 · built alongside by fune
_STEPS = {"daily": "day", "weekly": "week", "monthly": "month", "yearly": "year"}
def next_occurrence(recurrence: Recurrence, due: str, today: str) -> str:
"""The first date of the series strictly after both due and today.
Every occurrence is the anchor plus whole intervals, so month ends stay
month ends; finishing late skips the occurrences already missed.
"""
frequency, interval, anchor = recurrence.frequency, recurrence.interval, recurrence.anchor
if frequency != "weekdays" and frequency not in _STEPS:
raise ValueError(f'unknown repeat frequency "{frequency}": expected daily, weekdays, weekly, monthly or yearly')
if isinstance(interval, bool) or not isinstance(interval, int) or interval < 1:
raise ValueError(f"repeat interval must be 1 or more, received {interval}")
if frequency == "weekdays" and interval != 1:
raise ValueError(f"weekday repeats cannot skip weeks: interval must be 1, received {interval}")
add_days(anchor, 0)
add_days(due, 0)
add_days(today, 0)
if anchor > due:
raise ValueError(f"repeat anchor {anchor} is after the due date {due}")
after = due if due > today else today
if frequency == "weekdays":
nxt = add_days(after, 1)
while day_of_week(nxt) > 5:
nxt = add_days(nxt, 1)
return nxt
return next_billing_date(anchor, _STEPS[frequency], interval, after)