Calendar client library, written in Rust.
This library is composed of 2 feature-gated layers:
- Low-level I/O-free coroutines: these
no_std-compatible state machines wrap the underlying io-vdir and io-webdav coroutines and surface a shared least-common-denominator type on completion - Mid-level std client: a standard, blocking unified client that dispatches the shared API to the single active backend
- Shared LCD types:
CalendarandCalendarItemthat fit both Vdir and CalDAV. - I/O-free coroutines:
no_stdstate machines per (backend, operation), wrapping the underlying io-vdir / io-webdav coroutine and producing a shared type on completion. - Unified std client (
clientfeature): blocking dispatcher; since a calendar account speaks one protocol at a time,CalendarClientStdis an enum over the single active backend (Vdir or Webdav) rather than a multi-slot bag. - TLS for the CalDAV backend (gated by the same
rustls-ring/rustls-aws/native-tlsfeatures forwarded to io-webdav). - Optional iCalendar parsing (
parserfeature, calcard-backed) and serde round-trip on every shared type (serdefeature).
Tip
I/O Calendar is written in Rust and uses cargo features to gate backend support. The default feature set is declared in Cargo.toml or on docs.rs.
| Operation | Vdir | Webdav |
|---|---|---|
list_calendars |
yes | yes |
create_calendar |
yes | yes |
update_calendar |
yes | yes |
delete_calendar |
yes | yes |
list_items |
yes | yes |
get_item |
yes | yes |
create_item |
yes | yes |
update_item |
yes | yes |
delete_item |
yes | yes |
I/O Calendar can be consumed two ways, depending on how much of the I/O stack you want to own. Each mode is gated by cargo features.
Whichever mode you pick, every shared-API coroutine implements the backend trait of the protocol it targets (VdirCoroutine for the local backend, WebdavCoroutine for CalDAV). The resume(...) method returns the matching <Backend>CoroutineState<Yield, Return> with two variants:
Yielded(Y): intermediate.Yis the backend's standard yield (WantsDirRead/WantsFileCreate/WantsRenameetc. for the Vdir filesystem backend,WantsRead/WantsWritefor the CalDAV network backend). The driver services the request and feeds back the matching reply on the nextresume.Complete(R): terminal. By conventionR = Result<Output, Error>carrying the operation's final value typed against the sharedCalendar/CalendarItem.
The std client owns the resume loop for you; the I/O-free mode hands it back so you can drive the same coroutine under any blocking, async, or fuzz harness.
No client feature required: every wrapper lives under <domain>::<protocol>::<op> (for example calendar::vdir::list::VdirCalendarList, item::webdav::get::WebdavCalendarItemGet) and is built straight from the shared inputs. You own the loop and the syscalls; the library only produces operations and consumes their results.
Create a Vdir calendar against a blocking caller (the same shape works under async or in-memory replay):
use std::fs;
use io_calendar::calendar::vdir::create::VdirCalendarCreate;
use io_vdir::{coroutine::*, path::VdirPath};
let root = VdirPath::new("/path/to/vdir");
let mut coroutine = VdirCalendarCreate::new(&root, "personal", "Personal", None, None).unwrap();
let mut arg: Option<VdirReply> = None;
loop {
match coroutine.resume(arg.take()) {
VdirCoroutineState::Complete(Ok(())) => break,
VdirCoroutineState::Complete(Err(err)) => panic!("{err}"),
VdirCoroutineState::Yielded(VdirYield::WantsDirCreate(paths)) => {
for path in paths {
fs::create_dir_all(path.as_str()).unwrap();
}
arg = Some(VdirReply::DirCreate);
}
VdirCoroutineState::Yielded(VdirYield::WantsFileCreate(files)) => {
for (path, bytes) in files {
fs::write(path.as_str(), &bytes).unwrap();
}
arg = Some(VdirReply::FileCreate);
}
VdirCoroutineState::Yielded(other) => unreachable!("unexpected {other:?}"),
}
}
println!("created calendar personal");The CalDAV backend follows the same pattern but yields WantsRead / WantsWrite(Vec<u8>) instead; see io-webdav for the TCP / TLS / discovery setup that connects the stream before the wrapper coroutine runs.
Enable the client feature and at least one backend. A calendar account speaks one protocol at a time, so CalendarClientStd is an enum over the single active backend; build one from a per-backend client (VdirClient, WebdavClientStd) via From, then call the shared API.
[dependencies]
io-calendar = { version = "0.0.3", features = ["client", "vdir"] }use io_calendar::{client::CalendarClientStd, vdir::client::VdirClient};
let mut client = CalendarClientStd::from(VdirClient::new("/path/to/vdir"));
for calendar in client.list_calendars().unwrap() {
println!("{}: {}", calendar.id, calendar.name);
}The vdir backend runs against the local filesystem; the webdav backend drives its coroutines against the connected stream and reuses the inner client's CalDAV discovery cache.
Have a look at real-world projects built on top of this library:
- Calendula: CLI to manage calendars
This project is developed with AI assistance. This section documents how, so users and downstream packagers can make informed decisions.
-
Tools: Claude Code (Anthropic), Opus 4.8, invoked locally with a persistent project-scoped memory and a small set of repo-specific rules.
-
Used for: Refactors, mechanical multi-file edits, boilerplate (feature gates, error enums, derive macros, trait impls), test scaffolding, doc polish, exploratory design conversations.
-
Not used for: Engineering, critical code, git manipulation (commit, merge, rebase…), real-world tests.
-
Verification: Every AI-assisted change is read, compiled, tested, and formatted before commit (
nix develop --command cargo check / cargo test / cargo fmt). Behavioural correctness is verified against the relevant RFC or upstream spec, not assumed from the model output. Tests are never adjusted to fit AI-generated code; the code is adjusted to fit correct behaviour. -
Limitations: AI models occasionally produce code that compiles and passes tests but is subtly wrong: off-by-one errors, missed edge cases, plausible but nonexistent APIs, stale RFC references. The verification workflow catches most of this; it does not catch all of it. Bug reports are welcome and taken seriously.
-
Last reviewed: 11/06/2026
This project is licensed under either of:
at your option.
- Chat on Matrix
- News on Mastodon or RSS
- Mail at pimalaya.org@posteo.net
Special thanks to the NLnet foundation and the European Commission that have been financially supporting the project for years:
- 2022 → 2023: NGI Assure
- 2023 → 2024: NGI Zero Entrust
- 2024 → 2026: NGI Zero Core
- 2027 in preparation…
If you appreciate the project, feel free to donate using one of the following providers:
