Functional Weave
Code in Python

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]