日历方案

xuzikuan12 | Nov 14, 2026 required to read

1794622375352

从「找一个免费、网页 + 安卓 / 鸿蒙都能用、还得有周视图和三日视图的日历」开始,前后试了十几款软件、自建服务、协议网关和手机客户端,最后定下来的其实不是某一个 App,而是一套混合架构:主力继续用新版 Outlook,日程经 CalDAVconnect 实时同步到自建的 Radicale,Web 端用 Calino、手机端用 KashCal 直连 Radicale 作为备用入口。

这套结构最大的价值不是「免费」,而是容灾:微软服务抽风的时候,日程不丢,而且还能继续改。下面把最终方案和一路上的取舍整理成一篇可复现的笔记。

一、需求与硬约束

  • 免费,不接受订阅制;周视图 / 三日视图是刚需,不能藏在高级会员里;
  • 网页端 + 手机端(安卓 / 鸿蒙)都要能看、能写;
  • 国内网络可用,界面要能方便地编辑、加标签 / 分类、拖拽改时间,类似 Outlook 的体验;
  • 数据要有一份握在自己手里的副本,不能全托给某一家云服务;
  • 主力工具挂掉时,要有可以立即切换的备用方案。

二、最终架构

新版 Outlook ──(CalDAVconnect 双向桥接)── Radicale(飞牛 NAS)
                                          ├── Calino(Web / Docker)
                                          └── KashCal(Android,内置 CalDAV)
层选型职责
主力界面新版 Outlook日常新建 / 编辑日程,网页与手机端的界面、推送体验最好
协议桥接CalDAVconnect免费套餐,OAuth 授权后把 Outlook / Microsoft 365 与自建 CalDAV 双向同步
数据底座Radicale(飞牛 NAS 上 Docker 部署)存放全部日程,自有副本,随时可换回纯本地方案
Web 端Calino(Docker 自部署)周 / 三日视图、拖拽编辑;Outlook 不可用时的备用主力
手机端KashCal(Android,Apache-2.0)内置 CalDAV 直连 Radicale,真三日 / 周视图,免费无内购
外网访问Cloudflare Tunnel(可换 Tailscale / IPv6 / 国内 FRP)让手机在外网也能连到 NAS 上的 Radicale

数据怎么流动

  1. 正常情况:在 Outlook 里改动 → 微软通过 Webhook 通知 CalDAVconnect → 1–2 秒内写进 Radicale;Calino、KashCal 从 Radicale 自动拉取。反过来,在 Calino / KashCal 里改的日程也会被桥接推回 Outlook。当然,正常情况下我在手机上用的是 Outlook 的手机版。
  2. 同步基于 ETag 增量比对,只传变化过的那几条日程,不会整本日历重写。
  3. Renew webhook 是个很容易误解的按钮:它不是给聊天机器人发消息,而是向微软续订「日程有变就通知我」的回调订阅。订阅有有效期,一般会自动续订;只有发现 Outlook 的改动迟迟没同步过来、或后台提示订阅即将过期时,才需要手动点一下。真想让机器人播报日程,用 Power Automate 监听 Outlook 事件更省事。

三、为什么最后是这套:被否掉的路

候选放弃原因
滴答清单免费版只有月视图,日 / 三日 / 周视图要订阅
Google 日历、Notion Calendar国内不可用,或移动端没有完整周视图
微约日历、Teamup、Any.do太小众,或各平台的视图有阉割
飞书日历办公套件太笨重;鸿蒙版后台通知有 bug
华为 / 鸿蒙原生日历没有 Windows 端;系统隐私策略不允许向第三方 CalDAV 账户写入新日程
SyncCalendar(同步日历)能写 CalDAV,但所谓「7 天」只是顶部导航栏,下面一次只显示一天
52Time / AiCal / 优效日历视图合适,但收费模式不明或存在订阅
QQ DAV / 飞书 CalDAV / 钉钉 CalDAV标准支持情况不确定
Outlook CalDav Synchronizer没有尝试,AI说:只支持经典桌面版 Outlook;新版 Outlook 本质是 Web 应用,不支持 COM 插件
DavMail没有尝试,AI说:能把 Graph 翻译成 CalDAV,但官方自述同步成功率不稳定,有数据丢失风险
outlook-caldav-sync / 导出 ICS没有尝试,AI说:Power Automate + JSON 脚本链路长且非实时;ICS 只是单向静态快照

关键结论是:新版 Outlook 原生不支持 CalDAV,微软官方也确认插件路线走不通,所以只剩「云端桥接」这条省事的路。而 Radicale 作为底座,保证了这个第三方桥接随时可以被换掉。

四、部署与配置要点

