charity.regular-giving-schedule@1.0.0
impl/python.py
1,840 bytes · the Python implementation · view raw
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
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.add-days ^1.0.0 · built alongside by fune
from .dates_recurrence import recurrence ← from dates.recurrence ^1.0.0 · built alongside by fune
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]