Functional Weave
Code in Python

dates.bank-holidays

UK bank and public holidays for a region and year, as published by gov.uk, including one-off holidays.

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

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

What it does

The bank and public holidays for one UK region and one year, in date order, as the UK government publishes them. The three regions are the ones gov.uk uses: england-and-wales, scotland and northern-ireland. They differ: Scotland has 2 January and St Andrew's Day but no Easter Monday, and a different summer holiday; Northern Ireland adds St Patrick's Day and the Battle of the Boyne.

Source: "UK bank holidays", GOV.UK, https://www.gov.uk/bank-holidays, taken from its machine-readable form https://www.gov.uk/bank-holidays.json on 2026-09-22. That feed covers 2019 to 2028 for every region, and so does this version. Every row is copied from it: dates, titles (gov.uk's wording, curly apostrophes included) and the "Substitute day" note, which becomes `substitute: true`.

For example

  • bank_holidays(england-and-wales, 2,022) → ×10 England and Wales 2022: the Platinum Jubilee moved the spring holiday and added one, and the state funeral added another
  • bank_holidays(england-and-wales, 2,023) → ×9 England and Wales 2023, with the coronation bank holiday
  • bank_holidays(england-and-wales, 2,026) → ×8 England and Wales 2026: Boxing Day falls on a Saturday so the substitute is Monday 28 December

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 bank_holidays(region: UkRegion, year: int) -> List[BankHoliday]
regionUkRegionengland-and-wales, scotland or northern-ireland, as gov.uk divides them
yearintcalendar year; must be one gov.uk publishes
returnsBankHoliday[]the holidays in date order; substitute days are the day actually taken off

The types it declares, generated into your project

UkRegion = Literal["england-and-wales", "scotland", "northern-ireland"]

@dataclass(frozen=True)
class BankHoliday:
    """One day off, as gov.uk lists it."""

    date: str
    #: gov.uk's own wording, e.g. "Boxing Day"
    title: str
    #: true when this is a substitute weekday for a holiday that fell at a weekend
    substitute: bool

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

from fune.dates.bank_holidays import bank_holidays  # dates.bank-holidays@^1
impl/python.py · 40 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, Optional

from .dates_bank_holidays_data import BANK_HOLIDAYS  ← this capability’s own data, compiled from data/bank-holidays.json into the same file by fune build
from .dates_bank_holidays_types import BankHoliday, UkRegion


def bank_holidays(region: UkRegion, year: int) -> List[BankHoliday]:
    """The bank holidays for a UK region and year, in date order, as gov.uk
    publishes them.

    A year the data does not cover is an error rather than an empty list,
    because an empty list reads as "no holidays" and a deadline calculation
    would then count Christmas Day as a working day.
    """
    if isinstance(year, bool) or not isinstance(year, int):
        raise TypeError("year must be an integer, received %r" % (year,))
    first: Optional[int] = None
    last: Optional[int] = None
    found: List[BankHoliday] = []
    prefix = "%04d-" % (year,)
    for row in BANK_HOLIDAYS:
        if row.region != region:
            continue
        row_year = int(row.date[0:4])
        if first is None or row_year < first:
            first = row_year
        if last is None or row_year > last:
            last = row_year
        if row.date.startswith(prefix):
            found.append(BankHoliday(date=row.date, title=row.title, substitute=row.substitute))
    if first is None or last is None:
        raise ValueError(
            'unknown region "%s": expected england-and-wales, scotland or northern-ireland' % (region,)
        )
    if year < first or year > last:
        raise ValueError(
            "no bank holiday data for %s in %s: the published calendar covers %s to %s" % (region, year, first, last)
        )
    # ISO dates sort correctly as strings.
    return sorted(found, key=lambda holiday: holiday.date)

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and nothing else, 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 dates.bank-holidays
Download for Python dates.bank-holidays-1.0.1-python.fune · 55,467 bytes sha256 342cecc130084a36f322ee9fe50912805afad3e9b2a8e94276e10ed8e51c0cb4

