Calculating Delivery Dates with Holiday Awareness

javascript
// Calculate delivery date: 5 business days from today, US holidays
const response = await fetch(
	"https://worlddataapi.com/v1/business-days/US/add?start=2026-01-15&days=5",
	{ headers: { "X-API-Key": "YOUR_API_KEY" } },
);
const data = await response.json();
console.log(`Estimated delivery: ${data.result}`); // "2026-01-22"
python
import requests

response = requests.get(
    "https://worlddataapi.com/v1/business-days/US/add",
    params={"start": "2026-01-15", "days": 5},
    headers={"X-API-Key": "YOUR_API_KEY"}
)
data = response.json()
print(f"Estimated delivery: {data['result']}")  # "2026-01-22"
bash
curl -X GET "https://worlddataapi.com/v1/business-days/US/add?start=2026-01-15&days=5" \
  -H "X-API-Key: YOUR_API_KEY"

Promising a delivery date and missing it damages customer trust. This guide covers building delivery estimates that account for weekends, holidays, and the complexity of international shipping.

The Challenge#

Delivery date calculation seems straightforward until you encounter:

  • Weekends and holidays that vary by country and region

  • Non-Western workweeks (Israel uses Sunday-Thursday, UAE uses Monday-Saturday)

  • Multi-country shipping where origin and destination have different holiday schedules

  • Half-day holidays that some businesses observe as full closures

  • Year-end complexity when orders span December into January

A US-based e-commerce site shipping to the UK must account for US warehouse holidays during processing, UK holidays during final delivery, and the fact that Boxing Day (Dec 26) is a UK holiday but not a US one.

Prerequisites#

Before starting, you need:

  • A World Data API key with Premium tier access (business day calculations require Premium)

  • Basic familiarity with REST APIs

  • Node.js 18+ or Python 3.8+ for the code examples

Note: Business day calculations are a Premium feature. The Free tier does not include access to the /business-days endpoints. View pricing for tier details.

The Problem with Naive Calculations#

A common approach:

javascript
// DON'T DO THIS
function getDeliveryDate(orderDate, shippingDays) {
	const delivery = new Date(orderDate);
	delivery.setDate(delivery.getDate() + shippingDays);
	return delivery;
}

// Order on Friday, Dec 18 with 5-day shipping
getDeliveryDate("2026-12-18", 5);
// Returns Dec 23 — but that ignores weekends AND Christmas

This breaks when:

  • The delivery window includes weekends

  • Holidays fall in the shipping period

  • Origin and destination have different holiday schedules

  • The origin country has a non-Western workweek

Basic Business Day Calculation#

Account for weekends and origin country holidays:

javascript
async function calculateDeliveryDate(origin, orderDate, businessDays) {
	const response = await fetch(
		`https://worlddataapi.com/v1/business-days/${origin}/add?` +
			`start=${orderDate}&days=${businessDays}`,
		{ headers: { "X-API-Key": API_KEY } },
	);

	const data = await response.json();
	return data.result;
}

// 5 business days from order, accounting for US holidays
const delivery = await calculateDeliveryDate("US", "2026-12-18", 5);
// Returns "2026-12-29" — skips weekends AND Christmas

Multi-Country Shipping#

International orders involve holidays in both origin and destination countries. A package can't be processed if the origin warehouse is closed, and can't be delivered if the destination country has a holiday.

Strategy 1: Origin-Based Calculation#

Calculate using origin country holidays. The package ships when the warehouse operates; destination delivery depends on local carriers.

javascript
async function calculateInternationalDelivery(
	origin,
	destination,
	orderDate,
	config,
) {
	const { processingDays, transitDays } = config;

	// Processing uses origin business days
	const processedDate = await addBusinessDays(
		origin,
		orderDate,
		processingDays,
	);

	// Transit is calendar days (planes fly on holidays)
	const transitDate = addCalendarDays(processedDate, transitDays);

	// Final delivery uses destination business days
	const deliveryDate = await getNextBusinessDay(destination, transitDate);

	return {
		orderDate,
		processedDate,
		estimatedDelivery: deliveryDate,
	};
}

async function addBusinessDays(location, start, days) {
	const response = await fetch(
		`https://worlddataapi.com/v1/business-days/${location}/add?start=${start}&days=${days}`,
		{ headers: { "X-API-Key": API_KEY } },
	);
	return (await response.json()).result;
}

