Functional Weave
Code in Rust

legal.court-deadline@1.0.1

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