2026-03-17 00:13:41 +08:00
|
|
|
|
# Calendar Timezone Policy Protocol
|
|
|
|
|
|
|
|
|
|
|
|
## Version
|
|
|
|
|
|
|
|
|
|
|
|
- Current: `1.0`
|
|
|
|
|
|
- Status: Active
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Goal
|
|
|
|
|
|
|
|
|
|
|
|
统一日历事件在 App、Agent、工具、数据库之间的时间语义,消除时区不一致导致的显示和落库偏差。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Canonical Rules
|
|
|
|
|
|
|
|
|
|
|
|
1. 数据库存储基准为 UTC。
|
|
|
|
|
|
2. 事件语义时区使用 IANA 时区字符串(`event_timezone` / `timezone`)。
|
|
|
|
|
|
3. 禁止无时区时间(naive datetime)进入日历写入链路。
|
|
|
|
|
|
4. 日历写入必须显式确定事件时区,不允许工具层硬编码默认时区。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Timezone Resolution Priority
|
|
|
|
|
|
|
|
|
|
|
|
运行时事件时区解析顺序固定如下:
|
|
|
|
|
|
|
|
|
|
|
|
1. `event_timezone`(工具调用显式传参)
|
|
|
|
|
|
2. `forwardedProps.client_time.device_timezone`
|
|
|
|
|
|
3. `users.profile.settings.timezone`
|
|
|
|
|
|
4. `UTC`
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Write Contract
|
|
|
|
|
|
|
|
|
|
|
|
### Required fields
|
|
|
|
|
|
|
|
|
|
|
|
- `start_at`: RFC3339 且必须包含时区偏移
|
|
|
|
|
|
- `timezone`: IANA 时区
|
|
|
|
|
|
|
|
|
|
|
|
### Optional fields
|
|
|
|
|
|
|
|
|
|
|
|
- `end_at`: RFC3339 且必须包含时区偏移(若提供)
|
|
|
|
|
|
|
|
|
|
|
|
### Validation
|
|
|
|
|
|
|
|
|
|
|
|
- `timezone` 非法 -> 拒绝请求
|
|
|
|
|
|
- `start_at`/`end_at` 无时区 -> 拒绝请求
|
|
|
|
|
|
- `end_at < start_at` -> 拒绝请求
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Read Contract
|
|
|
|
|
|
|
|
|
|
|
|
读接口最小语义要求:
|
|
|
|
|
|
|
|
|
|
|
|
- 返回 UTC 时间字段(`start_at`, `end_at`)
|
|
|
|
|
|
- 返回事件时区字段(`timezone`)
|
|
|
|
|
|
- 前端展示必须以 `timezone` 作为事件本地时间转换基准,不允许直接按设备本地时区隐式渲染
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Error Codes
|
|
|
|
|
|
|
2026-04-23 16:04:04 +08:00
|
|
|
|
当前实现(`backend/src/v1/schedule_items/service.py`)实际返回以下稳定错误码:
|
2026-03-17 00:13:41 +08:00
|
|
|
|
|
2026-04-23 16:04:04 +08:00
|
|
|
|
- `SCHEDULE_ITEM_DATETIME_TIMEZONE_REQUIRED`:输入 datetime 缺少时区偏移
|
|
|
|
|
|
- `SCHEDULE_ITEM_START_AT_TIMEZONE_REQUIRED`:`end_at` 校验阶段缺少可用的 `start_at` 时区信息
|
|
|
|
|
|
- `SCHEDULE_ITEM_INVALID_TIME_RANGE`:`end_at <= start_at`
|
|
|
|
|
|
|
|
|
|
|
|
说明:`timezone` 非法、`start_at/end_at` 无时区等基础结构错误在 schema 校验阶段由 FastAPI/Pydantic 返回 `422`,业务错误码表(`docs/protocols/common/http-error-codes.md`)目前仅登记服务层稳定码。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Compatibility Strategy
|
|
|
|
|
|
|
|
|
|
|
|
- 策略:`backward-compatible`
|
|
|
|
|
|
- 本次为文档对齐更新,不改变线上接口行为;客户端若仅按 HTTP 状态码处理不受影响。
|
2026-03-17 00:13:41 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## DST Rule
|
|
|
|
|
|
|
|
|
|
|
|
- 夏令时切换期间,时间解释以 IANA 时区数据库为准。
|
|
|
|
|
|
- 对于歧义本地时间(例如回拨重复小时),由后端统一按标准库解析策略处理并返回确定 UTC 结果。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Non-Goals
|
|
|
|
|
|
|
|
|
|
|
|
- 本协议不引入新的数据库时间列。
|
|
|
|
|
|
- 本协议不改变 `schedule_items` 现有 `TIMESTAMPTZ + timezone` 存储结构。
|