Skip to content
RuaRuan
返回

CalDAV同步过程中如何处理时区差异问题?

CalDAV 本身不在协议层“转换时区”,而是把“时区语义”交给 iCalendar 格式 + IANA 时区数据库来承载,同步过程只搬 TZID 引用 + 本地时间(或 UTC 绝对时间),各端按同一套 IANA 规则独立展开。核心原则是:存储原意,按引用解析,显示时转换,绝不在同步中静默平移墙钟时间。

一、iCalendar 里三种时间写法,决定同步语义

DTSTART/DTEND 的值形态直接决定跨时区行为(RFC 5545):

CalDAV 同步时,服务器通常原样存客户端 PUT 进来的文本,不重写 DTSTART 形态(除非服务端做 TZID 规范化映射)。

二、TZID 怎么对齐:VTIMEZONE 内联 vs RFC 7809 按引用

早期做法(RFC 5545 强制):每个引用了 TZID 的 .ics 文件,必须把对应的 VTIMEZONE 组件内联进同一个 VCALENDAR。这样接收方就算自己 IANA 库旧也能展开。

问题:一个 VTIMEZONE(含历史夏令时规则)往往比事件本身还大,移动端同步浪费带宽。

RFC 7809(CalDAV Time Zones by Reference)的改进:

现实:iOS/macOS、Fastmail、Nextcloud(部分)、Radicale 均支持 7809 的“省略标准时区”模式;老客户端(某些 Android 第三方)仍要求内联 VTIMEZONE,此时服务端会按 CalDAV-Timezones: T 补回。

三、服务端在同步里到底管什么时区事

服务端不负责把事件“转成某时区”,只在两种场景用时区:

  1. 时间窗过滤(calendar-query 的 time-range)
    客户端发 REPORT 查“2026-09 的事件”,服务端要把带 TZID 的本地时间、浮动时间都归到 UTC 再比窗口。
    归法优先级(RFC 4791 §7.3):
    • 请求体里带 <C:timezone>(内联 VTIMEZONE)或 <C:timezone-id>(RFC 7809)→ 用这个
    • 否则用日历集合属性 CALDAV:calendar-timezone / calendar-timezone-id
    • 再没有 → 服务端自选(通常 UTC 或服务器本地)
      所以浮动时间事件在哪个时区被过滤,取决于上述默认值——这也是为什么跨时区共享别用浮动时间。
  2. 忙闲查询(free-busy-query)
    同样依赖 calendar-timezone 把 DATE/DATE-TIME 浮动值展开成 UTC 段。

除此之外,PUT/GET/ETag 同步过程中服务端不改动 DTSTART 的时区表达,保证“原意不动”。

四、多客户端跨时区同步的正确链路

以“北京用户建会议、纽约同事接收”为例:

  1. 北京客户端(IANA Asia/Shanghai)建事件
    DTSTART;TZID=Asia/Shanghai:20260901T090000 + 内联/引用 Asia/Shanghai VTIMEZONE
    PUT 到 CalDAV 服务器,存原文。
  2. 服务器存盘,ETag 变化,CTag/sync-token 推进。不转 UTC、不转纽约时间。
  3. 纽约客户端同步拉到同一份 .ics:
    • 若支持 7809 且服务端没发 VTIMEZONE → 用本地 IANA 库查 Asia/Shanghai 的 2026-09-01 偏移(+08:00),算出 UTC 20260901T010000Z,再按自己 America/New_York(-04:00 DST)显示成 8 月 31 日 21:00。
    • 若本地 IANA 旧(比如没装 2026 夏令时修正)→ 可能差一小时,这就是“时区库过期”bug 根源,需定期更新 tzdata。
  4. 纽约客户端改了 SUMMARY 后 PUT 回去,仍带原 TZID=Asia/Shanghai,北京端再拉到,显示仍是自己时区的 09:00。物理时刻全程没变,只是墙钟随观察者变。

五、递归事件(RRULE)的时区坑

FREQ=WEEKLY;BYDAY=TU 配 TZID=America/New_York 的 09:00 会议:

六、常见时区同步故障与根因

七、工程落地建议(自托管/开发 CalDAV 应用时)



上一篇
详解 CalDAV
下一篇
详解 CardDAV