Radicale:飞牛 NAS 上的 Docker 部署

Radicale 只是个单进程的 CalDAV 服务器:镜像约 25 MB、空闲内存约 20 MB,日程以纯文本文件存放,备份一个目录就够了。在飞牛的「Docker → Compose」里新建项目,目录按官方约定准备成这样:

radicale/
├── compose.yaml
├── config/
│   ├── config      # 主配置文件
│   ├── users       # htpasswd 账号文件
│   └── rights      # 可选:自定义权限规则
└── data/           # 日历数据,也是备份目标

compose.yaml(官方镜像使用 ./config 和 ./data 两个目录):

services:
  radicale:
    image: radicale/radicale:stable
    container_name: radicale
    restart: unless-stopped
    ports:
      - "5232:5232"
    volumes:
      - ./config:/config:ro
      - ./data:/data
    environment:
      - RADICALE_CONFIG=/config/config
  • 端口:容器内部固定监听 5232,只改冒号左边。飞牛后台占用 5666 / 5667,另有 8000 / 8001、80 / 443、3000 / 8080 / 9000 等常用端口;5232 被占用就改成 "15232:5232",选之前用 ss -tlnp | grep 5232 确认空闲。
  • 镜像标签:官方推荐 stable,latest 多数时候指向 nightly 构建,长期自用建议固定到 stable 或具体版本。

config/config 的最小可用配置。注意两个默认值必须显式改掉:3.5 以后默认 denyall(拒绝一切登录),且默认只绑定 localhost(容器外访问不到):

[server]
hosts = 0.0.0.0:5232

[auth]
type = htpasswd
htpasswd_filename = /config/users
htpasswd_encryption = autodetect

[storage]
filesystem_folder = /data/collections

[rights]
type = owner_only

在 config/ 目录下生成账号文件(-5 是 SHA-512;老版本 htpasswd 可换 -B 用 bcrypt)。飞牛上没有 htpasswd 命令时,在任意 Linux / WSL 上生成后把 users 文件放进 config/ 即可:

htpasswd -5 -c users xuzikuan12

启动并验证:

docker compose up -d
docker compose logs -f radicale
  1. 浏览器打开 http://飞牛IP:5232,用刚建的账号登录,在自带 Web 界面里新建一个日历集合(形如 /xuzikuan12/calendar)。
  2. 各客户端统一填根地址 http://飞牛IP:5232(必要时补 /用户名/):CalDAVconnect、Calino、KashCal 都指向同一个地址,配合 /.well-known/caldav 自动发现日历集合。
  3. 数据全在 data/ 目录,纯文本,直接打包就是完整备份;Calino 也可以写进同一个 compose 项目一起管理。

飞牛上两个常见坑:宿主机目录属主与容器内运行用户不一致时,日志会报 Permission denied,给 data/ 授权或在 compose 里显式加 user: "1000:1000"(按 NAS 实际 UID 调整)即可;另外 config/config 每次改动都要 docker compose restart radicale 才生效。

Radicale:CORS 与可写权限

Calino 是纯浏览器前端,跨域访问 Radicale 必须先开 CORS。把下面这段追加到 config/config:

[headers]
# 公网环境建议把 * 换成 Calino 的确切地址
Access-Control-Allow-Origin = *
Access-Control-Allow-Methods = GET, POST, OPTIONS, PROPFIND, PROPPATCH, REPORT, PUT, MOVE, DELETE, LOCK, UNLOCK, MKCALENDAR
Access-Control-Allow-Headers = User-Agent, Authorization, Content-type, Depth, If-match, If-None-Match, Lock-Token, Timeout, Destination, Overwrite, X-client, X-Requested-With
Access-Control-Expose-Headers = Etag
  • 有反向代理时,更推荐在代理层统一加 CORS,而不是写进 Radicale;也可以用 Calino 自带的 calino-proxy 绕过跨域(见下一节)。
  • 可写权限:默认 owner_only 已经允许登录用户读写自己 /用户名/ 下的集合。若手机端仍把日历识别成只读、新建日程时选不到它,就换成 from_file 显式授权——在 config/rights 里写:
[root]
user: .+
collection:
permissions: R

[principal]
user: .+
collection: {user}
permissions: RW

[calendars]
user: .+
collection: {user}/[^/]+
permissions: rw

同时在 config/config 中把 [rights] 改为:

[rights]
type = from_file
file = /config/rights
  • 让反代放行 /.well-known/caldav、/.well-known/carddav,并补齐 X-Forwarded-Proto、X-Forwarded-Host;客户端就能只填一个根地址自动发现日历集合。

Calino:飞牛 NAS 上的 Docker 部署