async function getNextBusinessDay(location, date) {
	const response = await fetch(
		`https://worlddataapi.com/v1/business-days/${location}/next?from=${date}`,
		{ headers: { "X-API-Key": API_KEY } },
	);
	return (await response.json()).next;
}

function addCalendarDays(date, days) {
	const d = new Date(date);
	d.setDate(d.getDate() + days);
	return d.toISOString().split("T")[0];
}

Strategy 2: Combined Holiday Check#

For more accuracy, check both countries:

javascript
async function getDeliveryWithBothCountries(
	origin,
	destination,
	orderDate,
	totalDays,
) {
	// Get holidays for both countries
	const [originHolidays, destHolidays] = await Promise.all([
		getHolidaySet(origin, getYear(orderDate)),
		getHolidaySet(destination, getYear(orderDate)),
	]);

	// Merge holiday sets
	const allHolidays = new Set([...originHolidays, ...destHolidays]);

	// Calculate manually with combined holidays
	let current = new Date(orderDate);
	let businessDaysAdded = 0;

	while (businessDaysAdded < totalDays) {
		current.setDate(current.getDate() + 1);
		const dateStr = current.toISOString().split("T")[0];

		if (isWeekday(current) && !allHolidays.has(dateStr)) {
			businessDaysAdded++;
		}
	}

	return current.toISOString().split("T")[0];
}

Real-World Delivery Calculation#

A complete implementation handling common scenarios:

javascript
class DeliveryCalculator {
	constructor(apiKey) {
		this.apiKey = apiKey;
		this.baseUrl = "https://worlddataapi.com/v1";
	}

	async calculate(options) {
		const {
			origin,
			destination,
			orderDate,
			orderTime,
			cutoffTime = "14:00",
			processingDays = 1,
			shippingMethod,
		} = options;

		// Check if order was placed before cutoff
		const effectiveOrderDate = this.getEffectiveOrderDate(
			orderDate,
			orderTime,
			cutoffTime,
			origin,
		);

		// Calculate processing completion
		const processedDate = await this.addBusinessDays(
			origin,
			effectiveOrderDate,
			processingDays,
		);

		// Calculate transit time based on shipping method
		const transitDays = this.getTransitDays(
			shippingMethod,
			origin,
			destination,
		);

		// Add transit (calendar days for air, business days for ground)
		let arrivalDate;
		if (
			shippingMethod.includes("air") ||
			shippingMethod.includes("express")
		) {
			arrivalDate = this.addCalendarDays(processedDate, transitDays);
		} else {
			arrivalDate = await this.addBusinessDays(
				origin,
				processedDate,
				transitDays,
			);
		}

		// Ensure delivery falls on a business day in destination
		const deliveryDate = await this.ensureBusinessDay(
			destination,
			arrivalDate,
		);

		return {
			orderDate,
			effectiveOrderDate,
			processedDate,
			arrivalDate,
			estimatedDelivery: deliveryDate,
			shippingMethod,
			businessDaysTotal: await this.countBusinessDays(
				origin,
				orderDate,
				deliveryDate,
			),
		};
	}

	getEffectiveOrderDate(orderDate, orderTime, cutoffTime, origin) {
		// If ordered after cutoff, processing starts next business day
		if (orderTime && orderTime > cutoffTime) {
			// For now, add one calendar day (simplified)
			// Production code should check if next day is a business day
			const next = new Date(orderDate);
			next.setDate(next.getDate() + 1);
			return next.toISOString().split("T")[0];
		}
		return orderDate;
	}

	getTransitDays(method, origin, destination) {
		// Simplified — real implementation uses carrier data
		const domestic = origin.substring(0, 2) === destination.substring(0, 2);

		const transitTimes = {
			ground: domestic ? 5 : 14,
			express: domestic ? 2 : 5,
			overnight: 1,
			"international-air": 7,
			"international-economy": 21,
		};

		return transitTimes[method] || 7;
	}

	async addBusinessDays(location, start, days) {
		const response = await fetch(
			`${this.baseUrl}/business-days/${location}/add?start=${start}&days=${days}`,
			{ headers: { "X-API-Key": this.apiKey } },
		);
		return (await response.json()).result;
	}

