legal.court-deadline
A deadline in days under CPR 2.8: clear days, short periods skip weekends and bank holidays, court-closed days.
1.0.0 (not the latest) · published 2026-10-03 by charlie · Anterra
Pinned by 23 tests, run in TypeScript, Python and Rust.
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
court_deadline(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 Octobercourt_deadline(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 Octobercourt_deadline(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.
pub fn court_deadline(trigger_date: &str, days: i64, rule: &str, at_court_office: bool, court_closed_days: &[String]) -> CourtDeadline
| trigger_date | date | the day the period is counted from: service, notice, or for at-least-before the hearing or other event |
| days | int | the number of days the rule, practice direction or order gives, 1 or more |
| rule | DeadlineRule | within: 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 |
| at_court_office | bool | true 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 |
| court_closed_days | date[] | extra days the court office is closed besides weekends and bank holidays; [] for none; only with atCourtOffice |
| returns | CourtDeadline |
The types it declares, generated into your project
// DeadlineRule is a string in Rust, one of: "within", "at-least-after", "at-least-before".
// Parameters take it as &str and results hold it as String.
/// The date, and which parts of CPR 2.8 shaped it.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CourtDeadline {
pub date: String,
/// 5 days or less, so weekends and bank holidays were not counted (CPR 2.8(4))
pub short_period: bool,
/// the period ended on a day the court office was closed and moved to the next open day (CPR 2.8(5))
pub extended: bool,
}
Your code names it in one line, in the file that uses it
fune!(legal.court-deadline@^1); // then call court_deadline(…)
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::dates_add_days::{add_days, parse_iso_date}; ← from dates.add-days ^1.0.0 · built alongside by fune
use super::dates_bank_holidays::bank_holidays; ← from dates.bank-holidays ^1.0.0 · built alongside by fune
use super::dates_day_of_week::day_of_week; ← from dates.day-of-week ^1.0.0 · built alongside by fune
/// A deadline in days by CPR 2.8: clear days, short periods, court-office closures.
///
/// # Panics
/// Panics on a bad date, days below 1, an unknown rule, atCourtOffice outside a
/// within period, closed days without atCourtOffice, or a year without
/// published bank holidays when one is needed.
pub fn court_deadline(
trigger_date: &str,
days: i64,
rule: &str,
at_court_office: bool,
court_closed_days: &[String],
) -> CourtDeadline {
parse_iso_date(trigger_date);
if days < 1 {
panic!("days must be an integer of at least 1, received {}", days);
}
if rule != "within" && rule != "at-least-after" && rule != "at-least-before" {
panic!("unknown deadline rule \"{}\": expected within, at-least-after or at-least-before", rule);
}
if at_court_office && rule != "within" {
panic!("atCourtOffice applies only to a within period (CPR 2.8(5))");
}
if !at_court_office && !court_closed_days.is_empty() {
panic!("courtClosedDays only apply when atCourtOffice is true");
}
for d in court_closed_days {
parse_iso_date(d);
}
// England and Wales holidays by year, fetched only for the years a count touches.
let mut holidays: Vec<(i64, Vec<String>)> = Vec::new();
let mut is_non_working = |iso: &str| -> bool {
if day_of_week(iso) >= 6 {
return true;
}
let year: i64 = iso[0..4].parse().unwrap();
if !holidays.iter().any(|(y, _)| *y == year) {
let dates = bank_holidays("england-and-wales", year).into_iter().map(|h| h.date).collect();
holidays.push((year, dates));
}
holidays.iter().find(|(y, _)| *y == year).unwrap().1.iter().any(|d| d == iso)
};
let short_period = days <= 5;
let step = if rule == "at-least-before" { -1 } else { 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 mut cursor = trigger_date.to_string();
let mut counted = 0;
while counted < days {
cursor = add_days(&cursor, step);
if !short_period || !is_non_working(&cursor) {
counted += 1;
}
}
if rule != "within" {
// The act or event falls on the day beyond the last clear day.
return CourtDeadline { date: add_days(&cursor, step), short_period, extended: false };
}
let mut date = cursor;
let mut extended = false;
if at_court_office {
while is_non_working(&date) || court_closed_days.iter().any(|d| *d == date) {
date = add_days(&date, 1);
extended = true;
}
}
CourtDeadline { date, short_period, extended }
}
pub fn court_deadline_to_value(d: &CourtDeadline) -> Value {
Value::obj(vec![
("date", Value::str(&d.date)),
("shortPeriod", Value::Bool(d.short_period)),
("extended", Value::Bool(d.extended)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let closed: Vec<String> = args[4].as_arr().iter().map(|v| v.as_str().to_string()).collect();
court_deadline_to_value(&court_deadline(
args[0].as_str(),
args[1].as_i64(),
args[2].as_str(),
args[3].as_bool(),
&closed,
))
}Install
fune build
With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and its 3 dependencies, pins them in fune.lock, downloads only the Rust 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. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:
fune add legal.court-deadline
The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./legal.court-deadline-1.0.0-rust.fune, or fetch it from a terminal with fune pull legal.court-deadline@1.0.0:rust.
The whole function, every language, is one file too: legal.court-deadline-1.0.0.fune, 21,263 bytes, sha256 2aabeb71f1faec59dad5daef47fd42e1be143465146efb5823a9735bf8b35e32. 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.
| Case | Arguments | Expected | |
|---|---|---|---|
| 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
| Case | Arguments | Expected | |
|---|---|---|---|
| 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.
Files
| Path | Bytes |
|---|---|
| README.md | 2,995 |
| impl/python.py | 2,562 |
| impl/rust.rs | 3,464 |
| impl/typescript.ts | 2,565 |
| vectors.json | 5,441 |