A checkout that shows a delivery date has to count dispatch days in the warehouse country and carrier days in the destination country. Narwhal supplies the public holidays of both countries, and the cut-off and carrier days stay settings of the shop.
The date in the cart is promised against two calendars
A customer sees one date, but two countries decide it. The warehouse dispatches only on its own working days, and the carrier delivers only on the destination's. A Christmas Eve that is an ordinary day for a warehouse in Germany is a public holiday for a buyer in Poland. This request asks Poland for the next closed day from the day of an example order.
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
"https://api.narwhalapi.com/v1/calendars/holidays/POL/next?from_date=2026-12-21"{
"country": "POL",
"coverage": [
{
"scope": "national_public",
"subdivision_codes": []
}
],
"holiday": {
"date": "2026-12-24",
"name": "Christmas Eve",
"holiday_key": "christmas_eve",
"local_name": "Wigilia Bożego Narodzenia",
"scope": "national_public",
"subdivision_codes": []
}
}The holiday object gives the date, the English name, the local_name and the scope. The same request for Germany returns Christmas Day on 25 December, so the first Polish closure falls a day earlier than the first German one. coverage shows that both calendars hold national holidays only.
Build the promise from cut-off, dispatch and carrier days
The inputs have different owners. The table shows which come from the shop and which from the calendar.
| Input | Owner | Example |
|---|---|---|
| Order cut-off | Shop setting | 16:00 in the warehouse's time |
| Dispatch day | Warehouse calendar | First open weekday at or after the cut-off rule |
| Carrier days | Carrier contract | 3 days |
| Delivery day | Destination calendar | Counts only open weekdays |
| Public holidays | Narwhal year list | One request per country and year |
The function below moves the order to the next warehouse working day when it arrives after the cut-off or on a closed day. It then counts carrier days on the destination's open weekdays. It loads each country and year once and loads the next year when the count crosses 1 January.
import json, os, urllib.request
from datetime import datetime, time, timedelta
from functools import cache
def get(path):
req = urllib.request.Request("https://api.narwhalapi.com" + path,
headers={"Authorization": "Bearer " + os.environ["NARWHAL_API_KEY"]})
with urllib.request.urlopen(req, timeout=10) as r:
return json.load(r)
@cache
def closed_days(country, year):
return {h["date"] for h in get(f"/v1/calendars/holidays/{country}/{year}")["holidays"]}
def is_open(day, country):
return day.weekday() < 5 and day.isoformat() not in closed_days(country, day.year)
def promise(placed, cutoff, origin, dest, transit_days):
dispatch = placed.date()
if placed.time() >= cutoff or not is_open(dispatch, origin):
dispatch += timedelta(1)
while not is_open(dispatch, origin):
dispatch += timedelta(1)
day, left = dispatch, transit_days
while left:
day += timedelta(1)
left -= is_open(day, dest)
return dispatch, day
CUTOFF = time(16, 0)
for placed, dest in [
(datetime(2026, 12, 21, 14, 0), "POL"),
(datetime(2026, 12, 21, 17, 30), "POL"),
(datetime(2026, 12, 21, 17, 30), "DEU"),
(datetime(2026, 12, 31, 10, 0), "POL"),
]:
dispatch, arrive = promise(placed, CUTOFF, "DEU", dest, 3)
print(f"{placed:%a %d %b %H:%M} to {dest}: dispatch {dispatch:%a %d %b}, delivery {arrive:%a %d %b %Y}")
Mon 21 Dec 14:00 to POL: dispatch Mon 21 Dec, delivery Mon 28 Dec 2026
Mon 21 Dec 17:30 to POL: dispatch Tue 22 Dec, delivery Tue 29 Dec 2026
Mon 21 Dec 17:30 to DEU: dispatch Tue 22 Dec, delivery Mon 28 Dec 2026
Thu 31 Dec 10:00 to POL: dispatch Thu 31 Dec, delivery Thu 07 Jan 2027An order at 14:00 on Monday 21 December leaves the same day and arrives Monday 28 December. The same order at 17:30 leaves on Tuesday 22 December and arrives Tuesday 29 December, a day later than the first because Christmas Eve is closed in Poland. Sent to Germany instead, the 17:30 order arrives Monday 28 December. An order on Thursday 31 December crosses the year, skips 1 January and Epiphany on 6 January in Poland, and arrives Thursday 7 January 2027.
Show a range and keep the rule in one place
Carriers quote a range such as 2 to 3 days. Run the function once for each end. For the 17:30 order above, 2 carrier days gives Monday 28 December and 3 gives Tuesday 29 December, so the cart can say delivery between those dates. The cut-off, the carrier days and the weekend rule belong in one function that the cart, the confirmation email and the order page all call, so that the three never show different dates. Convert the order time to the warehouse's clock first, because a buyer's evening can already be the next morning at the warehouse and the cut-off test reads the warehouse's time.
The calendar lists public holidays. It does not know a depot shutdown, a carrier's peak surcharge days or a regional closure. Germany's response holds the national scope only, so a state holiday is absent. Add those dates to the set the function reads.
Which country's calendar counts for which step?
The warehouse country decides the dispatch day. The destination country decides the carrier days, since the last mile happens there. If a parcel crosses a third country, add that country's days to the count only when the carrier counts them.
What happens to an order placed on a closed day?
It starts at the next warehouse working day. The function checks the order date with is_open before it applies the cut-off, so a Saturday or a holiday order is dispatched on the next open day. A cut-off time is a shop rule and is compared in the warehouse's local time.
Can the checkout call the API for every cart view?
It does not need to. The year list is the same for every order of a country and year, so the script keeps it in a cache and one request covers all carts. Refresh the cache on a schedule that suits you. The guide to adding business days shows the same walk in a single country, and the holiday check guide shows the single-date endpoint for a confirmation step.
Can Saturday count as a delivery day?
Yes, when the carrier delivers on Saturdays. Change the weekday test in is_open from day.weekday() < 5 to day.weekday() < 6. The holiday check stays in place, so a Saturday that is a public holiday in the destination is still skipped. Keep one test for dispatch and another for delivery if the warehouse is closed on Saturday and the carrier is not.
Published .