Functional Weave
Code in Rust

charity.regular-giving-schedule@1.0.0

impl/typescript.ts

1,779 bytes · the TypeScript 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.

import { addDays, parseIsoDate } from "./dates_add_days.ts";  ← from dates.add-days ^1.0.0 · built alongside by fune
import { recurrence } from "./dates_recurrence.ts";  ← from dates.recurrence ^1.0.0 · built alongside by fune
import { type RecurrenceRule } from "./dates_recurrence_types.ts";
import { type GivingFrequency } from "./charity_regular_giving_schedule_types.ts";

const INTERVAL: Record<string, number> = { monthly: 1, quarterly: 3, annually: 12 };

/**
 * 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, rather than drifting to the
 * 28th for ever after February.
 */
export function regularGivingSchedule(
  frequency: GivingFrequency,
  collectionDay: number,
  signUpDate: string,
  leadDays: number,
  endDate: string | null,
  count: number
): readonly string[] {
  const interval = INTERVAL[frequency];
  if (interval === undefined) {
    throw new RangeError(`unknown giving frequency "${frequency}": expected monthly, quarterly or annually`);
  }
  if (!Number.isInteger(collectionDay) || collectionDay < 1 || collectionDay > 31) {
    throw new RangeError(`collectionDay must be 1-31, received ${collectionDay}`);
  }
  if (!Number.isInteger(leadDays) || leadDays < 0) throw new RangeError(`leadDays must not be negative, received ${leadDays}`);
  if (!Number.isInteger(count) || count < 0) throw new RangeError(`count must be a non-negative integer, received ${count}`);
  if (endDate !== null) parseIsoDate(endDate);
  const first = addDays(signUpDate, leadDays);
  const rule: RecurrenceRule = { kind: "monthly-on-day", interval, day: collectionDay, weekday: null };
  if (endDate !== null && endDate < first) return [];
  return recurrence(rule, first, count).filter((d) => endDate === null || d <= endDate);
}