在你自己的應用程式中顯示財經日曆

在產品中提供財經日曆的團隊需要依使用者篩選國家、依每位檢視者的時區顯示發布時間,以及顯示被調整的發布。Narwhal 回傳的發布帶有 UTC 時間、發布機構的時區、所屬期間、前值和修訂計數,篩選、時鐘與快取則由應用程式自行加上。

應用程式向日曆 API 請求什麼

在自己的應用程式裡提供財經日曆的產品團隊,有三件事資料來源無法代勞:讓每位使用者選擇國家、把所有時間顯示為檢視者的時區,以及在發布被調整時讓畫面內容保持正確。API 提供發布資料,應用程式負責篩選、時鐘和快取。GET /v1/economics/calendar 接受 countries(每個國家重複一次,使用 USA 或 CAN 這樣的三字母代碼)、start_date、end_date、limit 和 cursor。這個請求取得兩個國家兩天的資料。

curl,兩個國家兩天
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/economics/calendar?countries=USA&countries=CAN&start_date=2026-11-05&end_date=2026-11-06&limit=100"
回應,前兩筆發布
{
  "releases": [
    {
      "release_id": "1b503ce9-69ee-4ec4-8dd9-deba7f1a6554",
      "revision": 2,
      "country": "USA",
      "series_id": "usa-employment-report",
      "name": "Employment report",
      "topic": "employment",
      "family": "labour",
      "period": "2026-10",
      "status": "scheduled",
      "at": "2026-11-06T13:30:00Z",
      "timezone": "America/New_York",
      "values": [
        {
          "measure": "nonfarm_payrolls_change",
          "concept": "payrolls",
          "unit": "thousands",
          "transformation": "change",
          "previous": "29"
        },
        {
          "measure": "unemployment_rate",
          "concept": "unemployment_rate",
          "unit": "percent",
          "previous": "4.2"
        }
      ]
    },
    {
      "release_id": "20c30c26-9a9a-4218-a8fb-6233ac8d291a",
      "revision": 1,
      "country": "USA",
      "series_id": "usa-productivity-and-costs",
      "name": "Productivity and costs",
      "topic": "other",
      "family": "labour",
      "period": "2026-Q3",
      "stage": "preliminary",
      "status": "scheduled",
      "at": "2026-11-05T13:30:00Z",
      "timezone": "America/New_York",
      "values": []
    }
  ],
  "next_cursor": null
}

完整回應包含五筆發布。at 是 UTC 發布時間,timezone 是發布機構自己的時區,所以應用程式可以為每位檢視者轉換 at,並把發布機構的時區作為第二個標籤顯示。period 是參考期間,values[].previous 是之前的數值。release_id 在不同請求之間識別同一筆發布,其內容變動時 revision 會增加。values 一律存在,而且可能是空清單。

依國家快取,並依檢視者時區呈現

