A high-level calendar and recurrence library for Rust with timezone-aware scheduling, exceptions, and ICS import/export.
- 🌍 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
| 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) |
✅ Built-in | |
| Recurrence | ✅ RRule + Exdates | ✅ RRule | ❌ None |
Add eventix to your Cargo.toml:
[dependencies]
eventix = "0.3.1"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(())
}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()?;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()?;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()?;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();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:20251027T150000ZThis ensures events display at the correct local time in:
- Google Calendar
- Microsoft Outlook
- Apple Calendar
- Any RFC 5545 compliant calendar application
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)?;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
)?;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_validationView the full API documentation:
cargo doc --openThe crate is organized into several modules:
calendar- Calendar container for managing eventsevent- Event types and builder APIrecurrence- Recurrence rules and patternsics- ICS format import/exporttimezone- Timezone handling and DST supportgap_validation- Schedule analysis, gap detection, conflict resolutionerror- Error types and results
chrono- Date and time handlingchrono-tz- Timezone databaserrule- Recurrence rule parsingicalendar- ICS format supportserde- Serialization 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);Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Built with these excellent crates:
chronoandchrono-tzfor date/time handlingrrulefor recurrence rule supporticalendarfor ICS format compatibility