Functional Weave
Code in TypeScript

legal.court-deadline Unreviewed

A deadline in days under CPR 2.8: clear days, short periods skip weekends and bank holidays, court-closed days.

1.0.1 · published 2026-10-03 by charlie · Anterra

Pinned by 23 tests, run in TypeScript, Python and Rust.

Unreviewed. This capability’s implementations agree in every language and pass its published test vectors, which were worked out from the official sources cited. But no qualified litigation solicitor has yet checked those vectors, or confirmed that the capability covers the cases it claims. Treat it as a draft. Do not use it for real people, money or decisions without your own expert review. Once a qualified reviewer signs off, this notice is replaced with their name, qualification and the date. Each new version needs fresh sign-off.

Not professional advice. This capability calculates legal figures from published rules. It is a software component for developers, not legal advice. Rules change and every rate here has an effective date. Check that the dates cover your case. Verify results against the official sources listed in its README, and have a litigation solicitor review how you use it, before anyone relies on the output. Provided “as is” under its licence, without warranty.

What it does

Status: needs review by a litigation solicitor before it is published.

The date a period "of days" in the Civil Procedure Rules of England and Wales, a practice direction or a court order leads to, computed by CPR 2.8:

For example

  • courtDeadline(2023-10-20, 3, at-least-before, false, ) → date 2023-10-16, short period true, extended false CPR 2.8 example (i): 3 days before a hearing on Friday 20 October: Monday 16 October
  • courtDeadline(2025-10-20, 3, at-least-before, false, ) → date 2025-10-14, short period true, extended false CPR 2.8(4) example: 3 days before a hearing on Monday 20 October: Tuesday 14 October
  • courtDeadline(2023-10-01, 28, at-least-after, false, ) → date 2023-10-30, short period false, extended false CPR 2.8 example (ii): at least 28 days after notice on 1 October: 30 October

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

export function courtDeadline(triggerDate: string, days: number, rule: DeadlineRule, atCourtOffice: boolean, courtClosedDays: readonly string[]): CourtDeadline
triggerDatedatethe day the period is counted from: service, notice, or for at-least-before the hearing or other event
daysintthe number of days the rule, practice direction or order gives, 1 or more
ruleDeadlineRulewithin: the last day to act; at-least-after: the earliest day for the event; at-least-before: the last day to act before the event
atCourtOfficebooltrue for an act done at the court office (filing), which CPR 2.8(5) lets be done on the next day the office is open; within periods only
courtClosedDaysdate[]extra days the court office is closed besides weekends and bank holidays; [] for none; only with atCourtOffice
returnsCourtDeadline

The types it declares, generated into your project

export type DeadlineRule = "within" | "at-least-after" | "at-least-before";

/** The date, and which parts of CPR 2.8 shaped it. */
export interface CourtDeadline {
  readonly date: string;
  /** 5 days or less, so weekends and bank holidays were not counted (CPR 2.8(4)) */
  readonly shortPeriod: boolean;
  /** the period ended on a day the court office was closed and moved to the next open day (CPR 2.8(5)) */
  readonly extended: boolean;
}

Your code names it in one line, in the file that uses it

import { courtDeadline } from "#fune/legal.court-deadline@^1";
impl/typescript.ts · 65 lines · open · 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 };
}

Install

fune build

With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 3 dependencies, pins them in fune.lock, downloads only the TypeScript package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:

fune add legal.court-deadline
Download for TypeScript legal.court-deadline-1.0.1-typescript.fune · 16,129 bytes sha256 f50e8205708f9cfd4e776b7eae67322633767b02cd93ebee1cccfd46a16c4709

The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./legal.court-deadline-1.0.1-typescript.fune, or fetch it from a terminal with fune pull legal.court-deadline@1.0.1:typescript.

The whole function, every language, is one file too: legal.court-deadline-1.0.1.fune, 22,391 bytes, sha256 4d0f68c186119372dad98ecaf67bd0da6cccbe52fa52ef1d1cff22b249c29a76. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

// fune: before legal.court-deadline

after — your function gets the result and the arguments, and returns the final result.

// fune: after legal.court-deadline

replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.

// fune: replace dates.add-days in legal.court-deadline
// fune: replace dates.bank-holidays in legal.court-deadline
// fune: replace dates.day-of-week in legal.court-deadline

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show legal.court-deadline --steps.