services:
  calino:
    image: ghcr.io/ivan-malinovski/calino:latest
    container_name: calino
    restart: unless-stopped
    ports:
      - "18080:8080"
  • 容器内部固定监听 8080,只改冒号左边的宿主机端口;避开飞牛后台的 5666 / 5667,以及 8000 / 8001、80 / 443、3000 / 8080 / 9000 等常用端口。选端口前用 ss -tlnp | grep 18080 确认空闲。
  • 不想在 Radicale 侧开 CORS,也可以启用官方 calino-proxy 镜像(docker compose --profile proxy up -d),在 Calino 连接设置里把 Proxy URL 填为 http://NAS:8081。
  • 把自己部署的实例安装成 PWA:界面资源缓存在本地,秒开且可离线查看;装 calino.io 的 PWA 没用,慢的是海外服务器。
  • 桌面通知必须在 HTTPS 安全上下文下才能申请权限。若「允许」一点击就自动变回「阻止」,重置站点权限、注销 Service Worker,并确认是通过受信任的 HTTPS 访问。
  • 简体中文在 2026-11-06 才合入源码(feat(i18n): add Simplified Chinese (zh) locale),Docker 版没有中文多半是镜像滞后:先 docker compose pull calino && docker compose up -d calino;仍不行就检查镜像内是否真的有 zh 语言文件,必要时挂载补上或提 Issue。

KashCal 与 CalDAVconnect

  • KashCal:F-Droid / IzzyOnDroid 获取,Apache-2.0、无内购;内置 CalDAV 客户端直连 Radicale,不用 DAVx⁵ 中转,三日视图是三列并排的真视图,支持拖拽改时间。
  • CalDAVconnect:在 caldavconnect.de/dashboard 填入 Radicale 地址并 OAuth 授权微软账户即可,本地不用装任何东西。授权走 OAuth,不需要交出账号密码,且随时能在微软账户安全中心撤销;注册用独立强密码,授权时看一眼它申请的权限是否只涉及日历读写。

外网访问(可替换的一层)

Cloudflare 代理回家里 NAS 偏慢,慢的是连接层而不是 Radicale,所以这一层随时可换:

  • Tailscale:用 MagicDNS 主机名 radicale.<tailnet>.ts.net 作为所有客户端的统一地址;手机上设「离开家里 Wi-Fi 才自动连接」或应用级分流(只让日历 App / DAVx⁵ 走 Tailscale),不必常驻全局 VPN,WireGuard 待机耗电基本可忽略。
  • IPv6 + DDNS:宽带和手机都有公网 IPv6 时最无感,客户端直接填域名,完全不需要 VPN。
  • 国内节点 FRP / 轻量内网穿透:延迟比 CF Tunnel 好,但要留意服务商稳定性和免费额度。

五、容灾:Outlook 崩了怎么办

  1. 正常:Outlook 作主力,改动同步到 Radicale,Calino / KashCal 自动拉取。
  2. Outlook 崩溃 / 断网:直接打开 Calino 或 KashCal。它们直连 NAS,只要 NAS 在运转就照常看、照常改;CalDAVconnect 此时向微软推送会报错或挂起,但期间所有变更都落在 Radicale 里。
  3. Outlook 恢复:桥接自动把 Radicale 里的增量变更反推回 Outlook,刷新一下就能看到崩溃期间加的日程,再切回主力。

⚠️ 唯一要留意的是冲突:如果崩溃前在 Outlook 改了某条日程但没同步出去,随后又在 Calino 改了同一条,恢复后多数同步工具按「最后写入者胜出(Last Write Wins)」处理,前一次修改可能被覆盖。遇到大面积故障,恢复后扫一眼当天日程有无出入即可。

六、代价与边界

  • 这是混合架构而非纯自建:同步过程会经过 CalDAVconnect 的服务器(德国服务、受 GDPR 约束、OAuth 可撤销),极其机密的日程不要放进去。
  • 但完整副本一直在自己的 Radicale / 飞牛 NAS 上:哪天它倒闭、不免费了,关掉换回纯本地方案即可,数据毫发无损——进可攻,退可守。
  • 其余都是小坑:浏览器通知要 HTTPS、Docker 镜像中文包滞后、鸿蒙系统日历不让第三方账户写入。用 Calino / KashCal 作为「写入端」就绕开了最后一条。

小结

Outlook 负责颜值和生态,CalDAVconnect 负责协议翻译,Radicale 负责兜底和绝对控制权,Calino / KashCal 负责灾备。

折腾一圈的体会是:不必执着于「万物自建」。把数据底座留在自己手里,把最难的「最后一公里」交给现成服务,反而是在便利、稳定、自主、免费之间最舒服的平衡点。