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);
}