// fune: step legal.court-deadline after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
CPR 2.8 example (i): 3 days before a hearing on Friday 20 October: Monday 16 October 2023-10-20, 3, at-least-before, false, → date 2023-10-16, short period true, extended false
CPR 2.8(4) example: 3 days before a hearing on Monday 20 October: Tuesday 14 October 2025-10-20, 3, at-least-before, false, → date 2025-10-14, short period true, extended false
CPR 2.8 example (ii): at least 28 days after notice on 1 October: 30 October 2023-10-01, 28, at-least-after, false, → date 2023-10-30, short period false, extended false
CPR 2.8 example (iii): within 14 days of service on 2 October: 16 October 2023-10-02, 14, within, false, → date 2023-10-16, short period false, extended false
5 days over Easter skip Good Friday, the weekend and Easter Monday 2026-04-02, 5, within, false, → date 2026-04-13, short period true, extended false
5 days over Christmas and New Year skip both years' bank holidays 2025-12-23, 5, within, false, → date 2026-01-02, short period true, extended false
6 days is not a short period: Easter counts, calendar days only 2026-04-03, 6, within, false, → date 2026-04-09, short period false, extended false
a 7-day period ending on a Saturday stays there for an act out of court 2026-09-12, 7, within, false, → date 2026-09-19, short period false, extended false
the same period for filing at court moves to Monday (CPR 2.8(5)) 2026-09-12, 7, within, true, → date 2026-09-21, short period false, extended true
14 days ending on the August bank holiday: filing is in time on Tuesday 2026-08-17, 14, within, true, → date 2026-09-01, short period false, extended true
Show the other 13 tests
CaseArgumentsExpected
the court office's own closure days move a filing deadline further 2026-12-15, 14, within, true, 2026-12-29, 2026-12-30 → date 2026-12-31, short period false, extended true
a filing deadline on an open day is not moved 2026-09-01, 14, within, true, 2026-09-16 → date 2026-09-15, short period false, extended false
at least 3 days after notice on a Friday: the clear days are Monday to Wednesday 2026-09-18, 3, at-least-after, false, → date 2026-09-24, short period true, extended false
3 days before a Thursday hearing: Monday to Wednesday are clear, so literally the Sunday 2026-09-24, 3, at-least-before, false, → date 2026-09-20, short period true, extended false
7 clear days before a hearing count every calendar day 2026-09-24, 7, at-least-before, false, → date 2026-09-16, short period false, extended false
a short period triggered on a Saturday starts counting on Monday 2026-09-19, 2, within, false, → date 2026-09-22, short period true, extended false
1 day within, from a Friday: Monday 2026-09-18, 1, within, false, → date 2026-09-21, short period true, extended false
zero days is an error 2026-09-18, 0, within, false, → error: days must be an integer of at least 1
CPR 2.8(5) cannot move a before-deadline later 2026-09-24, 3, at-least-before, true, → error: atCourtOffice applies only to a within period
closed days without atCourtOffice are an error, not ignored 2026-09-01, 14, within, false, 2026-09-15 → error: courtClosedDays only apply when atCourtOffice is true
a short period in a year without published bank holidays is an error 2029-03-01, 3, within, false, → error: no bank holiday data
an impossible trigger date is an error 2026-02-30, 3, within, false, → error: not a real calendar date
an unknown rule is an error 2026-09-18, 3, by, false, → error: unknown deadline rule

More from the author

- **Clear days (2.8(2), (3)).** The day the period begins is not counted, and if the period ends by reference to an event, the day of the event is not counted either. - **Short periods (2.8(4)).** In a period of 5 days or less, a Saturday, a Sunday, a bank holiday, Christmas Day or Good Friday does not count. The holidays are England and Wales's, from dates.bank-holidays (which includes Christmas Day and Good Friday); the CPR apply in England and Wales only. - **Court office closed (2.8(5)).** When a period for doing an act **at the court office** ends on a day the office is closed, the act is in time on the next day it is open. Closed means a weekend, a bank holiday, or a day in `courtClosedDays` (the office's own closures, which the caller knows).

Three shapes of rule, each one of the worked examples in CPR 2.8:

| rule | example | answer | |---|---|---| | `within` | particulars of claim within 14 days of service on 2 October | 16 October: the 14th day after | | `at-least-after` | the hearing at least 28 days after notice given on 1 October | 30 October: 28 clear days between | | `at-least-before` | notice served at least 3 days before a hearing on Friday 20 October | Monday 16 October | | `at-least-before`, short | the same, hearing on Monday 20 October | Tuesday 14 October: the weekend does not count |

Every one of those is a vector (2023 for the Friday example, 2025 for the Monday one).

**Literal reading.** For `at-least-before` and `at-least-after` the date returned is the one next to the clear days, even if it is a weekend: three clear days before a Thursday hearing are Monday to Wednesday, so the rule is satisfied by acting on the Sunday before, and that is what comes back. Whether a document served that day is deemed served then is a different rule (CPR 6.14, 6.26) and is not applied here. CPR 2.8(5) is applied only to `within` periods and only when `atCourtOffice` is true: moving a "before" deadline later would break the clear days it exists to protect, so asking for it is an error.

**Not handled.** Periods in months (CPR 2.10: calendar months; use dates.add-months), periods expressed as a date or a time of day, deemed service dates (CPR 6.14, 6.26), extensions by agreement (CPR 2.11, 3.8), and the rules of other courts and tribunals. Short periods and court-office closures need the bank holiday calendar for the years they touch; outside the years dates.bank-holidays publishes, the call fails rather than guessing.

## Source

Civil Procedure Rules, Part 2, rule 2.8 "Time", Ministry of Justice, https://www.justice.gov.uk/courts/procedure-rules/civil/rules/part02, read on 23 September 2026, including all four worked examples. Bank holidays: gov.uk, via dates.bank-holidays.

## Before you rely on this

**Not professional advice.** This capability calculates legal figures from published rules. It is a software component for developers, not legal advice. Rules change and every rate here has an effective date. Check that the dates cover your case. Verify results against the official sources listed above, and have a litigation solicitor review how you use it, before anyone relies on the output. Provided "as is" under its licence, without warranty.

**Unreviewed.** This capability's implementations agree in every language and pass its published test vectors, which were worked out from the official sources cited. But no qualified litigation solicitor has yet checked those vectors, or confirmed that the capability covers the cases it claims. Treat it as a draft. Do not use it for real people, money or decisions without your own expert review. Once a qualified reviewer signs off, this notice is replaced with their name, qualification and the date. Each new version needs fresh sign-off.

1.0.1 marks it unreviewed. The code and the tests are unchanged.

Files

PathBytes
README.md4,085
impl/python.py2,562
impl/rust.rs3,464
impl/typescript.ts2,565
vectors.json5,441