๐ก We check Nager.Date (and 75+ other public dev APIs) with a plain GET every 30 minutes โ see the live status page. As of publishing it's up and answering normally. This guide is not about availability โ it's about what time-dependent endpoints do to your tests.
Nager.Date is one of the good ones โ arguably the best free reference-data API on the internet. Public holidays and long weekends for 200+ countries (we counted the AvailableCountries list: 204), no key, no signup, CORS enabled, no published rate limits, and the whole project is open source so you can self-host it. A paid Pro API now exists to fund the work, and the community API stayed free. Nothing on this page is a complaint about any of that.
The trap isn't the service โ it's what happens when your tests call an API whose answers depend on today's date (all of this checked live on September 18, 2026, and confirmed in their own OpenAPI document):
GET /api/v3/IsTodayPublicHoliday/US answers 200 if today is a public holiday and 204 if it isn't. The code path that shows the "we're closed" banner, pauses the SLA clock, or skips the payroll run can only be exercised against the real API on an actual holiday. Your test for the most business-critical branch runs green 11 months a year because it never enters the branch.fetch(url).then(r => r.json()) throws SyntaxError: Unexpected end of JSON input on the everyday branch. Correct code reads r.status === 200 and never calls .json() โ a shape worth drilling on purpose.NextPublicHolidays/US is a rolling window: the test that asserts "Columbus Day" is first in the list is correct in September and broken on October 13. Date-dependent fixtures are flaky fixtures.404 with an RFC-7807 problem object ({"title":"Unknown country code",โฆ}); an out-of-range year answers 400 with an ASP.NET validation dictionary ({"errors":{"year":[โฆ]}}). Client code that assumes one error shape mis-parses the other.The fix isn't to stop using Nager.Date โ it's to stop pointing tests at a clock you don't control. Host a real year of their data verbatim on a free mock API, reproduce their exact URLs, and make "is today a holiday" a scenario you pick instead of a date you wait for. No signup.
These are the genuine Nager.Date records for PublicHolidays/2026/US (fetched September 18, 2026) with an id added โ swap in your countries:
cat > holidays.json <<'EOF'
{
"holidays": [
{"id":1,"date":"2026-01-01","localName":"New Year's Day","name":"New Year's Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Public","Bank"]},
{"id":2,"date":"2026-01-19","localName":"Martin Luther King, Jr. Day","name":"Martin Luther King, Jr. Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Public","Bank"]},
{"id":3,"date":"2026-02-12","localName":"Lincoln's Birthday","name":"Lincoln's Birthday","countryCode":"US","fixed":false,"global":false,"counties":["US-CA","US-CT","US-IL","US-IN","US-KY","US-MI","US-NY","US-MO","US-OH"],"launchYear":null,"types":["Observance"]},
{"id":4,"date":"2026-02-16","localName":"Washington's Birthday","name":"Presidents Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Public","Bank"]},
{"id":5,"date":"2026-04-03","localName":"Good Friday","name":"Good Friday","countryCode":"US","fixed":false,"global":false,"counties":["US-CT","US-DE","US-HI","US-IN","US-KY","US-LA","US-NC","US-ND","US-NJ","US-TN"],"launchYear":null,"types":["Public"]},
{"id":6,"date":"2026-04-03","localName":"Good Friday","name":"Good Friday","countryCode":"US","fixed":false,"global":false,"counties":["US-TX"],"launchYear":null,"types":["Optional"]},
{"id":7,"date":"2026-05-08","localName":"Truman Day","name":"Truman Day","countryCode":"US","fixed":false,"global":false,"counties":["US-MO"],"launchYear":null,"types":["School","Authorities"]},
{"id":8,"date":"2026-05-25","localName":"Memorial Day","name":"Memorial Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Public","Bank"]},
{"id":9,"date":"2026-06-19","localName":"Juneteenth National Independence Day","name":"Juneteenth National Independence Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Public","Bank"]},
{"id":10,"date":"2026-07-03","localName":"Independence Day","name":"Independence Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Public","Bank"]},
{"id":11,"date":"2026-09-07","localName":"Labor Day","name":"Labour Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Public","Bank"]},
{"id":12,"date":"2026-10-12","localName":"Columbus Day","name":"Columbus Day","countryCode":"US","fixed":false,"global":false,"counties":["US-AL","US-AZ","US-CO","US-CT","US-GA","US-ID","US-IL","US-IN","US-IA","US-KS","US-KY","US-LA","US-ME","US-MD","US-MA","US-MS","US-MO","US-MT","US-NE","US-NH","US-NJ","US-NM","US-NY","US-NC","US-OH","US-OK","US-PA","US-RI","US-SC","US-TN","US-UT","US-VA","US-WV"],"launchYear":null,"types":["Public"]},
{"id":13,"date":"2026-10-12","localName":"Columbus Day","name":"Columbus Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Bank"]},
{"id":14,"date":"2026-10-12","localName":"Indigenous Peoples' Day","name":"Indigenous Peoples' Day","countryCode":"US","fixed":false,"global":false,"counties":["US-AK","US-AL","US-CA","US-HI","US-IA","US-LA","US-ME","US-MI","US-MN","US-NC","US-NE","US-NM","US-OK","US-OR","US-SD","US-TX","US-VA","US-VT","US-WI"],"launchYear":null,"types":["Public"]},
{"id":15,"date":"2026-11-11","localName":"Veterans Day","name":"Veterans Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Public","Bank"]},
{"id":16,"date":"2026-11-26","localName":"Thanksgiving Day","name":"Thanksgiving Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Public","Bank"]},
{"id":17,"date":"2026-12-25","localName":"Christmas Day","name":"Christmas Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Public","Bank"]}
]
}
EOF
curl -X POST "https://mockbird.mockbird.workers.dev/api/projects/import?name=holidays" \
-H 'content-type: application/json' --data-binary @holidays.json
# response includes your project id + admin key
# (two warnings about "types"/"counties" are expected โ the arrays are kept
# verbatim in the records, they're just not part of the filterable schema)
Now the questions your app actually asks are query parameters:
# is a given date a holiday? (v3 has no single-date endpoint โ your mock does)
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/holidays?date=2026-12-25"
# โ [{"date":"2026-12-25","localName":"Christmas Day",โฆ}] X-Total-Count: 1
# careful: one date can be several rows โ 2026-10-12 is Columbus Day (state),
# Columbus Day (bank) AND Indigenous Peoples' Day. Real data, three records:
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/holidays?date=2026-10-12"
# โ X-Total-Count: 3
# everything in H2, nationwide-only, just the fields the UI needs
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/holidays?date_gte=2026-07-01"
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/holidays?global=true&select=localName,date"
And because it's a real CRUD store, your company's days off can live in the same collection โ something no public holidays API will ever host for you:
curl -X POST "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/holidays" \
-H 'content-type: application/json' \
-d '{"date":"2026-12-24","localName":"Company day off","name":"Company day off","countryCode":"US","fixed":true,"global":false,"counties":null,"launchYear":null,"types":["Company"]}'
Custom routes serve Nager.Date's actual paths, so switching your app to the mock is a base-URL change and nothing else. The year list at their exact URL, built straight from your import file (no hand-copying):
curl -X POST "https://mockbird.mockbird.workers.dev/api/projects/PROJECT_ID/routes" \
-H 'x-admin-key: KEY' -H 'content-type: application/json' \
-d "$(jq -n --arg body "$(jq -c '.holidays | map(del(.id))' holidays.json)" \
'{method:"GET",path:"/api/v3/PublicHolidays/2026/US",body:$body,contentType:"application/json"}')"
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/api/v3/PublicHolidays/2026/US"
# โ byte-for-byte the array Nager.Date served on capture day โ forever
A frozen NextPublicHolidays โ in your tests the next holiday is always Veterans Day, even in July 2031:
curl -X POST "https://mockbird.mockbird.workers.dev/api/projects/PROJECT_ID/routes" \
-H 'x-admin-key: KEY' -H 'content-type: application/json' \
-d '{"method":"GET","path":"/api/v3/NextPublicHolidays/:code","body":"[{\"date\":\"2026-11-11\",\"localName\":\"Veterans Day\",\"name\":\"Veterans Day\",\"countryCode\":\"US\",\"fixed\":false,\"global\":true,\"counties\":null,\"launchYear\":null,\"types\":[\"Public\",\"Bank\"]},{\"date\":\"2026-11-26\",\"localName\":\"Thanksgiving Day\",\"name\":\"Thanksgiving Day\",\"countryCode\":\"US\",\"fixed\":false,\"global\":true,\"counties\":null,\"launchYear\":null,\"types\":[\"Public\",\"Bank\"]},{\"date\":\"2026-12-25\",\"localName\":\"Christmas Day\",\"name\":\"Christmas Day\",\"countryCode\":\"US\",\"fixed\":false,\"global\":true,\"counties\":null,\"launchYear\":null,\"types\":[\"Public\",\"Bank\"]}]","contentType":"application/json"}'
And the two branches of IsTodayPublicHoliday โ the real API decides by the calendar; your mock decides by the URL prefix. Point the scenario's base URL at /m/PROJECT_ID and today is a holiday; point it at /m/PROJECT_ID/noholiday and it isn't. Same paths below the prefix, empty bodies and all, any day of the year:
# branch you can never trigger on demand for real: 200, empty body
curl -X POST "https://mockbird.mockbird.workers.dev/api/projects/PROJECT_ID/routes" \
-H 'x-admin-key: KEY' -H 'content-type: application/json' \
-d '{"method":"GET","path":"/api/v3/IsTodayPublicHoliday/:code","status":200,"body":"","contentType":"application/json"}'
# the everyday branch: 204, empty body โ the res.json() killer
curl -X POST "https://mockbird.mockbird.workers.dev/api/projects/PROJECT_ID/routes" \
-H 'x-admin-key: KEY' -H 'content-type: application/json' \
-d '{"method":"GET","path":"/noholiday/api/v3/IsTodayPublicHoliday/:code","status":204,"body":"","contentType":"application/json"}'
curl -i "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/api/v3/IsTodayPublicHoliday/US" # HTTP 200
curl -i "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/noholiday/api/v3/IsTodayPublicHoliday/US" # HTTP 204
Now the "we're closed today" banner, the skipped payroll run, and the r.status === 200-not-.json() handling all have a test that runs every day โ not once a year.
| Nager.Date | Your Mockbird project |
|---|---|
/api/v3/PublicHolidays/2026/US | same path via the route above โ or /holidays with filters |
| is this date a holiday? (no endpoint โ fetch year, filter client-side) | /holidays?date=2026-12-25, answer in X-Total-Count |
IsTodayPublicHoliday โ whatever today is | 200 or 204 โ your choice per scenario, via base-URL prefix |
NextPublicHolidays โ rolling, rots your assertions | frozen fixture โ deterministic forever |
| national + regional data for 200+ countries | only what you seed โ see the honest split below |
| company-specific days off: never | POST /holidays โ one collection for both |
Their exact bodies, captured live โ drill that your client parses both (one's a problem object, one's a validation dictionary):
# unknown country โ 404 RFC-7807 problem object
curl -X POST "https://mockbird.mockbird.workers.dev/api/projects/PROJECT_ID/routes" \
-H 'x-admin-key: KEY' -H 'content-type: application/json' --data-binary @- <<'EOF'
{"method":"GET","path":"/api/v3/PublicHolidays/2026/XX","status":404,"body":"{\"title\":\"Unknown country code\",\"status\":404,\"detail\":\"The country code provided is invalid or not recognized.\"}","contentType":"application/json"}
EOF
# unsupported year โ 400 validation dictionary (different shape!)
curl -X POST "https://mockbird.mockbird.workers.dev/api/projects/PROJECT_ID/routes" \
-H 'x-admin-key: KEY' -H 'content-type: application/json' --data-binary @- <<'EOF'
{"method":"GET","path":"/api/v3/PublicHolidays/1066/US","status":400,"body":"{\"title\":\"One or more validation errors occurred.\",\"status\":400,\"errors\":{\"year\":[\"The specified year '1066' is not supported.\"]}}","contentType":"application/json"}
EOF
# and generic resilience: two 503s then success, deterministically
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/holidays?mock_seq=503,503,200&mock_seq_key=w1"
More failure recipes in testing loading & error states.
Everywhere that isn't a test. Holidays are law, and law changes โ a country adds a holiday, moves an observance, and Nager.Date's maintainers ship the update; your fixture from last September won't know. So production lookups belong on the real thing: the free community API within its terms, the Pro API, or a self-hosted instance if you want the data on your infrastructure. The honest split: real calendars for real users โ Nager.Date. Every request where the test needs to control the calendar โ local dev, CI, Storybook, demos โ โ your import, where December 25th is whenever you say it is.
The one-curl import in section 1, or zero setup at all:
curl https://mockbird.mockbird.workers.dev/m/demo/products?limit=3
Facts checked live on September 18, 2026: IsTodayPublicHoliday/US answered 204 with an empty body (their OpenAPI documents 200 = "Today is a public holiday", 204 = "Today is not a public holiday", neither with a body schema); PublicHolidays/2026/XX answered 404 {"title":"Unknown country code","status":404,"detail":"The country code provided is invalid or not recognized."}; PublicHolidays/1066/US answered 400 {"title":"One or more validation errors occurred.","status":400,"errors":{"year":["The specified year '1066' is not supported."]}}; Access-Control-Allow-Origin: * confirmed on responses to cross-origin requests; the v3 path list (no single-date endpoint) is from their published OpenAPI; country count 204 from AvailableCountries. Every Mockbird command on this page was run against a live project before publishing (import with the two expected warnings, verbatim Christmas lookup with X-Total-Count 1, the 3-rows-one-date check, range + boolean filters with ?select=, the exact-path year route parse-equal to the live array, both IsToday branches incl. CORS on the 204, both error drills byte-exact, mock_seq 503/503/200, and the company-day POST read-back), and the scratch project was deleted after.
Full API reference in the docs. More guides: Deterministic test data ยท Custom endpoints ยท Testing loading & error states ยท Mock any third-party API. Create your API โ