from typing import List, Optional from .charity_regular_giving_schedule_types import GivingFrequency from .dates_add_days import add_days, parse_iso_date from .dates_recurrence import recurrence from .dates_recurrence_types import RecurrenceRule INTERVAL = {"monthly": 1, "quarterly": 3, "annually": 12} def _is_int(value: object) -> bool: return isinstance(value, int) and not isinstance(value, bool) def regular_giving_schedule( frequency: GivingFrequency, collection_day: int, sign_up_date: str, lead_days: int, end_date: Optional[str], count: int, ) -> List[str]: """Collection dates for a regular gift. Each date is clamped from the chosen day, not from the previous date (dates.recurrence), so a gift on the 31st is taken on 28 February and on 31 March again. """ interval = INTERVAL.get(frequency) if isinstance(frequency, str) else None if interval is None: raise ValueError('unknown giving frequency "%s": expected monthly, quarterly or annually' % (frequency,)) if not _is_int(collection_day) or collection_day < 1 or collection_day > 31: raise ValueError("collectionDay must be 1-31, received %s" % (collection_day,)) if not _is_int(lead_days) or lead_days < 0: raise ValueError("leadDays must not be negative, received %s" % (lead_days,)) if not _is_int(count) or count < 0: raise ValueError("count must be a non-negative integer, received %s" % (count,)) if end_date is not None: parse_iso_date(end_date) first = add_days(sign_up_date, lead_days) rule = RecurrenceRule(kind="monthly-on-day", interval=interval, day=collection_day, weekday=None) if end_date is not None and end_date < first: return [] return [d for d in recurrence(rule, first, count) if end_date is None or d <= end_date]