家里三个人的日程,硬是把我逼成了日历系统设计师。
起因其实很普通:孩子上小学之后,课程表开始变得复杂——有常规课、单周课、双周课、调课、临时加课;我和我太太的工作日历、健身安排、家长会、兴趣班又掺杂在一起。手机上装了四五个App,各自为政:学校的课程表App不能导出、孩子的兴趣班排课在另一个小程序里、家人的共享日历在系统日历里、有些提醒又只在微信群里。每次月底做下个月安排,我都要对着四五个界面临时拼凑,漏掉一次家长会或者忘了一次调课,就得花更大的代价去弥补。
后来我认真想了想,整个家庭日程的核心矛盾其实是同一个:大量事件带有明确的时间、地点、重复规律和提醒需求,但这些事件散落在不同App里,彼此之间没有任何标准可以互通。我需要的并不是另一个花里胡哨的课程表,而是一个能把所有日程统一收拢、能被全家人各种设备顺手消费的底层格式。于是我把目光放到了 iCalendar(RFC 5545)上,自己动手写了一套以课程表为切入点、以家庭日程管理为目标的系统。
这篇博文就把这段实践完整拆开:先讲我为什么选择 iCalendar 而不是自造轮子,再讲课程表数据模型怎么映射到标准事件上,然后是实际生成、订阅、同步过程中踩过的坑,以及最后沉淀下来的家庭日程管理办法。如果你想给孩子做课表、给家人做共享日历,或者单纯想弄明白日历系统背后的事件模型,这篇文章应该能给你一个可以直接上手的完整参考。
1. 为什么我不继续用现成的课程表App,而要重写一套
1.1 市面课程表产品的封闭性困局
先说我试过的那些课程表App。单论界面和交互,做得好的其实不少,能拍照识别、能手动录入、能显示单双周、还能把一节课做成卡片样式。可一旦进入"家庭协作"这个真实使用场景,问题就冒出来了:
第一,数据被锁死在App内部。绝大多数课程表App没有开放数据导出,更不用说提供标准格式的订阅入口。你录入一整学期的课表,它只能在你自己的手机屏幕里以一套私有UI呈现。家长要看,要么装同一个App,要么你截图发群。
第二,提醒机制与系统日历脱节。部分App确实有课前提醒,但这种提醒是App内部的推送,手机通知一多,很容易被折叠或者忽略。而且它没法跟系统日历里已有的其他安排放在同一个时间轴上统筹,冲突根本发现不了。
第三,更新链路太长。学校一旦发布调课通知,你得手工去App里改那一天的课程,改完之后全家人手机上看到的还是旧数据。不是说不可以,而是每学期几十次调课,每次都要重复“打开App-找到那天-修改-通知家人”这种操作,人力成本实在吃不消。
第四,没有“家庭日程”的视角。课程表对一个家庭来说不是孤立的,它和接送安排、兴趣班、家长会、加班出差、健身计划必须放在同一个调度视图里看,才有可能真正避冲突。
所以问题从来不是“录课表的工具不够好用”,而是“课表数据无法进入家庭通用的日程基础设施”。这不是换个App能解决的,这是一个数据标准和生态协作的问题。
1.2 课程表的本质:一条带重复规则的事件流
想明白这一点之后,我对课程表的理解发生了根本转变。所谓课程表,本质上就是一组带时间、地点、重复规律和提醒需求的结构化事件。一节课 = 一个事件(Event),一门课程每周三节 = 同一个事件按周重复三次,单双周差异 = 同一门课拆成两条不同规则的重复事件,调课 = 事件的时间地点变更。这个抽象一旦建立,课程表就不再是学校里专属的私有格式,它就是通用日历系统里一种再普通不过的事件组合。
既然本质是事件流,那么就应该用“事件流的标准语言”来表达。我最初也考虑过自己定义一套JSON结构,毕竟Web工程师对JSON最熟悉。但深入想想就放弃了:
JSON结构只能服务我自己,任何日历客户端都不可能原生理解我自定义的字段。而iCalendar是绝大多数日历客户端的通用语言,iOS日历、Google Calendar、Outlook、Thunderbird、微信日历订阅,全都原生支持。用iCalendar意味着我生成的课程表文件可以直接被全家人已有的设备消费,不需要安装任何额外软件。
1.3 选型结论:iCalendar 就是日程界的“通用插座”
如果拿日常事物类比,iCalendar 大概是日程管理界的“通用插座”。它规定了事件长什么样(VEVENT),重复规则怎么说(RRULE),提醒怎么定义(VALARM),时区怎么表达(TZID),日历之间怎么同步(CalDAV / 订阅URL)。有了这层标准,我的课程表系统只需要负责把“课程表数据”翻译成“标准事件流”,剩下的分发、展示、提醒就全部交给成熟的日历客户端。
这套思路下,家庭日程管理整个链条变得非常简单:课程表系统生成一个ICS文件,部署到一台家里的小服务器(或者任何能放置静态文件的设备),生成一个订阅URL,然后全家人的手机各自订阅这个URL。学校有调课,我改配置、重新生成、发布,全家人手机上的日历10分钟之内自动更新。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. iCalendar 标准速览:理解事件、重复与提醒三件套
在动手写代码之前,我花了一点时间把 iCalendar 标准的核心概念过了一遍。网上关于 RFC 5545 的资料很多,但直接读RFC原文太枯燥,我梳理出实际写课程表时最常用的几个组件,对初学者来说,掌握这几个就足够起步了。
2.1 VEVENT:一切日程的最小单元
iCalendar 文件(通常叫ICS文件)最核心的组件是 VEVENT,它描述一个具体的事件。一个最简单的 VEVENT 长这样:
code复制BEGIN:VEVENT
UID:20240902-math-01@family.local
DTSTAMP:20240820T120000Z
DTSTART:20240902T080000
DTEND:20240902T084500
SUMMARY:数学课
LOCATION:三号楼202教室
END:VEVENT
几个关键字段的含义要搞清楚:
- UID:事件的全局唯一标识。这是整个标准里最重要的字段之一,日历客户端靠它识别“这是同一个事件”,更新和同步都依赖这个ID,不能随便改。
- DTSTART / DTEND:开始时间和结束时间。注意ICS里的时间格式有多种,后面会专门讲时区的坑。
- SUMM
