Fetch each country's holiday dates once per year, keep them in a set, and test dates against it. For 2026, that means one request for the whole year instead of one per date. Friday 27 November 2026 is a business day under this rule; Thursday 26 November 2026 is Thanksgiving and is not. A business day must be neither Saturday nor Sunday and must be absent from the holiday set.
Do it with the API
GET /v1/calendars/holidays/{country}/{year} returns the whole year. Cache the returned date strings by country and year so a job that handles thousands of dates makes one request per country-year. Keep the country and year together in the cache: the set you use must match the date you pass to the function.
- Set your API key Get a free key and keep it in the
NARWHAL_API_KEYenvironment variable. The scripts read that variable and send the key asAuthorization: Bearer YOUR_KEYtohttps://api.narwhalapi.com. - Use the cached Python function The function stores holiday strings in a
frozensetand uses the country and year as cache arguments. Callis_business_daywith a Pythondate; its default country isUSA, and a weekend returnsFalsebefore the holiday lookup.Python, a cached business-day checkimport os from datetime import date from functools import lru_cache import requests HEADERS = {"Authorization": f"Bearer {os.environ['NARWHAL_API_KEY']}"} @lru_cache(maxsize=None) def holiday_dates(country: str, year: int) -> frozenset[str]: r = requests.get(f"https://api.narwhalapi.com/v1/calendars/holidays/{country}/{year}", headers=HEADERS, timeout=10) r.raise_for_status() return frozenset(h["date"] for h in r.json()["holidays"]) def is_business_day(day: date, country: str = "USA") -> bool: return day.weekday() < 5 and day.isoformat() not in holiday_dates(country, day.year) for d in (date(2026, 11, 26), date(2026, 11, 27), date(2026, 11, 28)): print(d, d.strftime("%a"), is_business_day(d)) - Use the JavaScript function in your app For JavaScript, the map stores a set for each country-year and the function accepts an ISO date string. Pass the country when you need a calendar other than the default
USA; the response status is handled before the dates enter the cache.JavaScript (Node 18+), the same checkconst headers = { Authorization: `Bearer ${process.env.NARWHAL_API_KEY}` }; const cache = new Map(); async function holidayDates(country, year) { const key = `${country}-${year}`; if (!cache.has(key)) { const res = await fetch(`https://api.narwhalapi.com/v1/calendars/holidays/${country}/${year}`, { headers }); if (!res.ok) throw new Error(`Narwhal ${res.status}`); cache.set(key, new Set((await res.json()).holidays.map(h => h.date))); } return cache.get(key); } export async function isBusinessDay(isoDate, country = "USA") { const day = new Date(`${isoDate}T12:00:00Z`).getUTCDay(); if (day === 0 || day === 6) return false; return !(await holidayDates(country, Number(isoDate.slice(0, 4)))).has(isoDate); } console.log(await isBusinessDay("2026-11-26"), await isBusinessDay("2026-11-27"), await isBusinessDay("2026-11-28"));
The Free plan has 5,000 requests a month, so reusing the yearly set matters when a job handles many dates.
The Python cache belongs to holiday_dates; the JavaScript cache is the Map outside the function. Both keep the holiday dates in memory for reuse. Add the refresh schedule and file or database storage when your app needs them. Refreshing the current year monthly and retaining dates across restarts require those additions to the cached lookup.
Do it in a sheet or without code
Keep a Holidays column on its own tab and use Holidays as the range name in the formula. Put the date you want to evaluate in A2. The formula combines the weekday test with a count of that date in the holiday range, so the sheet uses the same business-day rule as the scripts. The holiday column should contain dates for the country and year you are evaluating, just as the API cache does. Keep that column available when you use the formula for other dates.
=AND(WEEKDAY(A2,2)<6, COUNTIF(Holidays, A2)=0)A weekday returns TRUE only when the Holidays range contains no matching date. A Saturday or Sunday returns FALSE even if it is absent from that range. This route suits a team that edits holiday dates by hand: the column holds the dates, while the formula decides whether the date in A2 is a business day.
Which option to use when
| Situation | Best option | Why |
|---|---|---|
| A script or service | The cached set | Reuse the yearly holiday dates across date lookups |
| An app that restarts often | A file or database table with the year, refreshed yearly | Keep the holiday set across app restarts |
| A team that edits dates by hand | A sheet column | Use the holiday dates the team maintains |
For an app that restarts often, store the country alongside the year and holiday dates. Load the matching set for the date being evaluated. The persisted set and the in-memory set use the same holiday membership rule.
How long should I cache holiday dates?
Refresh the current year monthly and fetch next year when it appears. Holiday calendars change rarely, so the yearly set can be reused between refreshes. If your app restarts often, persist that set to a file or database table with its year, then refresh it yearly as well.
Do not cache a failed response. Both scripts handle an unsuccessful response before storing its holiday dates. Calendar revisions can be signalled by the holiday.calendar.revised event on the Basic or Pro webhook and stream.
What time zone should the date be in?
Use the calendar date in the market's own time zone. A UTC midnight date can refer to the previous day in the Americas, so the calendar date you pass to the function matters. Resolve the market's date before you pass it to either script; the holiday set contains date strings, and membership depends on the date string you use.
The Python function takes a date. The JavaScript function takes an ISO date string and uses its year to select the holiday set. Neither call accepts a market time zone argument.
What if the year is not published yet?
The API returns 404 calendar_not_published; fall back to weekends only and log it. In that fallback, Saturday and Sunday remain non-business days, while weekdays pass without a holiday lookup. A weekday result then depends only on the weekend rule because the holiday calendar is unavailable.
Both functions raise an error for an unsuccessful response. Add the weekends-only fallback where your app handles that error, and keep the unpublished response out of the holiday cache. See API errors for the error reference.
Worked example: Thanksgiving week 2026
Start with Thursday 26 November 2026 and the USA calendar for 2026. It is a weekday, so the holiday membership test decides the result. The date is US Thanksgiving, which makes the business-day result False.
Move to Friday 27 November 2026. It passes the weekday test and is absent from the holiday set, so the result is True. Saturday 28 November 2026 fails the weekday test and returns False without a holiday lookup.
| Date | Day | Business day | Reason |
|---|---|---|---|
| 26 November 2026 | Thursday | False | US Thanksgiving |
| 27 November 2026 | Friday | True | Weekday outside the holiday set |
| 28 November 2026 | Saturday | False | Weekend |
2026-11-26 Thu False
2026-11-27 Fri True
2026-11-28 Sat Falsefalse true falsePublished .
