construction.payment-notice-dates
Construction Act default dates: payment due, payment notice, final date for payment and pay-less notice.
1.0.0 (not the latest) · published 2026-10-03 by charlie · Anterra
Pinned by 17 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 solicitor review how you use it, before anyone relies on the output. Provided “as is” under its licence, without warranty.
What it does
The fallback payment deadlines of the Construction Act (Housing Grants, Construction and Regeneration Act 1996, as amended from 2011 by Part 8 of the Local Democracy, Economic Development and Construction Act 2009). When a construction contract leaves out its own payment mechanism, the Scheme for Construction Contracts fills the gap, and this gives the four dates it sets:
| date | rule (Scheme, Schedule Part II) | |---|---| | due date | interim (para 4): the later of 7 days after the end of the relevant period and the payee's claim; final (paras 5 and 6): the later of 30 days after completion and the claim; other (para 7): the later of 7 days after completion of that work and the claim | | payment notice | para 9: the payer's notice, no later than 5 days after the due date | | final date for payment | para 8: 17 days after the due date | | pay-less notice | para 10: no later than 7 days before the final date for payment |
For example
payment_notice_dates(interim, 2026-01-31, 2026-01-31, 2025-06-01, england-and-wales)→ due date 2026-02-07, payment notice by 2026-02-12, final date for payment 2026-02-24, pay less notice by 2026-02-17 interim payment: 7 days after the period, then 5, 17 and 7 days backpayment_notice_dates(interim, 2026-01-31, 2026-02-20, 2025-06-01, england-and-wales)→ due date 2026-02-20, payment notice by 2026-02-25, final date for payment 2026-03-09, pay less notice by 2026-03-02 a claim made after the 7 days sets the due datepayment_notice_dates(interim, 2026-02-28, 2026-02-10, 2025-06-01, england-and-wales)→ due date 2026-03-07, payment notice by 2026-03-12, final date for payment 2026-03-24, pay less notice by 2026-03-17 a claim made before the period ends does not bring the due date forward
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 payment_notice_dates(kind: SchemePaymentKind, work_date: str, claim_date: str, contract_date: str, region: UkRegion) -> PaymentNoticeDates
| kind | SchemePaymentKind | interim (para 4), final (paras 5 and 6) or other (para 7) |
| work_date | date | interim: the last day of the relevant period; final and other: the day the work was completed |
| claim_date | date | the day the payee made its claim (written notice of the sum it considers due) |
| contract_date | date | the day the contract was entered into; the 2011 rules apply from 1 October 2011 (Scotland 1 November 2011) |
| region | UkRegion | england-and-wales or scotland; northern-ireland has its own Order and is refused |
| returns | PaymentNoticeDates |
The types it declares, generated into your project
SchemePaymentKind = Literal["interim", "final", "other"]
@dataclass(frozen=True)
class PaymentNoticeDates:
"""The Scheme's default deadlines for one payment, every period reckoned under s.116."""
#: the payment due date: the later of the period's expiry and the claim
due_date: str
#: last day for the payer's payment notice, 5 days after the due date
payment_notice_by: str
#: 17 days after the due date
final_date_for_payment: str
#: last day for a pay-less notice, 7 days before the final date for payment
pay_less_notice_by: str
Your code names it in one line, in the file that uses it
from fune.construction.payment_notice_dates import payment_notice_dates # construction.payment-notice-dates@^1
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import re
from typing import Dict, List, Set
from .construction_payment_notice_dates_data import SCHEME_PERIODS, SCHEME_PERIODS_HISTORY ← this capability’s own data, compiled from data/scheme-periods.json into the same file by fune build
from .construction_payment_notice_dates_types import PaymentNoticeDates, SchemePaymentKind
from .dates_add_days import add_days ← from dates.add-days ^1.0.0 · built alongside by fune
from .dates_bank_holidays import UkRegion, bank_holidays ← from dates.bank-holidays ^1.0.0 · built alongside by fune
from .dates_day_of_week import day_of_week ← from dates.day-of-week ^1.0.0 · built alongside by fune
ISO_DATE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
SUNDAY = 7
def _check_date(name: str, value: str) -> None:
if not isinstance(value, str) or not ISO_DATE.match(value):
raise ValueError('%s must be an ISO date (YYYY-MM-DD), received "%s"' % (name, value))
add_days(value, 0) # rejects impossible dates such as 2026-02-30
def _is_sunday(iso: str) -> bool:
return day_of_week(iso) == SUNDAY
def _statutory_days(region: str, year: int) -> List[str]:
# Schedule 1 of the Banking and Financial Dealings Act 1971 names some fixed
# dates that gov.uk's list leaves out when they fall on a Saturday (it lists
# the weekday substitute instead). s.116 excludes the statutory day as well.
y = "%04d" % year
dec25 = y + "-12-25"
dec26 = y + "-12-26"
days = [dec25] # s.116(3) names Christmas Day itself
if region == "england-and-wales":
if not _is_sunday(dec26):
days.append(dec26)
if _is_sunday(dec25) or _is_sunday(dec26):
days.append(y + "-12-27")
elif region == "scotland":
jan1 = y + "-01-01"
jan2 = y + "-01-02"
if not _is_sunday(jan1):
days.append(jan1)
if not _is_sunday(jan2):
days.append(jan2)
if _is_sunday(jan1) or _is_sunday(jan2):
days.append(y + "-01-03")
if _is_sunday(dec25):
days.append(dec26)
return days
def payment_notice_dates(
kind: SchemePaymentKind, work_date: str, claim_date: str, contract_date: str, region: UkRegion
) -> PaymentNoticeDates:
"""The Scheme for Construction Contracts' default payment deadlines.
Every period is reckoned as s.116 of the Housing Grants, Construction and
Regeneration Act 1996 says: it starts the day after the date it runs from,
weekends count, and Christmas Day, Good Friday and that part of the UK's
bank holidays do not. That is why this is not dates.add-business-days.
"""
_check_date("workDate", work_date)
_check_date("claimDate", claim_date)
_check_date("contractDate", contract_date)
rule = None
for row in SCHEME_PERIODS:
if row.region != region or row.kind != kind:
continue
if contract_date < row.valid_from:
continue
if row.valid_to is not None and contract_date > row.valid_to:
continue
if rule is None or row.valid_from > rule.valid_from:
rule = row
if rule is None:
if SCHEME_PERIODS_HISTORY != "full":
earliest = None
for row in SCHEME_PERIODS:
if row.region != region or row.kind != kind:
continue
if earliest is None or row.valid_from < earliest:
earliest = row.valid_from
if earliest is not None and contract_date < earliest:
raise ValueError(
"no Scheme for Construction Contracts periods for %s/%s on %s: this build was installed with history=%s, "
"so it only carries rules from %s. Reinstall with history=full for older contracts."
% (region, kind, contract_date, SCHEME_PERIODS_HISTORY, earliest)
)
raise ValueError(
"no Scheme for Construction Contracts periods for %s/%s for a contract entered into on %s"
% (region, kind, contract_date)
)
excluded_by_year: Dict[int, Set[str]] = {}
def is_excluded(iso: str) -> bool:
year = int(iso[0:4])
excluded = excluded_by_year.get(year)
if excluded is None:
excluded = set(h.date for h in bank_holidays(region, year))
excluded.update(_statutory_days(region, year))
excluded_by_year[year] = excluded
return iso in excluded
def reckon(start: str, days: int) -> str:
# Count `days` reckonable days after `start` (before it when negative).
step = -1 if days < 0 else 1
remaining = abs(days)
day = start
while remaining > 0:
day = add_days(day, step)
if not is_excluded(day):
remaining -= 1
return day
expiry = reckon(work_date, rule.due_days)
due_date = claim_date if claim_date > expiry else expiry
final_date = reckon(due_date, rule.final_date_days)
return PaymentNoticeDates(
due_date=due_date,
payment_notice_by=reckon(due_date, rule.payment_notice_days),
final_date_for_payment=final_date,
pay_less_notice_by=reckon(final_date, -rule.pay_less_notice_days),
)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 construction.payment-notice-dates
The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./construction.payment-notice-dates-1.0.0-python.fune, or fetch it from a terminal with fune pull construction.payment-notice-dates@1.0.0:python.
The whole function, every language, is one file too: construction.payment-notice-dates-1.0.0.fune, 34,450 bytes, sha256 3f252214811a3ccf62f6e5ec82456cc7be821d47167b26e635e20ca62400a99d. 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 construction.payment-notice-dates
after — your function gets the result and the arguments, and returns the final result.
# fune: after construction.payment-notice-dates
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 construction.payment-notice-dates
# fune: replace dates.bank-holidays in construction.payment-notice-dates
# fune: replace dates.day-of-week in construction.payment-notice-dates
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 construction.payment-notice-dates --steps.
# fune: step construction.payment-notice-dates 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 | |
|---|---|---|---|
| interim payment: 7 days after the period, then 5, 17 and 7 days back | interim, 2026-01-31, 2026-01-31, 2025-06-01, england-and-wales | → | due date 2026-02-07, payment notice by 2026-02-12, final date for payment 2026-02-24, pay less notice by 2026-02-17 |
| a claim made after the 7 days sets the due date | interim, 2026-01-31, 2026-02-20, 2025-06-01, england-and-wales | → | due date 2026-02-20, payment notice by 2026-02-25, final date for payment 2026-03-09, pay less notice by 2026-03-02 |
| a claim made before the period ends does not bring the due date forward | interim, 2026-02-28, 2026-02-10, 2025-06-01, england-and-wales | → | due date 2026-03-07, payment notice by 2026-03-12, final date for payment 2026-03-24, pay less notice by 2026-03-17 |
| weekends count: a Saturday due date is not moved | interim, 2026-01-31, 2026-01-31, 2011-10-01, england-and-wales | → | due date 2026-02-07, payment notice by 2026-02-12, final date for payment 2026-02-24, pay less notice by 2026-02-17 |
| Easter in England: Good Friday and Easter Monday are not counted | interim, 2026-03-31, 2026-03-31, 2025-06-01, england-and-wales | → | due date 2026-04-09, payment notice by 2026-04-14, final date for payment 2026-04-26, pay less notice by 2026-04-19 |
| Easter in Scotland: Good Friday is excluded but Easter Monday is not a Scottish bank holiday | interim, 2026-03-31, 2026-03-31, 2025-06-01, scotland | → | due date 2026-04-08, payment notice by 2026-04-13, final date for payment 2026-04-25, pay less notice by 2026-04-18 |
| Christmas in England: Saturday 26 December is a statutory bank holiday as well as the Monday substitute | interim, 2026-12-18, 2026-12-18, 2025-06-01, england-and-wales | → | due date 2026-12-27, payment notice by 2027-01-03, final date for payment 2027-01-15, pay less notice by 2027-01-08 |
| Christmas in Scotland: 26 December counts, Saturday 2 January does not | interim, 2026-12-18, 2026-12-18, 2025-06-01, scotland | → | due date 2026-12-26, payment notice by 2027-01-03, final date for payment 2027-01-16, pay less notice by 2027-01-09 |
| final payment: 30 days after completion | final, 2026-06-01, 2026-06-01, 2025-06-01, england-and-wales | → | due date 2026-07-01, payment notice by 2026-07-06, final date for payment 2026-07-18, pay less notice by 2026-07-11 |
| final payment claimed late | final, 2026-06-01, 2026-08-10, 2025-06-01, england-and-wales | → | due date 2026-08-10, payment notice by 2026-08-15, final date for payment 2026-08-27, pay less notice by 2026-08-20 |
Show the other 7 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| other payment: 7 days after completion, across the May bank holidays | other, 2026-05-01, 2026-05-01, 2025-06-01, england-and-wales | → | due date 2026-05-09, payment notice by 2026-05-14, final date for payment 2026-05-27, pay less notice by 2026-05-19 |
| a contract from before the 2011 amendments is not covered | interim, 2026-01-31, 2026-01-31, 2011-09-30, england-and-wales | → | error: no Scheme for Construction Contracts periods |
| the Scottish amendments start on 1 November 2011 | interim, 2026-01-31, 2026-01-31, 2011-10-15, scotland | → | error: no Scheme for Construction Contracts periods |
| Northern Ireland has its own Order and is not covered | interim, 2026-01-31, 2026-01-31, 2025-06-01, northern-ireland | → | error: no Scheme for Construction Contracts periods |
| an unknown payment kind | weekly, 2026-01-31, 2026-01-31, 2025-06-01, england-and-wales | → | error: no Scheme for Construction Contracts periods |
| a period running into a year without bank holiday data | interim, 2028-12-20, 2028-12-20, 2025-06-01, england-and-wales | → | error: no bank holiday data |
| a malformed date | interim, 2026/01/31, 2026-01-31, 2025-06-01, england-and-wales | → | error: workDate must be an ISO date |
More from the author
The relevant period is the one the contract sets, or 28 days if it sets none (para 12). Pass its last day as `workDate`.
## Counting days
Periods are reckoned under s.116 of the 1996 Act: a period after a date starts the day after it, weekends **count**, and Christmas Day, Good Friday and the bank holidays of that part of the UK are left out. So this is not `dates.add-business-days`. Bank holidays come from `dates.bank-holidays` (gov.uk's list, 2019 to 2028; a period reaching outside that is an error, not a guess). s.116 means holidays under the Banking and Financial Dealings Act 1971, whose Schedule 1 names some fixed days that gov.uk leaves out when they fall on a Saturday, listing only the weekday substitute. Those are excluded too: 26 December (not a Sunday) and 27 December when 25 or 26 December is a Sunday in England and Wales; 1 and 2 January (not a Sunday, else 3 January) and 26 December when Christmas is a Sunday in Scotland. So in England Saturday 26 December 2026 is not counted, which a gov.uk-only calendar gets wrong.
The pay-less deadline counts 7 reckonable days backwards from the final date. s.116(2) speaks of periods "after or from" a date; counting backwards with the same exclusions is the reading this capability takes.
## What it does not do
- Contract terms win: if the contract sets its own due date, final date or notice periods (and they comply with the Act), use those instead. - The payee's default notice (s.110B) and the postponed final date that follows when the payer gives no payment notice are not calculated. - Northern Ireland has its own Construction Contracts (NI) Order 1997 and Scheme, and is refused. - Contracts entered into before the 2011 amendments (before 1 October 2011 in England and Wales, before 1 November 2011 in Scotland) are refused.
## Data
`data/scheme-periods.json` holds the periods per region and kind, dated by the day the contract was entered into (the amendments apply only to contracts made from their commencement). It declares `effective`, so a `history=current` build keeps the rows still in force and refuses dates before its horizon.
## Sources
- The Scheme for Construction Contracts (England and Wales) Regulations 1998 (SI 1998/649), Schedule Part II, as amended: https://www.legislation.gov.uk/uksi/1998/649/schedule/part/II - The Scheme for Construction Contracts (England and Wales) Regulations 1998 (Amendment) (England) Regulations 2011 (SI 2011/2333), reg. 1 (in force 1 October 2011, contracts entered into after that): https://www.legislation.gov.uk/uksi/2011/2333/regulation/1/made ; the Wales equivalent is SI 2011/1715, same date. - The Scheme for Construction Contracts (Scotland) Regulations 1998 (SI 1998/687), Schedule Part II: https://www.legislation.gov.uk/uksi/1998/687/schedule/part/II - The Scheme for Construction Contracts (Scotland) Amendment Regulations 2011 (SSI 2011/371), regs. 1 and 4 (in force 1 November 2011, contracts entered into on or after that date; paras 9 and 10 substituted with the same 5 and 7 day periods): https://www.legislation.gov.uk/ssi/2011/371/made - Housing Grants, Construction and Regeneration Act 1996, s.116 (reckoning periods of time): https://www.legislation.gov.uk/ukpga/1996/53/section/116 - Banking and Financial Dealings Act 1971, Schedule 1: https://www.legislation.gov.uk/ukpga/1971/80/schedule/1
Files
| Path | Bytes |
|---|---|
| README.md | 4,364 |
| data/scheme-periods.json | 2,287 |
| impl/python.py | 4,947 |
| impl/rust.rs | 6,920 |
| impl/typescript.ts | 4,767 |
| vectors.json | 5,706 |