	async ensureBusinessDay(location, date) {
		const response = await fetch(
			`${this.baseUrl}/business-days/${location}?date=${date}`,
			{ headers: { "X-API-Key": this.apiKey } },
		);
		const data = await response.json();

		if (data.is_business_day) {
			return date;
		}
		return data.next_business_day;
	}

	async countBusinessDays(location, start, end) {
		const response = await fetch(
			`${this.baseUrl}/business-days/${location}/count?start=${start}&end=${end}`,
			{ headers: { "X-API-Key": this.apiKey } },
		);
		return (await response.json()).business_days;
	}

	addCalendarDays(date, days) {
		const d = new Date(date);
		d.setDate(d.getDate() + days);
		return d.toISOString().split("T")[0];
	}
}

Usage#

javascript
const calculator = new DeliveryCalculator(process.env.API_KEY);

const estimate = await calculator.calculate({
	origin: "US-CA", // Shipping from California
	destination: "GB", // Delivering to UK
	orderDate: "2026-12-18",
	orderTime: "10:30",
	cutoffTime: "14:00",
	processingDays: 2,
	shippingMethod: "international-air",
});

console.log(estimate);
// {
//   orderDate: '2026-12-18',
//   effectiveOrderDate: '2026-12-18',
//   processedDate: '2026-12-22',
//   arrivalDate: '2026-12-29',
//   estimatedDelivery: '2026-12-29',
//   shippingMethod: 'international-air',
//   businessDaysTotal: 7
// }

Displaying Delivery Estimates#

Date Range Instead of Single Date#

Given uncertainty in transit times, show a range:

javascript
function formatDeliveryEstimate(estimate, bufferDays = 2) {
	const earliest = estimate.estimatedDelivery;
	const latest = addCalendarDays(earliest, bufferDays);

	const formatOptions = { weekday: "short", month: "short", day: "numeric" };

	return {
		earliest: new Date(earliest).toLocaleDateString("en-US", formatOptions),
		latest: new Date(latest).toLocaleDateString("en-US", formatOptions),
		display: `${formatDate(earliest)} - ${formatDate(latest)}`,
	};
}

// "Fri, Dec 25 - Sun, Dec 27"

Holiday Warnings#

Alert customers about holiday delays:

javascript
async function getDeliveryWarnings(origin, destination, startDate, endDate) {
	const warnings = [];

	// Check origin holidays
	const originHolidays = await getHolidaysInRange(origin, startDate, endDate);
	if (originHolidays.length > 0) {
		warnings.push({
			type: "origin-holiday",
			message: `Shipping may be delayed due to ${originHolidays[0].name} in origin country.`,
		});
	}

	// Check destination holidays
	const destHolidays = await getHolidaysInRange(
		destination,
		startDate,
		endDate,
	);
	if (destHolidays.length > 0) {
		warnings.push({
			type: "destination-holiday",
			message: `Delivery may be delayed due to ${destHolidays[0].name}.`,
		});
	}

	// Peak season warning
	const month = new Date(startDate).getMonth();
	if (month === 11 || month === 0) {
		warnings.push({
			type: "peak-season",
			message: "Holiday season may cause additional delays.",
		});
	}

	return warnings;
}

Regional Considerations#

US State Holidays#

California orders may be delayed on César Chávez Day (March 31) when shipping from California warehouses:

javascript
// Use state-level location for accuracy
const estimate = await calculator.calculate({
	origin: "US-CA", // Not just 'US'
	destination: "US-NY",
	// ...
});

Non-Western Workweeks#

Shipping from Israel or the UAE? Their workweek differs:

javascript
// Israel: Sunday-Thursday
// The API handles this automatically
const estimate = await calculator.calculate({
	origin: "IL",
	destination: "US",
	orderDate: "2026-01-02", // Friday — weekend in Israel
	// ...
});
// Processing starts Sunday, not Monday

Caching for Performance#

Delivery estimates are calculated frequently. Cache holiday data:

javascript
class CachedDeliveryCalculator extends DeliveryCalculator {
	constructor(apiKey) {
		super(apiKey);
		this.holidayCache = new Map();
	}

	async getHolidaySet(location, year) {
		const key = `${location}:${year}`;

		if (!this.holidayCache.has(key)) {
			const response = await fetch(
				`${this.baseUrl}/holidays/${location}?year=${year}`,
				{ headers: { "X-API-Key": this.apiKey } },
			);
			const data = await response.json();
			this.holidayCache.set(
				key,
				new Set(data.holidays.map((h) => h.date)),
			);
		}

		return this.holidayCache.get(key);
	}
}

