BACS Working Day Engine
BACS Working Day Engine
The BACS working day engine ensures that all collection and receipt dates in the Direct Debit service are valid UK banking working days. It powers both the tenant-facing mandate form and the internal collection scheduler.
Overview
BACS payments are only processed on UK banking working days — that is, Monday to Friday, excluding UK public holidays. Submitting a collection on a non-working day (weekend or bank holiday) would cause it to be rejected or delayed. The working day engine resolves all dates before they are displayed to tenants or submitted to Modulr.
Bank Holiday Data
The service maintains a bank_holidays table seeded with official UK bank holiday data fetched from the GOV.UK Bank Holidays API:
https://www.gov.uk/bank-holidays.json
This covers bank holidays in:
- England & Wales
- Scotland
- Northern Ireland
The table is used by the working day functions to skip public holidays when advancing dates.
Core Functions
calculateCollectionDate(requestedDate)
Resolves a requested collection date to the nearest valid BACS working day on or after the requested date.
Logic:
- If the requested date falls on a Saturday, advance to Monday.
- If the requested date falls on a Sunday, advance to Monday.
- If the resulting date is a UK bank holiday, advance to the next calendar day and repeat from step 1.
- Return the first date that is both a weekday and not a bank holiday.
Used by: The mandate form (Payment Details step) and the collection scheduler.
calculateReceiptDate(collectionDate)
Derives the expected receipt date from a confirmed collection date, following the standard BACS settlement cycle and skipping non-working days.
Used by: The mandate form to display the date pair to the tenant — i.e. "funds leave your account on [collection date] and will be received on [receipt date]".
Edge Cases
February 28/29 Boundary
When a mandate specifies a monthly collection day of 29, 30, or 31, the engine applies the following rules for February:
| Configured day | Non-leap year | Leap year |
|---|---|---|
| 29 | Feb 28 | Feb 29 |
| 30 | Feb 28 | Feb 29 |
| 31 | Feb 28 | Feb 29 |
In the following month (March), the collection reverts to the originally configured day.
Sunday-to-Monday Push
If a calculated date lands on a Sunday, it is pushed forward to Monday before any further bank holiday checks are applied. This means a Sunday that precedes a bank holiday Monday will be pushed to Tuesday.
Month-End Roll
If a mandate's configured collection day does not exist in a given month (e.g. day 31 in April, June, September, or November), the date is rolled back to the last valid day of that month.
Mandate Form Integration
On the Payment Details step of the mandate form, tenants are shown a clear date pair:
- Collection date — the working day on which funds will be debited from their account.
- Receipt date — the working day on which the agent/landlord will receive the funds.
Both dates are computed in real time using the working day engine as the tenant selects their preferred collection day.
Collection Scheduler Integration
The collection scheduler calls calculateCollectionDate(requestedDate) before submitting each collection to Modulr. This ensures that BACS submission dates are always valid and prevents rejections due to non-working-day submissions.