Functional Weave
Code in TypeScript

banking.bacs-processing-date

Bacs three-day cycle: input, processing and entry (arrival) dates for a submission, skipping weekends and bank holidays.

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

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

What it does

The three days of the Bacs cycle a payment file joins: the input day (day 1, the file is accepted), the processing day (day 2) and the entry day (day 3, when a Direct Credit lands in the recipient's account and a Direct Debit is taken). Payroll and supplier runs are planned backwards from day 3, and a bank holiday in the middle moves it.

## The rules

For example

  • bacsProcessingDates(2026-09-23, false) → input day 2026-09-23, processing day 2026-09-24, entry day 2026-09-25 a Wednesday before the cutoff
  • bacsProcessingDates(2026-09-23, true) → input day 2026-09-24, processing day 2026-09-25, entry day 2026-09-28 a Wednesday after the cutoff joins Thursday's cycle
  • bacsProcessingDates(2026-09-25, false) → input day 2026-09-25, processing day 2026-09-28, entry day 2026-09-29 a Friday submission arrives on Tuesday

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 bacsProcessingDates(submissionIso: string, afterCutoff: boolean): BacsCycle
submissionIsodatethe day the file is sent to Bacs
afterCutoffbooltrue when it is sent after the 22:30 input deadline
returnsBacsCycleday 1, day 2 and day 3 of the cycle it joins

The type it declares, generated into your project

/** The three days of one Bacs cycle. */
export interface BacsCycle {
  /** day 1: the file is accepted */
  readonly inputDay: string;
  /** day 2: Bacs processes it and sends it to the receiving banks */
  readonly processingDay: string;
  /** day 3: the money leaves one account and arrives in the other */
  readonly entryDay: string;
}

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

import { bacsProcessingDates } from "#fune/banking.bacs-processing-date@^1";
impl/typescript.ts · 43 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 { addBusinessDays } from "./dates_add_business_days.ts";  ← from dates.add-business-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 { isWeekday } from "./dates_day_of_week.ts";  ← from dates.day-of-week ^1.0.0 · built alongside by fune
import { type BacsCycle } from "./banking_bacs_processing_date_types.ts";

function holidaysFor(years: readonly number[]): string[] {
  const out: string[] = [];
  for (const year of years) {
    for (const holiday of bankHolidays("england-and-wales", year)) out.push(holiday.date);
  }
  return out;
}

function cycleWith(submissionIso: string, afterCutoff: boolean, holidays: readonly string[]): BacsCycle {
  const processingDay = isWeekday(submissionIso) && !holidays.includes(submissionIso);
  const inputDay = processingDay && !afterCutoff ? submissionIso : addBusinessDays(submissionIso, 1, holidays);
  return {
    inputDay,
    processingDay: addBusinessDays(inputDay, 1, holidays),
    entryDay: addBusinessDays(inputDay, 2, holidays),
  };
}

/**
 * The Bacs cycle a file joins. Bacs works on processing days - Monday to
 * Friday except England and Wales bank holidays - and a file joins the cycle
 * whose input day is the day it arrives, if that is a processing day and it
 * arrived before the 22:30 deadline, or the next processing day otherwise.
 * Processing is the next processing day and entry the one after.
 */
export function bacsProcessingDates(submissionIso: string, afterCutoff: boolean): BacsCycle {
  // isWeekday validates the date before its year is read.
  isWeekday(submissionIso);
  const year = Number(submissionIso.slice(0, 4));
  // Only reach for next year's calendar when the cycle really runs into it,
  // so a December submission in the last published year still works as long
  // as its cycle ends inside that year.
  let cycle = cycleWith(submissionIso, afterCutoff, holidaysFor([year]));
  if (cycle.entryDay.slice(0, 4) !== submissionIso.slice(0, 4)) {
    cycle = cycleWith(submissionIso, afterCutoff, holidaysFor([year, year + 1]));
  }
  return cycle;
}

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 banking.bacs-processing-date
Download for TypeScript banking.bacs-processing-date-1.0.0-typescript.fune · 11,781 bytes sha256 991ba1dbd0a53588caac5444764acd8175f4fdc59e75227f622979049692e9a4

The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./banking.bacs-processing-date-1.0.0-typescript.fune, or fetch it from a terminal with fune pull banking.bacs-processing-date@1.0.0:typescript.

The whole function, every language, is one file too: banking.bacs-processing-date-1.0.0.fune, 16,519 bytes, sha256 d6c142dfcdfc0352df592c4b9d80d19c87383647f6a4e1a3df99f140b852d8cf. 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 banking.bacs-processing-date

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

// fune: after banking.bacs-processing-date

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-business-days in banking.bacs-processing-date
// fune: replace dates.bank-holidays in banking.bacs-processing-date
// fune: replace dates.day-of-week in banking.bacs-processing-date

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 banking.bacs-processing-date --steps.

// fune: step banking.bacs-processing-date 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
a Wednesday before the cutoff 2026-09-23, false → input day 2026-09-23, processing day 2026-09-24, entry day 2026-09-25
a Wednesday after the cutoff joins Thursday's cycle 2026-09-23, true → input day 2026-09-24, processing day 2026-09-25, entry day 2026-09-28
a Friday submission arrives on Tuesday 2026-09-25, false → input day 2026-09-25, processing day 2026-09-28, entry day 2026-09-29
a Saturday submission waits for Monday 2026-09-26, false → input day 2026-09-28, processing day 2026-09-29, entry day 2026-09-30
the cutoff does not matter on a non-processing day 2026-09-26, true → input day 2026-09-28, processing day 2026-09-29, entry day 2026-09-30
Easter: Thursday before Good Friday processes on Tuesday 2026-04-02, false → input day 2026-04-02, processing day 2026-04-07, entry day 2026-04-08
a file sent on Good Friday starts on the Tuesday after Easter Monday 2026-04-03, false → input day 2026-04-07, processing day 2026-04-08, entry day 2026-04-09
Christmas 2026: the substitute Boxing Day is skipped too 2026-12-23, false → input day 2026-12-23, processing day 2026-12-24, entry day 2026-12-29
Christmas Eve after the cutoff 2026-12-24, true → input day 2026-12-29, processing day 2026-12-30, entry day 2026-12-31
New Year's Eve runs into next year's calendar 2026-12-31, false → input day 2026-12-31, processing day 2027-01-04, entry day 2027-01-05
Show the other 8 tests
CaseArgumentsExpected
the summer bank holiday Monday is skipped 2026-08-28, false → input day 2026-08-28, processing day 2026-09-01, entry day 2026-09-02
Christmas 2027 falls at a weekend, both substitute days skipped 2027-12-24, false → input day 2027-12-24, processing day 2027-12-29, entry day 2027-12-30
a December cycle inside the last published year needs no later data 2028-12-22, false → input day 2028-12-22, processing day 2028-12-27, entry day 2028-12-28
the first published year 2019-01-02, false → input day 2019-01-02, processing day 2019-01-03, entry day 2019-01-04
a cycle that runs past the published calendar is an error 2028-12-29, false → error: no bank holiday data for england-and-wales in 2029
a year before the published calendar is an error 2018-06-01, false → error: no bank holiday data for england-and-wales in 2018
a malformed date is an error 2026-9-23, false → error: is not an ISO date
an impossible date is an error 2026-02-30, false → error: not a real calendar date

More from the author

Bacs describes the cycle for Direct Credit as: "Input day is the latest day a business user/bureau may submit a payment file to Bacs for a processing cycle. Payment files must be transmitted to Bacs between 07.00 and 22.30"; on day 2 files are delivered to the receiving banks; on day 3 "payments are simultaneously credited to the recipients' accounts and debited from your account". Source: Bacs, "Bacs Direct Credit", https://www.bacs.co.uk/bacs-schemes/getting-started/bacs-direct-credit/ (read 2026-09-23).

The days are Bacs processing days: Monday to Friday, excluding bank holidays in England and Wales, which Bacs uses as its non-processing days for the whole of the UK (Scotland's and Northern Ireland's extra holidays do not stop Bacs). The published Bacs processing calendar is the authority; the bank holidays here come from `dates.bank-holidays` (gov.uk data, region `england-and-wales`), and the working-day steps from `dates.add-business-days`.

So:

- input day = the submission date if it is a processing day and the file was sent by 22:30 (`afterCutoff` false); otherwise the next processing day. A file sent at a weekend or on a bank holiday joins the next processing day whatever the time; - processing day = the next processing day after the input day; - entry day = the processing day after that.

## Why `afterCutoff` is a flag

The time of day is the caller's to read (this capability never reads a clock), and comparing a timestamp with 22:30 is simpler at the call site than passing a time zone in. The 22:30 deadline is the one Bacs publishes (quoted above); a bureau or bank may set an earlier internal deadline, in which case pass `afterCutoff` against that.

## Limits

The bank holiday data covers the years gov.uk publishes (2019 to 2028 in `dates.bank-holidays@1.0.0`). A cycle that needs a year outside it fails with that capability's error rather than treating an unknown Christmas as a working day. Next year's calendar is only consulted when the cycle actually runs into next year, so a December cycle in the last published year still works.

This gives the cycle for a submission. It does not work backwards from a required arrival date; for that, step back two processing days from the entry day with `dates.add-business-days` and the same holiday list.

Files

PathBytes
README.md2,687
impl/python.py2,100
impl/rust.rs2,490
impl/typescript.ts1,977
vectors.json4,159