ezcal 0.3.4

Ergonomic iCalendar + vCard library for Rust
Documentation
<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?

| You Want To | Use This | Result |
|-------------|----------|--------|
| 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`)

| Type | What It Does | Key Methods |
|------|-------------|-------------|
| `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`)

| Type | What It Does | Key Methods |
|------|-------------|-------------|
| `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`)

| Type | What It Does | Key Methods |
|------|-------------|-------------|
| `DateTimeValue` | Date/time values | `parse()`, `to_chrono_utc()`, `from_chrono_utc()`, `to_property()` |

---

## Comparison

| Library | iCal | vCard | Read + Write | Status |
|---------|------|-------|-------------|--------|
| **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