一个 ICS 文件是一个用纯文本写出来的日历事件。它以电子邮件附件的形式抵达、坐在每一个“加入日历”按钮后面,而且它正是“发件人用的软件和你完全不同,但那个会议邀请还是进得了你的日历”的原因。
把一个改名成 .txt 打开——内容是读得懂的,而且人们碰到的几乎每一个日历问题,在里面都看得见。
BEGIN:VCALENDAR
METHOD:REQUEST
BEGIN:VEVENT
UID:[email protected]
DTSTART;TZID=Asia/Taipei:20261110T140000
DTEND;TZID=Asia/Taipei:20261110T150000
SUMMARY:项目检讨
LOCATION:3 号会议室
END:VEVENT
END:VCALENDAR
一个容器、一个事件、一个开始、一个结束、一个标题。上面有四行做的事比它们看起来多,而每一行都解释了一个熟悉的失败。
每个事件带着一个 UID——一个在那个事件的一生中都不变的唯一标识符。
当一个会议移动,主办人的软件会寄出一个新的 ICS 文件,带着相同的 UID 和一个更后面的序号。你的日历认出它是同一个事件,于是更新既有的那一笔,而不是再加一笔。
这正是重复事件出现的原因。如果一个工具每次导出都生成一个全新的 UID——有些预约系统和电子报的“加入日历”链接就是这样——那每一次更新都会以一个全新事件的身份抵达,于是你手上有同一个会议的三个副本。那些重复不是你日历的 bug;它被告知它们是不同的事件。
METHOD 那一行告诉你的日历这是哪一种文件:
所以当一个邀请以纯附件的形式抵达、没有任何回复按钮,它通常是一个 PUBLISH 文件,或者是一个在转发过程中失去邀请身份的 REQUEST。接受它会把事件加进你的日历,但完全没告诉主办人任何事——这就是为什么有人会在一个他打算出席的会议上,被标记成“尚未回复”。
ICS 文件里的时间可以用三种不同的方式写,而那个差别极为要紧。
DTSTART;TZID=Asia/Taipei:20261110T140000——台北的两点,对其他所有人都正确换算。这是你要的。DTSTART:20261110T060000Z——结尾那个 Z 代表 UTC,每个日历都会正确换算成当地时间。也没问题。DTSTART:20261110T140000——不管读的人在哪里的两点。对一个个人提醒来说那是对的。对一个跨城市的会议来说那是灾难:每个人都看到自己时区的下午两点,然后没有人碰得到面。当一个会议落在日历上差了好几小时,几乎永远是因为这个。第二种常见变体是从一个自身时区设置就错了的系统导出的事件,那种情况下文件内部是一致的,而且一致地错误。
全天事件有它自己的版本。一个正规的全天事件是写成一个完全不带时间的日期(DTSTART;VALUE=DATE:20261110)。如果改写成一个时间——某个时区的午夜——它对任何位置够东或够西的人来说,就会移到前一天或后一天。这就是为什么一个生日有时候会落在错的日期上。
一个每周会议不是存成五十二个事件。它是一个事件加上一条规则:
RRULE:FREQ=WEEKLY;BYDAY=MO;COUNT=52
日历从那条规则生成那些场次。另外两行处理真实行程一定会产生的例外:EXDATE 删掉其中一次,而一个带着 RECURRENCE-ID 的独立事件覆盖其中一次——那就是“这周的会议改到星期二”而不扰动其余系列的机制。
各家日历程序对这些例外的支持程度不一,这就是为什么一个被修改过的场次在系列跨系统搬移时,有时候会变回去、或者出现两次。
这是关于日历文件最有用的一件事,而这两个选项在你选的当下看起来几乎一模一样。
导入一个 ICS 文件,是把那些事件复制进你的日历一次。它们变成你的、你可以编辑它们,而且它们是冻结的。如果来源改变了,什么都不会发生——你的副本还是原来的样子。
订阅一个 ICS 网址则保留一条链接。你的日历会定期重新抓那个文件,来源的改动会出现。你不能编辑那些事件,因为它们其实不是你的。
几乎每个人都至少中过一次:一份课表、一份赛程表或一份共用的团队行程被导入了,然后那个组织改了好几个日期,而日历安安静静地继续显示旧的。没有任何东西警告你,因为从日历的角度看,什么问题都没发生。
规则很简单。如果来源会持续改变,就订阅。如果是一次性的事件,就导入。订阅链接通常长得像一个以 .ics 结尾的网址,有时候开头是 webcal://,那是同一个东西,只是指名要给日历 App。
一个值得知道的但书:订阅的日历是照那个应用程序自己的排程更新,可能是好几小时甚至一天。今天早上做的改动,可能明天才会出现。