The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./dates.bank-holidays-1.0.1-python.fune, or fetch it from a terminal with fune pull dates.bank-holidays@1.0.1:python.

The whole function, every language, is one file too: dates.bank-holidays-1.0.1.fune, 59,839 bytes, sha256 0b3290c1a820afe756b41df29850449ee4c8ae1ddc5834ea7012cf5f094edaf1. 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 dates.bank-holidays

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

# fune: after dates.bank-holidays

replace — it requires no other capability, so there is no dependency to replace.

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 dates.bank-holidays --steps.

# fune: step dates.bank-holidays 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
England and Wales 2022: the Platinum Jubilee moved the spring holiday and added one, and the state funeral added another england-and-wales, 2,022 → ×10
England and Wales 2023, with the coronation bank holiday england-and-wales, 2,023 → ×9
England and Wales 2026: Boxing Day falls on a Saturday so the substitute is Monday 28 December england-and-wales, 2,026 → ×8
England and Wales 2019, the first year gov.uk publishes england-and-wales, 2,019 → ×8
England and Wales 2028, the last year gov.uk publishes england-and-wales, 2,028 → ×8
Scotland 2026: 2 January and St Andrew’s Day, no Easter Monday, and the one-off World Cup bank holiday scotland, 2,026 → ×10
Scotland 2020: the early May holiday moved to Friday 8 May for VE day scotland, 2,020 → ×9
Scotland 2022: New Year and 2 January both substituted scotland, 2,022 → ×11
Northern Ireland 2025: the Battle of the Boyne substituted to Monday 14 July northern-ireland, 2,025 → ×10
Northern Ireland 2024: St Patrick’s Day substituted to Monday 18 March northern-ireland, 2,024 → ×10
Show the other 5 tests
CaseArgumentsExpected
a year before the published calendar is an error, not an empty list england-and-wales, 2,018 → error: no bank holiday data for england-and-wales in 2018
a year after the published calendar is an error, not an empty list scotland, 2,029 → error: no bank holiday data for scotland in 2029
Wales is not its own region in the gov.uk calendar wales, 2,026 → error: unknown region "wales"
a two-letter code is not a region GB, 2,026 → error: unknown region "GB"
a fractional year is an error scotland, 2,026.5 → error: year must be an integer

More from the author

The holidays are data, not rules in code. Easter moves, holidays falling on a weekend are substituted by proclamation, and one-off holidays are added at a few months' notice: the Platinum Jubilee (3 June 2022, which also moved the spring holiday to 2 June), the State Funeral of Queen Elizabeth II (19 September 2022), the coronation of King Charles III (8 May 2023), the VE day move of the early May holiday (8 May 2020) and Scotland's World Cup bank holiday (15 June 2026). No formula produces those, which is why this is a table. When gov.uk publishes another year or another one-off holiday, that is a new version of this capability, not a code change.

A year outside the published range is an error, not an empty list. An empty list would read as "no holidays that year", and a deadline calculation would silently count Christmas Day as a working day.

The data deliberately has no `effective` columns. A bank holiday is an event on a date, not a rule that stays in force until superseded, so there is nothing for `history=current` to keep; declaring the columns would let a project-wide history setting prune the whole calendar away.

This lists days, it does not count them. To skip holidays in working-day arithmetic, pass the dates to dates.add-business-days or dates.business-days-between, which take an explicit holiday list so they work for any country and any employer's closure days.

## Notices

Contains public sector information licensed under the Open Government Licence v3.0 (https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).

1.0.1 adds its attribution notices (NOTICE). The code and the tests are unchanged.

Files

PathBytes
NOTICE182
README.md2,456
data/bank-holidays.json29,358
impl/python.py1,665
impl/rust.rs2,595
impl/typescript.ts1,609
vectors.json13,225