Skip to content

A high-level calendar and recurrence library for Rust with timezone-aware scheduling, exceptions, and ICS import/export.

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT
Notifications You must be signed in to change notification settings

AriajSarkar/eventix

Repository files navigation

Eventix 📅

A high-level calendar and recurrence library for Rust with timezone-aware scheduling, exceptions, and ICS import/export.

Crates.io Documentation CI License

Features

  • 🌍 Timezone-aware events - Full support for timezones and DST handling using chrono-tz
  • 🔄 Recurrence patterns - Daily, weekly, monthly, and yearly recurrence with advanced rules
  • 🚫 Exception handling - Skip specific dates, weekends, or custom holiday lists
  • 🚦 Booking workflow - Manage event status (Confirmed, Tentative, Cancelled) with smart gap validation
  • 📅 ICS support - Import and export events using the iCalendar (.ics) format
  • 🛠️ Builder API - Ergonomic, fluent interface for creating events and calendars
  • 🔍 Gap validation - Find gaps between events, detect conflicts, analyze schedule density
  • 📊 Schedule analysis - Occupancy metrics, conflict detection, availability finding
  • Type-safe - Leverages Rust's type system for correctness

Why Eventix?

Feature eventix icalendar chrono
Primary Goal Booking & Scheduling File Parsing Date/Time Math
Gap Finding ✅ Native Support ❌ Manual Logic ❌ Manual Logic
Booking State ✅ Confirmed/Cancelled ❌ No Concept ❌ No Concept
Timezone/DST ✅ Built-in (chrono-tz) ⚠️ Partial ✅ Built-in
Recurrence ✅ RRule + Exdates ✅ RRule ❌ None

Quick Start

Add eventix to your Cargo.toml:

[dependencies]
eventix = "0.3.1"

Basic Usage

use eventix::{Calendar, Duration, Event, Recurrence};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create a calendar
    let mut cal = Calendar::new("My Calendar");

    // Create a simple event
    let meeting = Event::builder()
        .title("Team Meeting")
        .description("Weekly sync with the team")
        .start("2025-11-01 10:00:00", "America/New_York")
        .duration(Duration::hours(1))
        .attendee("alice@example.com")
        .attendee("bob@example.com")
        .build()?;

    cal.add_event(meeting);

    // Create a recurring event
    let standup = Event::builder()
        .title("Daily Standup")
        .start("2025-11-01 09:00:00", "America/New_York")
        .duration(Duration::minutes(15))
        .recurrence(Recurrence::daily().count(30))
        .skip_weekends(true)
        .build()?;

    cal.add_event(standup);

    // Export to ICS
    cal.export_to_ics("calendar.ics")?;

    Ok(())
}

Examples

Daily Recurrence with Exceptions

use eventix::{Event, Recurrence, timezone};

let tz = timezone::parse_timezone("America/New_York")?;
let holiday = timezone::parse_datetime_with_tz("2025-11-27 09:00:00", tz)?;

let event = Event::builder()
    .title("Morning Standup")
    .start("2025-11-01 09:00:00", "America/New_York")
    .duration(Duration::minutes(15))
    .recurrence(Recurrence::daily().count(30))
    .skip_weekends(true)
    .exception_date(holiday)  // Skip Thanksgiving
    .build()?;

Weekly Recurrence

use eventix::{Event, Recurrence};

let event = Event::builder()
    .title("Weekly Team Meeting")
    .start("2025-11-03 14:00:00", "UTC")
    .duration(Duration::hours(1))
    .recurrence(Recurrence::weekly().count(10))
    .build()?;

Monthly Recurrence

use eventix::{Event, Recurrence};

let event = Event::builder()
    .title("Monthly All-Hands")
    .start("2025-11-01 15:00:00", "America/Los_Angeles")
    .duration(Duration::hours(2))
    .recurrence(Recurrence::monthly().count(12))
    .build()?;

Booking Workflow

use eventix::{Event, EventStatus};

let mut event = Event::builder()
    .title("Tentative Meeting")
    .start("2025-11-01 10:00:00", "UTC")
    .duration(Duration::hours(1))
    .status(EventStatus::Tentative)
    .build()?;

// Later, confirm the booking
event.confirm();

// Or cancel it (automatically ignored by gap validation)
event.cancel();

ICS Import/Export

use eventix::Calendar;

// Export with timezone awareness
let mut cal = Calendar::new("Work Schedule");
// ... add events ...
cal.export_to_ics("schedule.ics")?;

// Import
let imported_cal = Calendar::import_from_ics("schedule.ics")?;
println!("Imported {} events", imported_cal.event_count());

Timezone-Aware ICS Export:

Events are exported with proper timezone information for compatibility with calendar applications:

// Non-UTC timezones include TZID parameter
let event = Event::builder()
    .title("Team Meeting")
    .start("2025-10-27 10:00:00", "America/New_York")
    .duration(Duration::hours(1))
    .build()?;

