在产品中提供经济日历的团队需要按用户筛选国家、按每位查看者的时区显示发布时间,以及展示被调整的发布。Narwhal 返回的发布带有 UTC 时间、发布机构的时区、所属期间、前值和修订计数,筛选、时钟和缓存则由应用添加。
应用向日历 API 请求什么
在自己的应用里提供经济日历的产品团队,有三件事数据源无法代劳:让每个用户选择国家,把所有时间显示为查看者的时区,以及在发布被调整时保持屏幕内容准确。API 提供发布数据,应用负责筛选、时钟和缓存。GET /v1/economics/calendar 接受 countries(每个国家重复一次,使用 USA 或 CAN 这样的三字母代码)、start_date、end_date、limit 和 cursor。这个请求获取两个国家两天的数据。
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 始终存在,并且可能是空列表。
按国家缓存,并按查看者时区呈现
用户选择的国家组合各不相同,所以按国家分别缓存,再按用户合并列表。两个都关注美国的用户就共用同一份缓存列表。
- 一次获取一个国家
country_releases请求一个国家,并跟随next_cursor直到它为null,因为一页最多包含 100 条发布。 - 让数据保持短时效
TTL是缓存的国家数据保持新鲜的秒数。请根据用户需要多快看到被调整的时间来选择。 - 为用户合并并转换
user_calendar汇集用户的国家,用用户的时区转换每个at,并按当地时间排序。ZoneInfo会应用查看者所在地的时钟变化,所以应用无需存储偏移量。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 日期。
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 与新的 revision | release_id, revision |
| 主题或系列标签 | 在应用中筛选,因为请求没有主题参数 | family, topic |
| 速率限制与重试 | 使用缓存,并在收到 429 响应时退避,参见缓存响应并处理 429 错误 | 响应头 |
用户可以按主题或系列筛选吗?
请求接受国家、日期、limit 和 cursor,没有主题筛选。每条发布都有 family,大多数有 topic,所以请在你自己的代码中筛选。请使用 x.get("topic"),因为有些发布没有主题,直接取值会引发键错误。
日期窗口依据 UTC 还是发布机构的本地日期?
依据发布机构的本地日期。日本在 UTC 23:30 或 23:50 的发布,在东京已是次日早晨,所以落在后一天的窗口中。当应用按查看者的时区构建一天时,请在两侧各多请求一天,并按 at 筛选。
一个屏幕需要多少次请求?
缓存中每个国家一次,next_cursor 有值时每多一页再加一次。已缓存的国家在其有效期结束前不产生请求。
发布于。