配送日 祝日 計算:チェックアウトで2か国にまたがる配送日を約束する

配送日を表示するチェックアウトでは、発送日の数え方を倉庫のある国、配送日数の数え方を配送先の国に合わせる必要があります。Narwhal は両国の祝日を提供し、締め切り時刻と配送日数はショップの設定のままです。

カートの日付は2つのカレンダーに対して約束される

顧客に見える日付は1つですが、決めているのは2つの国です。倉庫は自国の営業日にしか発送せず、配送業者は配送先の営業日にしか届けません。ドイツの倉庫にとって平日であるクリスマスイブは、ポーランドの購入者にとっては祝日です。このリクエストは、サンプル注文の日から数えて、ポーランドの次の休業日を尋ねます。

curl、配送先の国の次の休業日
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日のクリスマスが返るので、ポーランド最初の休業日はドイツより1日早くなります。coverage は、両国のカレンダーが国の祝日のみであることを示します。

締め切り、発送、配送日数から約束日を作る

入力値の持ち主はそれぞれ違います。表は、ショップの側にあるものとカレンダーから来るものを示しています。

入力値オーナー例
注文の締め切りショップの設定倉庫の時刻で16:00
発送日倉庫のカレンダー締め切りルールのあと最初の営業日(平日)
配送日数配送業者の契約3日
配送日配送先のカレンダー営業日の平日だけを数える
祝日Narwhal の年間リスト国と年ごとに1リクエスト

下の関数は、注文が締め切りのあとに入った場合や休業日に入った場合に、注文を倉庫の次の営業日に送ります。そのあと、配送先の営業日の平日で配送日数を数えます。国と年ごとにリストを1回だけ読み込み、数え方が1月1日をまたぐときは翌年を読み込みます。

python、発送日と配送日
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 2027

12月21日月曜の14:00の注文はその日に出荷され、12月28日月曜に届きます。同じ注文が17:30の場合は12月22日火曜に出荷され、12月29日火曜に届きます。ポーランドではクリスマスイブが休業日のため、1つ目より1日遅くなります。ドイツ宛てなら、17:30の注文は12月28日月曜に届きます。12月31日木曜の注文は年をまたぎ、1月1日とポーランドの1月6日の公現祭を飛ばして、2027年1月7日木曜に届きます。

範囲を表示し、ルールは1か所にまとめる

配送業者は2〜3日のように幅で見積もります。関数を両端について1回ずつ実行します。上の17:30の注文では、配送日数2日で12月28日月曜、3日で12月29日火曜になるので、カートは「この日付の間にお届け」と表示できます。締め切り、配送日数、週末のルールは1つの関数にまとめ、カート、確認メール、注文ページのすべてがそれを呼び出すようにして、3か所で日付が食い違わないようにします。まず注文時刻を倉庫の時計に変換してください。購入者の夜は倉庫ではすでに翌朝のことがあり、締め切りの判定は倉庫の時刻で行うためです。

カレンダーに載っているのは祝日です。倉庫の操業停止、配送業者の繁忙期割増日、地域の休業は分かりません。ドイツのレスポンスは国全体の範囲のみなので、州の祝日は含まれません。そうした日付は、関数が読む集合に追加してください。

どのステップでどの国のカレンダーを使いますか。

発送日は倉庫の国で決まります。配送日数は、ラストワンマイルが行われる配送先の国で決まります。荷物が第三国を通る場合は、配送業者がその国の日数を数えるときだけ加えます。

休業日に入った注文はどうなりますか。

倉庫の次の営業日から始まります。関数は締め切りを適用する前に is_open で注文日を確認するので、土曜や祝日の注文は次の営業日に発送されます。締め切り時刻はショップのルールで、倉庫の現地時刻で比較します。

チェックアウトはカートを表示するたびに API を呼べますか。

その必要はありません。年間リストは、同じ国・同じ年のすべての注文で共通なので、スクリプトはキャッシュに保存し、1回のリクエストですべてのカートをまかないます。キャッシュは都合のよい間隔で更新してください。営業日を日付に加えるガイド は同じ計算を1か国で示し、祝日の確認ガイド は確認ステップ向けの単一日付のエンドポイントを説明します。

土曜を配送日に数えられますか。

はい。配送業者が土曜に配達する場合は、is_open の曜日判定を day.weekday() < 5 から day.weekday() < 6 に変えます。祝日の確認はそのまま残るので、配送先で祝日になる土曜はスキップされます。倉庫は土曜休みで配送業者は土曜も動く場合は、発送用と配送用に別々の判定を持たせてください。

公開。

次のステップ