<p align="center">
<img src="https://raw.githubusercontent.com/AdamPerlinski/ezcal/master/docs/logo.svg" alt="ezcal" width="400">
</p>
<p align="center">
<a href="https://crates.io/crates/ezcal"><img src="https://img.shields.io/crates/v/ezcal.svg?color=e6832a" alt="crates.io"></a>
<a href="https://docs.rs/ezcal"><img src="https://img.shields.io/docsrs/ezcal?color=e6832a" alt="docs.rs"></a>
<a href="https://github.com/AdamPerlinski/ezcal/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-e6832a" alt="license"></a>
<a href="https://github.com/AdamPerlinski/ezcal"><img src="https://img.shields.io/github/stars/AdamPerlinski/ezcal?style=social" alt="GitHub stars"></a>
</p>
<p align="center">
<a href="https://adamperlinski.github.io/ezcal/"><img src="https://img.shields.io/badge/%E2%96%B6%20Docs%20%26%20Examples-Visit%20Site-6366f1?style=for-the-badge&logoColor=white" alt="Docs & Examples"></a>
</p>
**You don't need a 33K LOC enterprise library to read a calendar file.**
Most apps just need to create a meeting invite, parse an .ics export, or read a contacts file. You shouldn't need to understand RFC 5545 to do that.
ezcal is iCalendar + vCard in one crate. Builder pattern in, valid `.ics` / `.vcf` out. Parse, modify, write back — unknown properties preserved.
```
cargo add ezcal
```
---
## What Can You Do With It?
### Create a Meeting Invite
Got a meeting? Turn it into a `.ics` file any calendar app can open.
```rust
use ezcal::ical::{Calendar, Event};
let cal = Calendar::new()
.event(
Event::new()
.summary("Team Standup")
.location("Room 42")
.starts("2026-03-15T09:00:00")
.ends("2026-03-15T09:30:00")
)
.build();
std::fs::write("meeting.ics", cal.to_string())?;
// → Opens in Google Calendar, Apple Calendar, Outlook
```
### Parse Calendar Exports
Got a `.ics` file from Google Calendar? Read it in two lines.
```rust
use ezcal::ical::Calendar;
let calendar = Calendar::parse(&std::fs::read_to_string("export.ics")?)?;
for event in calendar.events() {
println!("{}: {}",
event.get_starts().unwrap(),
event.get_summary().unwrap_or("(untitled)")
);
}
```
### Build a Contact Card
Create `.vcf` files that import into any contacts app.
```rust
use ezcal::vcard::Contact;
let card = Contact::new()
.full_name("Jane Doe")
.email("jane@example.com")
.phone("+1-555-0123")
.organization("Acme Corp")
.build();
std::fs::write("jane.vcf", card.to_string())?;
```
### Read a Contacts File
Parse `.vcf` exports — even files with hundreds of contacts.
```rust
use ezcal::vcard::Contact;
let contacts = Contact::parse_all(&std::fs::read_to_string("contacts.vcf")?)?;
for c in &contacts {
println!("{}: {}", c.get_full_name().unwrap(), c.get_email().unwrap_or("(no email)"));
}
```
---
## When to Use What?
| Create a calendar event | `Calendar::new().event(Event::new()...)` | Valid `.ics` file |
| Parse an .ics file | `Calendar::parse(&text)` | Typed `Calendar` struct |
| Create a contact card | `Contact::new().full_name(...)` | Valid `.vcf` file |
| Parse a .vcf file | `Contact::parse_all(&text)` | Vec of `Contact` structs |
| Add recurring events | `.rrule(RecurrenceRule::parse("FREQ=WEEKLY")?)` | RRULE in your event |
| Add reminders | `.alarm(Alarm::display("-PT15M", "Soon!"))` | VALARM in your event |
| Work with timezones | `.starts_dt(DateTimeValue::DateTimeTz{...})` | TZID parameter set |
| Convert to chrono | `dt.to_chrono_utc()` | `DateTime<Utc>` |
---
## Real-World Use Cases
### 1. Appointment Booking System
**Problem:** "Users book appointments, I need to send them a calendar invite."
```rust
use ezcal::ical::{Calendar, Event, Alarm};
fn create_booking(title: &str, start: &str, end: &str, location: &str) -> String {
Calendar::new()
.event(
Event::new()
.summary(title)
.location(location)
.starts(start)
.ends(end)
.status("CONFIRMED")
.alarm(Alarm::display("-PT15M", "Appointment starting soon"))
)
.build()
.to_string()
}
// Attach to email or serve as HTTP response
let ics = create_booking("Haircut", "2026-03-15T10:00:00", "2026-03-15T10:30:00", "Main St Salon");
```
### 2. Calendar Sync / Migration
**Problem:** "Export from one calendar, import to another."
```rust
use ezcal::ical::Calendar;
let cal = Calendar::parse(&std::fs::read_to_string("google-export.ics")?)?;
println!("Migrating {} events, {} todos", cal.events().len(), cal.todos().len());
for event in cal.events() {
let summary = event.get_summary().unwrap_or("(untitled)");
let start = event.get_starts().map(|d| d.to_string()).unwrap_or_default();
// Insert into your new system...
println!(" {} @ {}", summary, start);
}
```
### 3. Contact Import/Export
**Problem:** "Let users export their contacts as a .vcf file."
```rust
use ezcal::vcard::{Contact, Address, StructuredName};
fn export_contacts(users: &[User]) -> String {
let mut output = String::new();
for user in users {
let card = Contact::new()
.full_name(&user.name)
.email(&user.email)
.phone(&user.phone)
.organization(&user.company)
.build();
output.push_str(&card.to_string());
}
output
}
```
### 4. Recurring Event Schedules
**Problem:** "Set up a weekly standup that repeats for a year."
```rust
use ezcal::ical::{Calendar, Event, RecurrenceRule};
let cal = Calendar::new()
.event(
Event::new()
.summary("Weekly Standup")
.starts("2026-01-05T09:00:00")
.ends("2026-01-05T09:30:00")
.rrule(RecurrenceRule::parse("FREQ=WEEKLY;BYDAY=MO;COUNT=52").unwrap())
.add_category("MEETING")
)
.build();
```
### 5. Todo List Export
**Problem:** "Export tasks to a format other apps can read."
```rust
use ezcal::ical::{Calendar, Todo};
let cal = Calendar::new()
.todo(Todo::new().summary("Ship v1.0").due_date("2026-04-01").priority(1).status("NEEDS-ACTION"))
.todo(Todo::new().summary("Write docs").due_date("2026-03-15").priority(3).status("IN-PROCESS"))
.todo(Todo::new().summary("Fix bug #42").status("COMPLETED").completed_date("2026-03-01"))
.build();
std::fs::write("tasks.ics", cal.to_string())?;
```
---
## API Reference
### iCalendar (`ezcal::ical`)
| `Calendar` | .ics container | `new()`, `parse()`, `event()`, `todo()`, `build()` |
| `Event` | VEVENT component | `summary()`, `starts()`, `ends()`, `location()`, `rrule()`, `alarm()` |
| `Todo` | VTODO component | `summary()`, `due_date()`, `status()`, `priority()`, `percent_complete()` |
| `Alarm` | VALARM component | `display(trigger, desc)`, `audio(trigger)` |
| `RecurrenceRule` | RRULE | `parse(str)`, `new(Frequency)` |
### vCard (`ezcal::vcard`)
| `Contact` | .vcf container | `new()`, `parse()`, `parse_all()`, `full_name()`, `email()`, `phone()` |
| `StructuredName` | N property | `new(family, given)`, `with_prefix()`, `with_suffix()` |
| `Address` | ADR property | `new()`, `street()`, `city()`, `region()`, `postal_code()`, `country()` |
### DateTime (`ezcal::datetime`)
| `DateTimeValue` | Date/time values | `parse()`, `to_chrono_utc()`, `from_chrono_utc()`, `to_property()` |
---
## Comparison
| **ezcal** | Yes | Yes | Yes | Active |
| `ical` | Yes | No | Read only | Archived (Aug 2024) |
| `icalendar` | Yes | No | Write-focused | Active |
| `calcard` | Yes | Yes | Yes | 33K LOC, complex API |
---
## Compatibility
Output tested against:
- Google Calendar (.ics import)
- Apple Calendar (.ics import)
- Outlook (.ics import)
- Google Contacts (.vcf import)
- Apple Contacts (.vcf import)
## Links
- [Documentation & Examples](https://adamperlinski.github.io/ezcal/)
- [API Docs (docs.rs)](https://docs.rs/ezcal)
- [crates.io](https://crates.io/crates/ezcal)
- [GitHub](https://github.com/AdamPerlinski/ezcal)
## License
MIT