The Challenge#
Calculating business days sounds straightforward until you consider:
Holidays vary by country — New Year's Day is January 1st in most places, but not everywhere
Workweeks differ globally — Israel uses Sunday-Thursday, UAE uses Monday-Friday (recently changed from Sunday-Thursday)
Regional holidays exist — US states have different holidays; German Länder celebrate different days
Some holidays move — Easter, Thanksgiving, and lunar calendar holidays change yearly
Half-day holidays — Christmas Eve is a half-day in some countries, full day in others
Python's built-in date libraries don't handle any of this. This guide shows you how to solve it.
Prerequisites#
Before starting, you need:
Python 3.8+ installed
pip for installing packages
A World Data API key (for the API-based approach) — get a free key
Install the required packages:
pip install requests numpy
For the workalendar approach, also install:
pip install workalendar
Note: The World Data API business days endpoint requires a paid tier (Starter or higher). The free tier provides access to basic country and holiday data.
Quick Solution#
import requests
# Add 10 business days to a date, excluding US holidays
response = requests.get(
"https://worlddataapi.com/v1/business-days/US/add",
params={"start": "2026-01-15", "days": 10},
headers={"X-API-Key": "YOUR_API_KEY"}
)
result = response.json()
print(result["result"]) # "2026-01-29"
Or using curl:
curl -X GET "https://worlddataapi.com/v1/business-days/US/add?start=2026-01-15&days=10" \
-H "X-API-Key: YOUR_API_KEY"
That's the complete solution for holiday-aware business day calculations. The rest of this guide covers Python's built-in options, their limitations, and when to use each approach.
Python's Built-in Options#
NumPy's busday_count#
NumPy provides fast business day calculations:
import numpy as np
# Count business days between two dates
start = np.datetime64('2026-01-01')
end = np.datetime64('2026-01-31')
business_days = np.busday_count(start, end)
print(business_days) # 22
The problem: NumPy doesn't know about holidays.
# January 1, 2026 is New Year's Day — not a business day
# But NumPy counts it as one
np.busday_count('2025-12-31', '2026-01-02') # Returns 1, should be 0
You can pass holidays manually:
holidays = ['2026-01-01', '2026-01-19'] # New Year's, MLK Day
np.busday_count('2026-01-01', '2026-01-31', holidays=holidays) # 20
But now you're maintaining holiday data yourself.
Adding Business Days with NumPy#
import numpy as np
# Add 10 business days
start = np.datetime64('2026-01-15')
result = np.busday_offset(start, 10)
print(result) # 2026-01-29
Same limitation — no holiday awareness without manual data.
The datetime and dateutil Approach#
For basic weekday skipping without NumPy:
from datetime import datetime, timedelta
def add_business_days(start_date: datetime, days: int) -> datetime:
"""Add business days, skipping weekends only."""
result = start_date
added = 0
while added < days:
result += timedelta(days=1)
if result.weekday() < 5: # Monday = 0, Friday = 4
added += 1
return result
# Add 10 business days
start = datetime(2026, 1, 15)
result = add_business_days(start, 10)
print(result.date()) # 2026-01-29
This fails on holidays and non-Western workweeks, just like the NumPy version.
The workalendar Library#
workalendar provides holiday calendars for many countries:
from workalendar.usa import UnitedStates
from datetime import date
cal = UnitedStates()
# Check if a date is a business day
print(cal.is_working_day(date(2026, 1, 1))) # False (New Year's)
print(cal.is_working_day(date(2026, 1, 2))) # True
# Add business days
result = cal.add_working_days(date(2026, 1, 15), 10)
print(result) # 2026-01-30 (accounts for MLK Day)
workalendar covers 70+ countries with state/province support for some:
from workalendar.usa import California
cal = California()
# Includes César Chávez Day (March 31)
workalendar Limitations#
Coverage gaps for some countries and regions
Doesn't handle all regional holidays
Updates lag behind government announcements
Islamic holidays use calculated dates (not moon-sighted)
For applications where accuracy matters, verify workalendar's data against official sources.
API-Based Solution#
World Data API handles business day calculations directly, with current holiday data for 230+ countries:
import requests
from typing import Optional
class BusinessDays:
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://worlddataapi.com/v1/business-days"
self.session = requests.Session()
self.session.headers["X-API-Key"] = api_key
def is_business_day(self, location: str, date: str) -> dict:
"""Check if a date is a business day."""
response = self.session.get(
f"{self.base_url}/{location}",
params={"date": date}
)
response.raise_for_status()
return response.json()
def add(self, location: str, start: str, days: int,
day_type: str = "business") -> dict:
"""Add business days to a date."""
response = self.session.get(
f"{self.base_url}/{location}/add",
params={"start": start, "days": days, "type": day_type}
)
response.raise_for_status()
return response.json()
def subtract(self, location: str, start: str, days: int,
day_type: str = "business") -> dict:
"""Subtract business days from a date."""
response = self.session.get(
f"{self.base_url}/{location}/subtract",
params={"start": start, "days": days, "type": day_type}
)
response.raise_for_status()
return response.json()
def count(self, location: str, start: str, end: str,
day_type: str = "business") -> dict:
"""Count business days between two dates."""
response = self.session.get(
f"{self.base_url}/{location}/count",
params={"start": start, "end": end, "type": day_type}
)
response.raise_for_status()
return response.json()
Usage Examples#
import os
api = BusinessDays(os.environ["WORLD_DATA_API_KEY"])
# Check if New Year's Day is a business day
result = api.is_business_day("US", "2026-01-01")
print(result["is_business_day"]) # False
print(result["reason"]) # "holiday"
print(result["holiday"]["name"]) # "New Year's Day"
# Add 10 business days
result = api.add("US", "2026-01-15", 10)
print(result["result"]) # "2026-01-29"
# Count business days in January (California)
result = api.count("US-CA", "2026-01-01", "2026-01-31")
print(result["business_days"]) # 21
print(result["holidays"]) # 2
for holiday in result["holiday_list"]:
print(f" {holiday['date']}: {holiday['name']}")
Handling Different Workweeks#
Israel uses Sunday-Thursday. The API returns the workweek in responses:
result = api.is_business_day("IL", "2026-01-02") # Friday
print(result["is_business_day"]) # False
print(result["reason"]) # "weekend"
print(result["workweek"]) # ["sunday", "monday", "tuesday", "wednesday", "thursday"]
Banking vs Business Days#
Financial applications often need banking days:
# UK August bank holiday affects banking days
result = api.add("GB", "2026-08-25", 5, day_type="banking")
print(result["result"]) # Accounts for bank holiday
Day types:
business— weekends + public holidaysbanking— weekends + public holidays + bank holidaysgovernment— weekends + public holidays + government closures
Combining Approaches#
For high-volume applications, fetch holidays once and calculate locally:
import os
import requests
from datetime import datetime, timedelta
from functools import lru_cache
@lru_cache(maxsize=100)
def get_holidays(location: str, year: int) -> frozenset:
"""Fetch and cache holidays for a location and year."""
response = requests.get(
f"https://worlddataapi.com/v1/holidays/{location}",
params={"year": year},
headers={"X-API-Key": os.environ['WORLD_DATA_API_KEY']}
)
response.raise_for_status()
data = response.json()
return frozenset(h["date"] for h in data["holidays"])
def add_business_days_cached(location: str, start: datetime, days: int) -> datetime:
"""Add business days using cached holiday data."""
holidays = get_holidays(location, start.year)
result = start
added = 0
while added < days:
result += timedelta(days=1)
# Check if we crossed into a new year
if result.year != start.year:
holidays = holidays | get_holidays(location, result.year)
date_str = result.strftime("%Y-%m-%d")
if result.weekday() < 5 and date_str not in holidays:
added += 1
return result
This approach:
Reduces API calls for repeated calculations
Works offline after initial fetch
Trades accuracy for speed (cached data may become stale)
Error Handling#
import os
import requests
from requests.exceptions import RequestException
def safe_add_business_days(location: str, start: str, days: int) -> dict:
try:
response = requests.get(
f"https://worlddataapi.com/v1/business-days/{location}/add",
params={"start": start, "days": days},
headers={"X-API-Key": os.environ['WORLD_DATA_API_KEY']},
timeout=10
)
if response.status_code == 400:
raise ValueError("Invalid date format or location code")
if response.status_code == 401:
raise PermissionError("Invalid API key")
if response.status_code == 429:
raise RuntimeError("Rate limit exceeded — retry later")
response.raise_for_status()
return response.json()
except requests.exceptions.Timeout:
raise TimeoutError("API request timed out")
except requests.exceptions.ConnectionError:
raise ConnectionError("Network error — check your connection")
Comparison: When to Use What#
| Approach | Pros | Cons |
|---|---|---|
| NumPy busday_count | Fast, no dependencies beyond NumPy | No holidays, Mon-Fri only |
| datetime + timedelta | No dependencies | No holidays, Mon-Fri only |
| workalendar | Good country coverage, offline | Coverage gaps, may lag updates |
| World Data API | 249 countries, current data, regional support | Requires network, API costs |
Use NumPy/datetime when:#
You only need weekend skipping
Holidays aren't relevant (rough estimates)
Offline operation is required
Use workalendar when:#
You need holidays for supported countries
Offline operation is required
You can verify accuracy for your use case
Use an API when:#
Dates have legal/financial significance
You support multiple countries or regions
You need regional holidays (US states, German Länder)
Accuracy matters more than latency
Pandas Integration#
For dataframe operations:
import pandas as pd
import numpy as np
# Create a date range and identify business days
dates = pd.date_range('2026-01-01', '2026-01-31')
# Basic business day check (no holidays)
df = pd.DataFrame({'date': dates})
df['is_weekday'] = df['date'].dt.dayofweek < 5
# With holidays from API
holidays = get_holidays('US', 2026)
df['is_business_day'] = (
(df['date'].dt.dayofweek < 5) &
(~df['date'].dt.strftime('%Y-%m-%d').isin(holidays))
)
print(df[~df['is_business_day']])
Common Pitfalls#
Assuming Monday-Friday everywhere#
Israel, UAE, and other countries use different workweeks. Hardcoding weekday() < 5 breaks for these regions:
# This fails for Israel (Sunday-Thursday workweek)
if date.weekday() < 5: # Wrong assumption
business_days += 1
Forgetting to handle year boundaries#
When adding business days across December/January, you need holidays for both years:
# Bug: only fetches holidays for start year
holidays = get_holidays("US", start.year)
# Fix: fetch holidays for the result year too
if result.year != start.year:
holidays = holidays | get_holidays("US", result.year)
Caching holidays without invalidation#
Holiday data can change (governments announce new holidays). Cache with a reasonable TTL:
# Set cache TTL to 24 hours, not forever
@lru_cache(maxsize=100) # No TTL - stale data risk
# Better: use a time-aware cache or refresh periodically
Ignoring regional holidays#
US federal holidays differ from state holidays. California observes Cesar Chavez Day; other states don't:
# US federal holidays only
result = api.count("US", "2026-03-01", "2026-04-01")
# California-specific (includes Cesar Chavez Day)
result = api.count("US-CA", "2026-03-01", "2026-04-01")
Treating half-days as full days#
Some countries treat Christmas Eve as a half-day. The API treats it as a full holiday. For precise calculations, check your specific country's rules.
Limitations#
World Data API calculates Islamic holidays astronomically. Actual observance in some countries depends on physical moon sighting and may differ by 1-2 days.
Half-day holidays (Christmas Eve in some countries) are treated as full holidays.
Summary#
Python's built-in date functions and NumPy handle basic weekday calculations but ignore holidays entirely. The workalendar library adds holiday support for many countries but has coverage gaps and maintenance lag. For applications where accuracy matters — financial calculations, delivery estimates, legal deadlines — use an API with current holiday data.
Key takeaways:
NumPy busday_count works for rough estimates when holidays don't matter
workalendar provides offline holiday support for 70+ countries
World Data API covers 230+ countries with regional holidays and different workweek support
Always handle year boundaries when calculations span December/January
Remember that workweeks vary globally — not everyone works Monday-Friday
Ready to build accurate business day calculations? Get your API key and start with the free tier, or check out the API documentation for full endpoint details.
Next Steps#
Adding International Holiday Support to Your App — Holiday data integration
Calculating Delivery Dates with Holiday Awareness — E-commerce applications
Caching Strategies for Reference Data APIs — Optimizing API usage