C#生成iCalendar(.ics)文件 C#如何创建日历事件文件

3次阅读

用 StringBuilder 拼接 iCalendar 最直接:需严格遵循 RFC 5545,手动处理时区(UTC 加 Z 或带偏移)、软换行、UID 唯一性、BASE64 编码中文、UTF-8 无 bom 保存及 DTSTAMP/SEQUENCE 规则。

C#生成iCalendar(.ics)文件 C#如何创建日历事件文件

StringBuilder 拼接标准 iCalendar 格式最直接

iCalendar(.ics)本质是纯文本,RFC 5545 规范定义了严格的字段顺序、换行和转义规则。C# 中不推荐用 XmlSerializerjsON 库生成,也不必引入重型库(如 Ical.Net)——除非你需处理重复事件、时区转换或日历订阅等复杂逻辑。对单个会议、提醒类事件,StringBuilder 手动拼接更可控、无依赖、不易出编码问题。

关键点:

  • DTSTARTDTEND 必须用 UTC 时间(末尾加 Z)或带偏移的本地时间(如 +0800),不能只写“20240520T140000”而不声明时区
  • 每行不能超过 75 字节,超长需软换行(行末加 rn ,注意空格)——但现代日历客户端(outlookapple 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.WebWebEncoders.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 解码
  • DESCRIPTIONlocation 忘记同样处理

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 和 iosDTSTAMPSEQUENCE 的宽松与严格

简单事件可省略 SEQUENCE,但 DTSTAMP(时间戳)必须存在,且应设为当前 UTC 时间。Outlook 允许缺省,iOS 则可能拒绝导入无 DTSTAMP 的文件。

若后续需更新同一事件(比如改时间),必须同时更新:

  • DTSTAMP:设为更新时刻的 UTC 时间
  • SEQUENCE:从 0 开始递增(每次修改 +1)
  • UID:保持不变

否则 iOS 会当作新事件重复添加,Outlook 可能无法识别更新意图。测试时可用同一 UID 连续生成两个版本,导入后观察是否覆盖而非新增。

容易被忽略的是:所有时间字段(DTSTARTDTENDDTSTAMP)格式必须统一——要么全用 UTC(结尾 Z),要么全用本地时间+偏移(如 20240520T140000+0800)。混用会导致 iOS 解析出错,且错误不报具体提示,只静默失败。

text=ZqhQzanResources