使用者選擇的國家組合各不相同,所以依國家分別快取,再依使用者合併清單。兩位都關注美國的使用者就共用同一份快取清單。

  1. 一次取得一個國家 country_releases 請求一個國家,並跟隨 next_cursor 直到它為 null,因為一頁最多包含 100 筆發布。
  2. 讓資料保持短時效 TTL 是快取的國家資料保持新鮮的秒數。請依使用者需要多快看到被調整的時間來選擇。
  3. 為使用者合併並轉換 user_calendar 彙整使用者的國家,用使用者的時區轉換每個 at,並依當地時間排序。ZoneInfo 會套用檢視者所在地的時鐘變化,所以應用程式不必儲存偏移量。
    Python,附快取的使用者日曆
    import os
    import time
    from datetime import datetime
    from zoneinfo import ZoneInfo
    import requests
    
    BASE = "https://api.narwhalapi.com"
    HEADERS = {"Authorization": f"Bearer {os.environ['NARWHAL_API_KEY']}"}
    TTL = 300  # seconds a cached country stays fresh
    cache = {}  # (country, start, end) -> (fetched_at, releases)
    
    
    def country_releases(country, start, end):
        key = (country, start, end)
        hit = cache.get(key)
        if hit and time.time() - hit[0] < TTL:
            return hit[1]
        params = {"countries": country, "start_date": start, "end_date": end, "limit": 100}
        releases = []
        while True:
            r = requests.get(f"{BASE}/v1/economics/calendar", params=params, headers=HEADERS, timeout=10)
            r.raise_for_status()
            body = r.json()
            releases += body["releases"]
            if not body["next_cursor"]:
                break
            params["cursor"] = body["next_cursor"]
        cache[key] = (time.time(), releases)
        return releases
    
    
    def user_calendar(user, start, end):
        zone = ZoneInfo(user["tz"])
        rows = []
        for country in user["countries"]:
            for x in country_releases(country, start, end):
                if "at" in x:
                    local = datetime.fromisoformat(x["at"].replace("Z", "+00:00")).astimezone(zone)
                    rows.append((local, x))
        return sorted(rows, key=lambda t: t[0])
    
    
    user = {"countries": ["USA", "CAN"], "tz": "Asia/Jakarta"}
    day = None
    for local, x in user_calendar(user, "2026-11-05", "2026-11-06"):
        if local.date() != day:
            day = local.date()
            print(day.isoformat())
        print(f"  {local:%H:%M} {x['country']} {x['name']} {x.get('period', '-')}")

下面的輸出針對一位關注美國和加拿大的雅加達使用者。Credit statistics 的發布時間是 11 月 6 日 20:00 UTC,在雅加達是 11 月 7 日 03:00,所以在該使用者的檢視中歸入另一個日期。請一律依檢視者的當地日期分組,而不是依 UTC 日期。

Asia/Jakarta 使用者的輸出
2026-11-05
  20:30 USA Productivity and costs 2026-Q3
  20:30 USA Jobless claims 2026-10-31
2026-11-06
  20:30 USA Employment report 2026-10
  20:30 CAN Labour force survey 2026-10
2026-11-07
  03:00 USA Credit statistics 2026-09

發布被調整時讓畫面保持正確

隨每一列顯示的內容一起儲存 release_id 和 revision。重新整理後,revision 高於已儲存值的資料列已經變化,需要重繪並標出新時間。狀態為 cancelled 的發布應加刪除線或隱藏;在該時間範圍中不再回傳的資料列可能已移到別的日期,所以請把它從該檢視中移除。

有些發布沒有確切時間,帶有 date、between 或 by 而不是 at。上面的腳本會跳過它們。在實際應用程式中,請把它們作為全天項目列在當天的最前面。若要推播,webhook 會回報 release.scheduled、release.rescheduled 和 release.cancelled,請參閱接收 webhook。

產品團隊只需決定一次的事項

決策選擇欄位或參數
依使用者篩選國家傳送使用者的國家,每個國家一份快取清單countries
顯示的時間把 UTC 時間轉換為檢視者的時區,並把發布機構的時區作為標籤顯示at, timezone
日期分組依檢視者的當地日期分組at
變更偵測比較已儲存的 revision 與新的 revisionrelease_id, revision
主題或系列標籤在應用程式中篩選,因為請求沒有主題參數family, topic
速率限制與重試使用快取,並在收到 429 回應時退避,請參閱快取回應並處理 429 錯誤回應標頭

使用者可以依主題或系列篩選嗎?

請求接受國家、日期、limit 和 cursor,沒有主題篩選。每筆發布都有 family,大多數有 topic,所以請在你自己的程式碼中篩選。請使用 x.get("topic"),因為有些發布沒有主題,直接取值會引發鍵錯誤。

日期範圍依據 UTC 還是發布機構的當地日期?

依據發布機構的當地日期。日本在 UTC 23:30 或 23:50 的發布,在東京已是隔天早晨,所以落在後一天的範圍中。當應用程式依檢視者的時區建構一天時,請在兩側各多請求一天,並依 at 篩選。

一個畫面需要多少次請求?

快取中每個國家一次,next_cursor 有值時每多一頁再加一次。已快取的國家在其有效期結束前不產生請求。

發布於 。

後續步驟