"Payment is due in 5 business days." "We ship within 3 business days." "Settlement is T+1."
Every one of these turns into a line of code somewhere, and that line is usually wrong. This post works through the problem one step at a time: the naive version, the weekend-aware version, the holiday-aware version, and finally a business day API call. At each step we'll see exactly which date it gets wrong.
The running example is an invoice dated Monday, 23 November 2026, due in 5 business days, in the United States.
The correct answer is Tuesday, 1 December 2026. Let's see how many attempts it takes to get there.
Attempt 1: just add 5 days
const due = new Date("2026-11-23T00:00:00Z");
due.setUTCDate(due.getUTCDate() + 5);
console.log(due.toISOString().slice(0, 10)); // "2026-11-28"
That's a Saturday. Calendar days aren't business days, so nobody will be surprised when this one fails.
Side note: notice the
T00:00:00Zand theUTCmethods. Mixnew Date("2026-11-23")(parsed as UTC) withsetDate()(local time) and anyone west of Greenwich can end up a day off before business days even come into it. Keep date-only maths in UTC, or use a date-only type.
Attempt 2: skip weekends
function addWeekdays(isoDate, n) {
const d = new Date(isoDate + "T00:00:00Z");
const step = n < 0 ? -1 : 1;
let left = Math.abs(n);
while (left > 0) {
d.setUTCDate(d.getUTCDate() + step);
const day = d.getUTCDay();
if (day !== 0 && day !== 6) left--; // 0 = Sunday, 6 = Saturday
}
return d.toISOString().slice(0, 10);
}
console.log(addWeekdays("2026-11-23", 5)); // "2026-11-30"
Monday 30 November. Closer, but still wrong: Thursday 26 November 2026 is Thanksgiving, a US federal holiday. Banks are shut, so it can't count as one of the five days.
Even the "weekend" part has an assumption baked in. Saturday–Sunday isn't universal: the UAE moved from a Friday–Saturday weekend to Saturday–Sunday in 2022, and several countries in the region still use Friday–Saturday. Hardcode 0 and 6 and you're wrong for those countries, and wrong for any UAE date before 2022.
Attempt 3: weekends plus a holiday list
The classic fix is to get a list of holidays and skip those too. In Python, the excellent open-source holidays library plus NumPy gets you there:
import holidays
import numpy as np
us_holidays = list(holidays.US(years=2026).keys())
due = np.busday_offset("2026-11-23", 5, roll="forward", holidays=us_holidays)
print(due) # 2026-12-01
2026-12-01. Correct! For a single country and public holidays, this is a perfectly good solution, and if that's all you need, stop here.
Here's where it starts to hurt in production, though.
1. Public holidays aren't the same as business days
"Business day" depends on whose business. Juneteenth 2027 falls on a Saturday, so the federal holiday is observed on Friday 18 June 2027. The NYSE is closed that Friday. The Federal Reserve, though, follows its own rule: when a holiday falls on a Saturday, it stays open the Friday before. So US dollar payments still settle that day.
Same date, same country, two different answers:
| Calendar | Fri 18 June 2027 |
|---|---|
| US federal holidays | closed (Juneteenth observed) |
| NYSE | closed |
| USD settlement (Fed) | open |
A public holiday list gives you the first row. If you're moving money, you need the third.
2. Cross-border means two calendars at once
Say a payment leaves the US on Thursday 24 December 2026 and has to arrive in the UK one business day later. Christmas is Friday the 25th in both countries. But Boxing Day falls on a Saturday in 2026, so the UK observes it on Monday 28 December, while in the US the 28th is a normal working day.
- US alone: next business day is Mon 28 Dec
- US and UK both open: next business day is Tue 29 Dec
Now you need two holiday lists, an intersection, and to get the observed-day rules right for both.
3. Some holidays aren't known in advance
Eid al-Fitr and Eid al-Adha are set by moon sighting, and several Gulf states confirm the exact dates only days ahead. National days and one-off holidays get declared mid-year. A holiday list you generated in January can be wrong by March, and nothing tells you.
4. Someone has to keep it updated
Library upgrades, regional variants (state holidays, bank holidays, school holidays), exchange early closes. Every one of them is a dependency you now own.
Attempt 4: use a business day API
A business day API moves all of that to the server: calendars per country, exchange and settlement currency, observed-day rules, combining calendars, and updates when a government changes a date. You ask a date question and get a date back.
The examples below use the Finance Calendar API, which I built for exactly this problem. The same ideas apply whichever provider you pick.
Add business days
curl -H "Authorization: Bearer $FC_API_KEY" \
"https://api.financecalendarapi.com/v1/business-days/add?date=2026-11-23&n=5&calendars=US"
{
"date": "2026-11-23",
"n": 5,
"result": "2026-12-01",
"mode": "all",
"coverage": "verified",
"estimated": false,
"data_release": { "id": "20261018T084309Z", "...": "..." }
}
Thanksgiving is handled. n can be negative too: n=-5 from 2026-12-01 gives back 2026-11-23.
Several calendars at once
The cross-border case is just a comma. With mode=all (the default), a day only counts when every calendar is open:
curl -H "Authorization: Bearer $FC_API_KEY" \
"https://api.financecalendarapi.com/v1/business-days/add?date=2026-12-24&n=1&calendars=US,GB"
# "result": "2026-12-29"
If you want to know why a day is out, ask about that day directly:
curl -H "Authorization: Bearer $FC_API_KEY" \
"https://api.financecalendarapi.com/v1/days/2026-12-28?calendars=US,GB"
{
"date": "2026-12-28",
"is_business_day": false,
"calendars": [
{ "id": "US", "status": "open", "is_business_day": true, "entries": [] },
{
"id": "GB", "status": "closed", "is_business_day": false,
"entries": [{ "date": "2026-12-28", "name": "Boxing Day (observed)", "observed": true }]
}
]
}
And for money movement, use the settlement-currency calendar instead of the country one. calendars=USD follows the Fed's rules, so Friday 18 June 2027 comes back open.
Count business days between two dates
curl -H "Authorization: Bearer $FC_API_KEY" \
"https://api.financecalendarapi.com/v1/business-days/count?from=2026-11-01&to=2026-12-01&calendars=US"
# "count": 19
November 2026 has 21 weekdays. Take away Veterans Day and Thanksgiving and you get 19.
Watch the boundaries on any "business days between" function: here from is counted and to isn't, like Python's range(). That lets you pass the first of the next month as to. Other tools count the other way round, so check before you compare numbers.
In JavaScript
const API = "https://api.financecalendarapi.com";
async function addBusinessDays(date, n, calendars) {
const params = new URLSearchParams({ date, n, calendars });
const res = await fetch(`${API}/v1/business-days/add?${params}`, {
headers: { Authorization: `Bearer ${process.env.FC_API_KEY}` },
});
const body = await res.json();
if (!res.ok) throw new Error(`${body.code}: ${body.detail}`);
return body.result;
}
console.log(await addBusinessDays("2026-11-23", 5, "US")); // "2026-12-01"
console.log(await addBusinessDays("2026-12-24", 1, "US,GB")); // "2026-12-29"
In Python
import os
import requests
API = "https://api.financecalendarapi.com"
def add_business_days(date: str, n: int, calendars: str) -> str:
res = requests.get(
f"{API}/v1/business-days/add",
params={"date": date, "n": n, "calendars": calendars},
headers={"Authorization": f"Bearer {os.environ['FC_API_KEY']}"},
timeout=10,
)
body = res.json()
if not res.ok:
raise RuntimeError(f"{body['code']}: {body['detail']}")
return body["result"]
print(add_business_days("2026-11-23", 5, "US")) # 2026-12-01
print(add_business_days("2026-12-24", 1, "US,GB")) # 2026-12-29
Two fields worth storing
Look back at the responses and you'll see two fields a homemade holiday list can't give you:
-
coverage/estimatedtell you whether the answer rests on officially confirmed dates or on an estimate, such as an Eid date that hasn't been announced yet. For a due date shown to a customer, that's the difference between "due 1 Dec" and "due 1 Dec (provisional)". -
data_release.idsays which version of the calendar data answered. Store it next to anything you calculate. If a government moves a holiday later, you can find every date that came from the old data and recalculate it, instead of finding out from an angry customer.
Which approach should you use?
| Your situation | Use |
|---|---|
| One country, public holidays only, dates computed on the fly |
holidays + numpy.busday_offset (or your language's equivalent) |
| Payments, settlement or trading dates | A business day API with settlement and exchange calendars |
| Two or more countries in one calculation | A business day API that combines calendars |
| You need to know when a holiday changes after you've cached it | A business day API with a change feed or webhooks |
Whatever you choose, please don't ship + 5 days.
I built Finance Calendar API after running into all of the above: 1,600+ country, exchange and settlement-currency calendars, multi-calendar business-day maths, and a change feed that tells you when a date moves. There's a 14-day free trial if you want to try the calls in this post. Questions or edge cases I missed? Leave a comment and I'll dig in.
Top comments (0)