在你自己的应用中显示经济日历

在产品中提供经济日历的团队需要按用户筛选国家、按每位查看者的时区显示发布时间,以及展示被调整的发布。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 有值时每多一页再加一次。已缓存的国家在其有效期结束前不产生请求。

发布于。

后续步骤