Common Pitfalls#

Half-Day Holidays#

The API treats half-day holidays (such as Christmas Eve in some countries) as full holidays. This is a known limitation.

Important: The World Data API does not distinguish between half-day and full-day holidays. Days like Christmas Eve, New Year's Eve, or the day before Thanksgiving are treated as full holidays in the business day calculation. If your business operates on half-days, you may need to add manual adjustments to your calculations.

For example, if your warehouse operates until noon on Christmas Eve, the API will skip that day entirely. Consider adding custom logic for these edge cases:

javascript
const halfDayHolidays = {
	US: ["12-24", "12-31"], // Christmas Eve, New Year's Eve
	DE: ["12-24", "12-31"], // Heiligabend, Silvester
};

function isHalfDay(location, date) {
	const monthDay = date.slice(5); // "MM-DD"
	return halfDayHolidays[location]?.includes(monthDay) || false;
}

Cutoff Time Timezone#

Ensure cutoff time matches warehouse timezone:

javascript
function isBeforeCutoff(orderTime, cutoffTime, warehouseTimezone) {
	const orderMoment = new Date(orderTime);
	const cutoff = new Date(orderTime);

	// Parse cutoff time in warehouse timezone
	const [hours, minutes] = cutoffTime.split(":");
	cutoff.setHours(parseInt(hours), parseInt(minutes), 0, 0);

	// Compare in same timezone
	return orderMoment < cutoff;
}

Year Boundaries#

Orders near year-end may span two years:

javascript
async function getHolidaysForRange(location, startDate, endDate) {
	const startYear = new Date(startDate).getFullYear();
	const endYear = new Date(endDate).getFullYear();

	const holidays = [];
	for (let year = startYear; year <= endYear; year++) {
		const yearHolidays = await getHolidays(location, year);
		holidays.push(...yearHolidays);
	}

	return holidays.filter((h) => h.date >= startDate && h.date <= endDate);
}

Carrier Operating Days#

Some carriers don't deliver on Saturdays. Adjust final delivery:

javascript
async function adjustForCarrier(deliveryDate, carrier, destination) {
	const date = new Date(deliveryDate);
	const dayOfWeek = date.getDay();

	if (carrier.noSaturdayDelivery && dayOfWeek === 6) {
		// Push to Monday (or next business day)
		return getNextBusinessDay(destination, deliveryDate);
	}

	if (carrier.noSundayDelivery && dayOfWeek === 0) {
		return getNextBusinessDay(destination, deliveryDate);
	}

	return deliveryDate;
}

Testing#

javascript
describe("DeliveryCalculator", () => {
	it("skips Christmas", async () => {
		const calc = new DeliveryCalculator(API_KEY);
		const result = await calc.calculate({
			origin: "US",
			destination: "US",
			orderDate: "2026-12-22",
			processingDays: 1,
			shippingMethod: "ground",
		});

		// Should skip Dec 25
		expect(result.estimatedDelivery).not.toBe("2026-12-25");
	});

	it("handles Friday orders after cutoff", async () => {
		const calc = new DeliveryCalculator(API_KEY);
		const result = await calc.calculate({
			origin: "US",
			destination: "US",
			orderDate: "2026-01-16", // Friday
			orderTime: "15:00", // After 2pm cutoff
			processingDays: 1,
			shippingMethod: "overnight",
		});

		// Processing starts Monday, delivery Tuesday
		expect(result.processedDate).toBe("2026-01-20");
	});
});

Summary#

Building accurate delivery date calculations requires handling several complexities:

  • Use business day calculations instead of naive calendar day additions to skip weekends and holidays automatically

  • Account for origin and destination holidays when shipping internationally

  • Implement cutoff times in the correct timezone to determine when processing starts

  • Handle year boundaries by fetching holidays for multiple years when orders span December-January

  • Add buffer time and display date ranges rather than single dates to account for carrier variability

  • Adjust for carrier schedules since not all carriers deliver on weekends

The World Data API handles regional workweeks (like Israel's Sunday-Thursday) and country-specific holidays automatically, but remember that half-day holidays are treated as full days off.

Ready to implement accurate delivery estimates? Get your API key and start with 60 free requests per day, or upgrade to Premium for access to business day calculations.

Next Steps#