Functional Weave
Code in Python

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

  • bacs_processing_dates(2026-09-23, false) → input day 2026-09-23, processing day 2026-09-24, entry day 2026-09-25 a Wednesday before the cutoff
  • bacs_processing_dates(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
  • bacs_processing_dates(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.

def bacs_processing_dates(submission_iso: str, after_cutoff: bool) -> BacsCycle
submission_isodatethe day the file is sent to Bacs
after_cutoffbooltrue 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

@dataclass(frozen=True)
class BacsCycle:
    """The three days of one Bacs cycle."""

    #: day 1: the file is accepted
    input_day: str
    #: day 2: Bacs processes it and sends it to the receiving banks
    processing_day: str
    #: day 3: the money leaves one account and arrives in the other
    entry_day: str

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

from fune.banking.bacs_processing_date import bacs_processing_dates  # banking.bacs-processing-date@^1
impl/python.py · 46 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.

from typing import List, Sequence

from .banking_bacs_processing_date_types import BacsCycle
from .dates_add_business_days import add_business_days  ← from dates.add-business-days ^1.0.0 · built alongside by fune
from .dates_bank_holidays import bank_holidays  ← from dates.bank-holidays ^1.0.0 · built alongside by fune
from .dates_day_of_week import is_weekday  ← from dates.day-of-week ^1.0.0 · built alongside by fune


def _holidays_for(years: Sequence[int]) -> List[str]:
    out: List[str] = []
    for year in years:
        out.extend(holiday.date for holiday in bank_holidays("england-and-wales", year))
    return out


def _cycle_with(submission_iso: str, after_cutoff: bool, holidays: Sequence[str]) -> BacsCycle:
    processing_day = is_weekday(submission_iso) and submission_iso not in holidays
    input_day = submission_iso if processing_day and not after_cutoff else add_business_days(submission_iso, 1, holidays)
    return BacsCycle(
        input_day=input_day,
        processing_day=add_business_days(input_day, 1, holidays),
        entry_day=add_business_days(input_day, 2, holidays),
    )


def bacs_processing_dates(submission_iso: str, after_cutoff: bool) -> BacsCycle:
    """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.
    """
    if not isinstance(after_cutoff, bool):
        raise TypeError("afterCutoff must be a boolean, received %r" % (after_cutoff,))
    # is_weekday validates the date before its year is read.
    is_weekday(submission_iso)
    year = int(submission_iso[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.
    cycle = _cycle_with(submission_iso, after_cutoff, _holidays_for([year]))
    if cycle.entry_day[0:4] != submission_iso[0:4]:
        cycle = _cycle_with(submission_iso, after_cutoff, _holidays_for([year, year + 1]))
    return cycle

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 3 dependencies, pins them in fune.lock, downloads only the Python 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 Python banking.bacs-processing-date-1.0.0-python.fune · 11,899 bytes sha256 6dcda6bef20da7f653c34f6769936da693d91e96149a1d62b34d9f34a9ee6329

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

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