用 StringBuilder
拼接标准 iCalendar 格式最直接
iCalendar(.ics)本质是纯文本,RFC 5545 规范定义了严格的字段顺序、换行和转义规则。C# 中不推荐用
XmlSerializer或 JSON 库生成,也不必引入重型库(如 Ical.Net)——除非你需处理重复事件、时区转换或日历订阅等复杂逻辑。对单个会议、提醒类事件,
StringBuilder手动拼接更可控、无依赖、不易出编码问题。
关键点:
DTSTART和
DTEND必须用 UTC 时间(末尾加
Z)或带偏移的本地时间(如
+0800),不能只写“20240520T140000”而不声明时区 每行不能超过 75 字节,超长需软换行(行末加
\r\n,注意空格)——但现代日历客户端(Outlook、Apple Calendar)大多容忍,可暂不处理
UID必须全局唯一,建议用
Guid.NewGuid().ToString()生成,避免重复导入时被忽略 必须以
BEGIN:VCALENDAR开头、
END:VCALENDAR结尾,中间嵌套
BEGIN:VEVENT/
END:VEVENT
System.Text.Encodings.Web
处理中文标题和描述的 URL 编码陷阱
如果事件标题含中文(如“项目评审会”),直接写入 .ics 文件会导致 Outlook 打开乱码或解析失败。iCalendar 要求非 ASCII 字符必须用
ENCODING=BASE64+
CHARSET=UTF-8声明,不能靠文件保存编码“蒙混过关”。
正确做法是用
System.Text.Encodings.Web的
WebEncoders.Base64Encode编码字节:
string summary = "项目评审会"; byte[] utf8Bytes = Encoding.UTF8.GetBytes(summary); string encoded = WebEncoders.Base64Encode(utf8Bytes); // 写入 .ics 时: // SUMMARY;ENCODING=BASE64;CHARSET=UTF-8:UEBqZWN057yW56iL5L+h5oGv
常见错误:
用Convert.ToBase64String(Encoding.UTF8.GetBytes(...))—— 结果一样,但
WebEncoders是 .NET Core 3.0+ 官方推荐,语义更明确 漏写
CHARSET=UTF-8,部分安卓日历会默认当 ISO-8859-1 解码 对
DESCRIPTION或
LOCATION忘记同样处理
用 File.WriteAllText
保存时必须指定 Encoding.UTF8
.ics 文件不是“随便保存就能用”的文本。如果调用
File.WriteAllText(path, content)不传编码参数,.NET 默认用系统 ANSI(Windows 上常为 GBK),中文字段立刻变乱码,且 Apple Calendar 会直接拒绝导入。
务必显式指定 UTF-8:
File.WriteAllText(filePath, icsContent, Encoding.UTF8);
验证方法:用 VS Code 或 Notepad++ 打开生成的 .ics 文件,右下角确认编码显示为 “UTF-8”,而非 “UTF-8 with BOM” 或 “GBK”。iCalendar 规范禁止 BOM,BOM 会导致某些客户端解析失败。
其他注意事项:
路径中不要含中文或空格(尤其部署到 Linux 服务器时),用Path.GetInvalidFileNameChars()过滤 文件扩展名必须是
.ics,不能是
.txt或
.ical,否则 macOS 双击不会关联日历应用 HTTP 下载时,响应头应设
Content-Type: text/calendar; charset=utf-8
Outlook 和 iOS 对 DTSTAMP
和 SEQUENCE
的宽松与严格
简单事件可省略
SEQUENCE,但
DTSTAMP(时间戳)必须存在,且应设为当前 UTC 时间。Outlook 允许缺省,iOS 则可能拒绝导入无
DTSTAMP的文件。
若后续需更新同一事件(比如改时间),必须同时更新:
DTSTAMP:设为更新时刻的 UTC 时间
SEQUENCE:从 0 开始递增(每次修改 +1)
UID:保持不变
否则 iOS 会当作新事件重复添加,Outlook 可能无法识别更新意图。测试时可用同一
UID连续生成两个版本,导入后观察是否覆盖而非新增。
容易被忽略的是:所有时间字段(
DTSTART、
DTEND、
DTSTAMP)格式必须统一——要么全用 UTC(结尾
Z),要么全用本地时间+偏移(如
20240520T140000+0800)。混用会导致 iOS 解析出错,且错误不报具体提示,只静默失败。
