impl/typescript.ts
2,565 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 { bankHolidays } from "./dates_bank_holidays.ts"; ← from dates.bank-holidays ^1.0.0 · built alongside by fune
import { dayOfWeek } from "./dates_day_of_week.ts"; ← from dates.day-of-week ^1.0.0 · built alongside by fune
import { type CourtDeadline, type DeadlineRule } from "./legal_court_deadline_types.ts";
/** A deadline in days by CPR 2.8: clear days, short periods, court-office closures. */
export function courtDeadline(
triggerDate: string,
days: number,
rule: DeadlineRule,
atCourtOffice: boolean,
courtClosedDays: readonly string[],
): CourtDeadline {
parseIsoDate(triggerDate);
if (!Number.isInteger(days) || days < 1) {
throw new RangeError(`days must be an integer of at least 1, received ${days}`);
}
if (rule !== "within" && rule !== "at-least-after" && rule !== "at-least-before") {
throw new RangeError(`unknown deadline rule "${rule}": expected within, at-least-after or at-least-before`);
}
if (atCourtOffice && rule !== "within") {
throw new RangeError("atCourtOffice applies only to a within period (CPR 2.8(5))");
}
if (!atCourtOffice && courtClosedDays.length > 0) {
throw new RangeError("courtClosedDays only apply when atCourtOffice is true");
}
for (const d of courtClosedDays) parseIsoDate(d);
// England and Wales holidays by year, fetched only for the years a count touches.
const holidays = new Map<number, Set<string>>();
const isHoliday = (iso: string): boolean => {
const year = Number(iso.slice(0, 4));
let set = holidays.get(year);
if (set === undefined) {
set = new Set(bankHolidays("england-and-wales", year).map((h) => h.date));
holidays.set(year, set);
}
return set.has(iso);
};
const isNonWorking = (iso: string): boolean => dayOfWeek(iso) >= 6 || isHoliday(iso);
const shortPeriod = days <= 5;
const step = rule === "at-least-before" ? -1 : 1;
// Walk the clear days: the trigger day is never one, and in a short period
// weekends and bank holidays are passed over without counting.
let cursor = triggerDate;
let counted = 0;
while (counted < days) {
cursor = addDays(cursor, step);
if (!shortPeriod || !isNonWorking(cursor)) counted += 1;
}
if (rule !== "within") {
// The act or event falls on the day beyond the last clear day.
return { date: addDays(cursor, step), shortPeriod, extended: false };
}
let date = cursor;
let extended = false;
if (atCourtOffice) {
while (isNonWorking(date) || courtClosedDays.includes(date)) {
date = addDays(date, 1);
extended = true;
}
}
return { date, shortPeriod, extended };
}