// Generates: DTSTART;TZID=America/New_York:20251027T100000

// UTC events use standard Z suffix
let utc_event = Event::builder()
    .title("Global Call")
    .start("2025-10-27 15:00:00", "UTC")
    .duration(Duration::hours(1))
    .build()?;

// Generates: DTSTART:20251027T150000Z

This ensures events display at the correct local time in:

  • Google Calendar
  • Microsoft Outlook
  • Apple Calendar
  • Any RFC 5545 compliant calendar application

Query Events

use eventix::{Calendar, timezone};

let cal = Calendar::new("My Calendar");
// ... add events ...

// Find events by title
let meetings = cal.find_events_by_title("meeting");

// Get events in a date range
let tz = timezone::parse_timezone("UTC")?;
let start = timezone::parse_datetime_with_tz("2025-11-01 00:00:00", tz)?;
let end = timezone::parse_datetime_with_tz("2025-11-30 23:59:59", tz)?;

let november_events = cal.events_between(start, end)?;

// Get events on a specific date
let date = timezone::parse_datetime_with_tz("2025-11-15 00:00:00", tz)?;
let events = cal.events_on_date(date)?;

Gap Detection & Schedule Analysis

Unique to Eventix - Features not found in other calendar crates:

use eventix::{Calendar, Event, Duration, gap_validation, timezone};

let mut cal = Calendar::new("Work Schedule");
// ... add events ...

let tz = timezone::parse_timezone("America/New_York")?;
let start = timezone::parse_datetime_with_tz("2025-11-03 08:00:00", tz)?;
let end = timezone::parse_datetime_with_tz("2025-11-03 18:00:00", tz)?;

// Find gaps between events (at least 30 minutes)
let gaps = gap_validation::find_gaps(&cal, start, end, Duration::minutes(30))?;
for gap in gaps {
    println!("Free: {} to {} ({} min)",
        gap.start.format("%H:%M"),
        gap.end.format("%H:%M"),
        gap.duration_minutes()
    );
}

// Detect scheduling conflicts
let overlaps = gap_validation::find_overlaps(&cal, start, end)?;
if !overlaps.is_empty() {
    println!("⚠️  Found {} conflicts", overlaps.len());
}

// Analyze schedule density
let density = gap_validation::calculate_density(&cal, start, end)?;
println!("Schedule occupancy: {:.1}%", density.occupancy_percentage);
println!("Busy: {:.1}h, Free: {:.1}h",
    density.busy_duration.num_minutes() as f64 / 60.0,
    density.free_duration.num_minutes() as f64 / 60.0
);

// Find available slots for a 1-hour meeting
let slots = gap_validation::find_available_slots(&cal, start, end, Duration::hours(1))?;
println!("Available times for 1-hour meeting: {}", slots.len());

// Check if specific time is available
let check_time = timezone::parse_datetime_with_tz("2025-11-03 14:00:00", tz)?;
let available = gap_validation::is_slot_available(&cal, check_time, check_time + Duration::hours(1))?;

// Get alternative times for conflicts
let alternatives = gap_validation::suggest_alternatives(
    &cal,
    check_time,
    Duration::hours(1),
    Duration::hours(2)  // search within 2 hours
)?;

Documentation

Run the examples:

# Basic calendar usage
cargo run --example basic

# Recurrence patterns
cargo run --example recurrence

# ICS import/export
cargo run --example ics_export

# Gap validation and schedule analysis
cargo run --example gap_validation

View the full API documentation:

cargo doc --open

Architecture

The crate is organized into several modules:

  • calendar - Calendar container for managing events
  • event - Event types and builder API
  • recurrence - Recurrence rules and patterns
  • ics - ICS format import/export
  • timezone - Timezone handling and DST support
  • gap_validation - Schedule analysis, gap detection, conflict resolution
  • error - Error types and results

Dependencies

Timezone Support

Eventix fully supports timezone-aware datetime handling with automatic DST transitions:

use eventix::timezone;

// Parse timezone
let tz = timezone::parse_timezone("America/New_York")?;

// Parse datetime with timezone
let dt = timezone::parse_datetime_with_tz("2025-11-01 10:00:00", tz)?;

// Convert between timezones
let tokyo_tz = timezone::parse_timezone("Asia/Tokyo")?;
let dt_tokyo = timezone::convert_timezone(&dt, tokyo_tz);

// Check if datetime is in DST
let is_summer_time = timezone::is_dst(&dt);

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under either of:

at your option.

Acknowledgments

Built with these excellent crates:

  • chrono and chrono-tz for date/time handling
  • rrule for recurrence rule support
  • icalendar for ICS format compatibility

About

A high-level calendar and recurrence library for Rust with timezone-aware scheduling, exceptions, and ICS import/export.

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Packages

No packages published

Contributors 5

Languages