DEV Community

Cover image for How to Add Business Days to a Date (and Why + 5 Days Is Wrong): A Business Day API Tutorial
Mathias Ahlgren
Mathias Ahlgren

Posted on

How to Add Business Days to a Date (and Why + 5 Days Is Wrong): A Business Day API Tutorial

"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"
Enter fullscreen mode Exit fullscreen mode

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:00Z and the UTC methods. Mix new Date("2026-11-23") (parsed as UTC) with setDate() (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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode
{
  "date": "2026-11-23",
  "n": 5,
  "result": "2026-12-01",
  "mode": "all",
  "coverage": "verified",
  "estimated": false,
  "data_release": { "id": "20261018T084309Z", "...": "..." }
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode
{
  "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 }]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Two fields worth storing

Look back at the responses and you'll see two fields a homemade holiday list can't give you:

  • coverage / estimated tell 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.id says 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)