显示送达日期的结账页面,必须按仓库所在国计算发货日,按目的国计算承运天数。Narwhal 提供两国的公共假日,截止时间和承运天数仍是店铺自己的设置。
购物车里的日期是按两个日历承诺的
客户只看到一个日期,但由两个国家共同决定。仓库只在本国工作日发货,承运商只在目的国工作日投递。对德国仓库来说是普通日子的平安夜,对波兰买家却是公共假日。这个请求从示例订单当天起,查询波兰的下一个休息日。
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": []
}
}holiday 对象给出 date、英文 name、local_name 和 scope。对德国发出同样的请求会返回 12 月 25 日的圣诞节,所以波兰的第一个休息日比德国早一天。coverage 显示两个日历都只包含全国性假日。
根据截止时间、发货日和承运天数得出承诺日期
各项输入的来源不同。下表显示哪些来自店铺,哪些来自日历。
| 输入 | 所有者 | 示例 |
|---|---|---|
| 下单截止时间 | 店铺设置 | 仓库当地时间 16:00 |
| 发货日 | 仓库日历 | 按截止规则之后的第一个开放工作日 |
| 承运天数 | 承运合同 | 3 天 |
| 送达日 | 目的国日历 | 只计算开放的工作日 |
| 节假日 | Narwhal 年度列表 | 每个国家每年一次请求 |
下面的函数在订单于截止时间之后或休息日到达时,把订单顺延到仓库的下一个工作日。然后按目的国的开放工作日计算承运天数。它对每个国家和年份只加载一次,当计数跨过 1 月 1 日时再加载下一年。
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 202712 月 21 日星期一 14:00 的订单当天发出,12 月 28 日星期一送达。同一订单如果在 17:30 下单,则于 12 月 22 日星期二发出,12 月 29 日星期二送达,比第一种晚一天,因为平安夜在波兰是休息日。如果改发德国,17:30 的订单会在 12 月 28 日星期一送达。12 月 31 日星期四的订单会跨年,跳过 1 月 1 日和波兰的 1 月 6 日主显节,在 2027 年 1 月 7 日星期四送达。
显示一个范围,并把规则放在一处
承运商通常报一个范围,例如 2 到 3 天。对两个端点各运行一次函数。对上面 17:30 的订单,承运 2 天是 12 月 28 日星期一,3 天是 12 月 29 日星期二,所以购物车可以显示在这两个日期之间送达。截止时间、承运天数和周末规则应放在同一个函数里,让购物车、确认邮件和订单页面都调用它,这样三处不会显示不同的日期。请先把下单时间转换为仓库的时钟,因为买家的晚上在仓库可能已经是第二天早上,而截止判断读取的是仓库时间。
日历列出的是公共假日。它不知道仓库停工、承运商的旺季附加费日期或地区性休息。德国的响应只包含全国范围,所以州假日不在其中。请把这些日期加入函数所读取的集合。
每一步该用哪个国家的日历?
发货国决定发货日。目的国决定承运天数,因为最后一段派送发生在那里。如果包裹经过第三国,只有在承运商计算该国天数时,才把它的天数加入计数。
在休息日下的订单会怎样?
它从仓库的下一个工作日开始处理。函数在应用截止时间之前,先用 is_open 检查订单日期,所以周六或假日的订单会在下一个开放日发货。截止时间是店铺的规则,按仓库当地时间比较。
结账页面每次查看购物车都要调用 API 吗?
不需要。同一国家同一年的年度列表对所有订单都一样,所以脚本把它保存在缓存中,一次请求就能服务所有购物车。请按适合你的频率刷新缓存。增加工作日指南展示了在单个国家内同样的计算,公共假日检查指南展示了用于确认步骤的单日期端点。
周六可以算作送达日吗?
可以,前提是承运商周六投递。把 is_open 中的星期判断从 day.weekday() < 5 改为 day.weekday() < 6。假日检查仍然保留,所以在目的国是公共假日的周六依然会被跳过。如果仓库周六休息而承运商不休息,请为发货和送达分别保留一个判断。
发布于。