Thanks to visit codestin.com
Credit goes to github.com

Skip to content

thoven87/icalendar-kit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

36 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

iCalendar Kit

A comprehensive Swift 6 library for parsing and creating iCalendar (RFC 5545) events with full support for structured concurrency, Sendable conformance, and modern Swift features.

let calendar = ICalendar.calendar {
    CalendarName("My Calendar")
    createEvent(summary: "Meeting", startDate: Date(), endDate: Date().addingTimeInterval(3600))
}

CI

Features

  • RFC Compliant: Full support for multiple iCalendar specifications (see RFC Compliance below)
  • Maximum Compatibility: RFC 7986 standard properties with automatic legacy X-WR fallbacks
  • Swift 6 Ready: Complete Sendable conformance and structured concurrency support
  • Type Safe: Uses structs instead of classes with comprehensive type operations
  • Unified Alarm API: Type-safe alarm creation with RFC 5545 compliance and action-specific requirements
  • Comprehensive: Support for events, todos, journals, alarms, time zones, and recurrence rules
  • Enhanced Properties: RFC 7986 extensions including COLOR, IMAGE, CONFERENCE, GEO, and ATTACH properties
  • Binary Attachments: Base64-encoded binary data support for ATTACH and IMAGE properties
  • Timezone Integration: Foundation TimeZone integration with VTIMEZONE generation (RFC 7808)
  • Builder Pattern: Fluent API for easy calendar and event creation
  • Cross-Platform: Works in modern apps (Google Calendar) and legacy systems (older Apple Calendar)

Requirements

  • iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+
  • Swift 6.0+
  • Xcode 16.0+
  • Linux

Installation

Swift Package Manager

Add the following to your Package.swift file:

dependencies: [
    .package(url: "https://github.com/thoven87/icalendar-kit.git", from: "1.0.0")
]

Or add it through Xcode:

  1. File β†’ Add Package Dependencies
  2. Enter the repository URL
  3. Select the version and target

Quick Start

Creating a Simple Event

import ICalendar

// Create calendar with RFC 7986 properties (automatic legacy compatibility)
var calendar = ICalendar(productId: "-//My App//Team Calendar//EN")
calendar.name = "My Team Calendar"  // Sets both NAME and X-WR-CALNAME
calendar.calendarDescription = "Team events and meetings"  // Sets both DESCRIPTION and X-WR-CALDESC
calendar.refreshInterval = ICalDuration(hours: 6)  // Sets both REFRESH-INTERVAL and X-PUBLISHED-TTL

// Create event with unified alarm API
let organizer = ICalAttendee(email: "[email protected]", commonName: "Manager")

let teamMeeting = EventBuilder(summary: "Team Meeting")
    .starts(at: Date(), timeZone: .current)
    .duration(3600) // 1 hour in seconds
    .location("Conference Room A")
    .description("Weekly team sync meeting")
    .categories("Work", "Meeting")
    .confirmed()
    // Unified alarm API with type-safe, RFC 5545 compliant alarms
    .addAlarm(.display(description: "Meeting starts in 15 minutes"), trigger: .minutesBefore(15))
    .addAlarm(.email(description: "Meeting reminder", summary: "Team Meeting", to: organizer), trigger: .minutesBefore(60))
    .addAlarm(.audio(), trigger: .minutesBefore(5))  // Sound notification
    .buildEvent()

calendar.addEvent(teamMeeting)

// Serialize to iCalendar format (includes both RFC 7986 and legacy properties)
let icsString = try ICalendarSerializer().serialize(calendar)
print(icsString)

Parsing iCalendar Content

let icalContent = """
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//My App//EN
BEGIN:VEVENT
UID:event-123
DTSTAMP:20240101T120000Z
DTSTART:20240101T140000Z
DTEND:20240101T150000Z
SUMMARY:Important Meeting
LOCATION:Room 101
END:VEVENT
END:VCALENDAR
"""

// Direct parsing functions (no client needed in v2.0)
let calendar = try ICalendarKit.parseCalendar(from: icalContent)
print("Found \(calendar.events.count) events")

Using EventBuilder with Unified Alarm API

// Create events using EventBuilder with type-safe alarms
let startTime = Date()
let timeZone = TimeZone(identifier: "America/New_York")!
let manager = ICalAttendee(email: "[email protected]", commonName: "Project Manager")

// Meeting with progressive alarms
let meeting = EventBuilder(summary: "Daily Standup")
    .starts(at: startTime, timeZone: timeZone)
    .duration(1800) // 30 minutes in seconds
    .location("Virtual - Zoom")
    .description("Daily team standup meeting")
    .categories("Work", "Meeting")
    .confirmed()
    // Multiple alarms with different actions
    .addAlarms([
        .display(description: "Standup in 15 minutes"),
        .display(description: "Standup in 5 minutes"),
        .audio()  // Sound notification
    ], triggers: [
        .minutesBefore(15),
        .minutesBefore(5),
        .minutesBefore(2)
    ])
    .buildEvent()

// Project planning with email notification
let projectPlanning = EventBuilder(summary: "Project Planning")
    .starts(at: Calendar.current.date(byAdding: .day, value: 1, to: Date())!, timeZone: timeZone)
    .duration(7200) // 2 hours in seconds
    .location("Conference Room B")
    .description("Weekly project planning session")
    .organizer(email: "[email protected]", name: "Project Manager")
    .addAttendee(email: "[email protected]", name: "Alice", role: .requiredParticipant)
    .addAttendee(email: "[email protected]", name: "Bob", role: .optionalParticipant)
    .highPriority()
    .confirmed()
    // Email alarm with all required properties
    .addAlarm(.email(
        description: "Project planning session tomorrow - please prepare your reports",
        summary: "Project Planning Reminder",
        to: manager
    ), trigger: .minutesBefore(1440))  // 24 hours before
    .buildEvent()

// Create calendar with maximum compatibility
var calendar = ICalendar(productId: "-//My Company//Project Calendar//EN")
calendar.name = "Project Calendar"  // Automatically sets NAME + X-WR-CALNAME
calendar.calendarDescription = "Weekly project events"  // Sets DESCRIPTION + X-WR-CALDESC
calendar.color = "#2196F3"  // Material Blue
calendar.addEvent(meeting)
calendar.addEvent(projectPlanning)

Advanced Usage

Creating Recurring Events

let startTime = Date()
let timeZone = TimeZone(identifier: "America/New_York")!

// Daily standup for 10 occurrences
let dailyStandup = EventBuilder(summary: "Daily Standup")
    .starts(at: startTime, timeZone: timeZone)
    .duration(1800) // 30 minutes
    .location("Team Room")
    .description("Daily team standup meeting")
    .repeats(every: 1, count: 10) // Daily for 10 days
    .categories("Work", "Standup")
    .confirmed()
    .addAlarm(action: .display, minutesBefore: 5, description: "Standup starting soon")
    .buildEvent()

// Weekly team meeting on weekdays (Monday, Wednesday, Friday)
let weeklyMeeting = EventBuilder(summary: "Team Meeting")
    .starts(at: startTime, timeZone: timeZone)
    .duration(3600) // 1 hour
    .location("Conference Room A")
    .description("Weekly team sync meeting")
    .repeatsWeekly(every: 1, on: [.monday, .wednesday, .friday], count: 8)
    .organizer(email: "[email protected]", name: "Team Lead")
    .categories("Work", "Team Meeting")
    .confirmed()
    .addAlarm(action: .display, minutesBefore: 15, description: "Team meeting reminder")
    .buildEvent()

// Monthly all-hands meeting
let monthlyAllHands = EventBuilder(summary: "All Hands Meeting")
    .starts(at: startTime, timeZone: timeZone)
    .duration(7200) // 2 hours
    .location("Main Auditorium")
    .description("Monthly company-wide meeting")
    .repeatsMonthly(every: 1, count: 12) // Monthly for 12 months
    .organizer(email: "[email protected]", name: "CEO")
    .categories("Company", "All Hands")
    .highPriority()
    .confirmed()
    .addAlarm(action: .display, minutesBefore: 30, description: "All hands meeting starting soon")
    .buildEvent()

// Create calendar and add events
var calendar = ICalendar(productId: "-//My Company//Recurring Events//EN")
calendar.name = "Recurring Events Calendar"
calendar.addEvent(dailyStandup)
calendar.addEvent(weeklyMeeting)
calendar.addEvent(monthlyAllHands)

Adding Attendees and Organizer

// Create meeting with attendees and organizer
let startTime = Date()
let timeZone = TimeZone(identifier: "America/New_York")!

let projectKickoff = EventBuilder(summary: "Project Kickoff")
    .starts(at: startTime, timeZone: timeZone)
    .duration(7200) // 2 hours
    .location("Conference Room A")
    .description("Project kickoff meeting to discuss goals and timeline")
    .organizer(email: "[email protected]", name: "Meeting Organizer")
    .attendee(email: "[email protected]", name: "John Doe", required: true)
    .attendee(email: "[email protected]", name: "Jane Smith", required: false)
    .attendee(email: "[email protected]", name: "Mike Johnson", required: true)
    .categories("Project", "Kickoff")
    .highPriority()
    .confirmed()
    .addAlarm(action: .display, minutesBefore: 15, description: "Meeting starts in 15 minutes")
    .addAlarm(action: .display, minutesBefore: 60, description: "Project kickoff in 1 hour")
    .buildEvent()

// Create calendar and add the event
var calendar = ICalendar(productId: "-//My Company//Meeting Planner//EN")
calendar.name = "Team Meetings"
calendar.addEvent(projectKickoff)

// All-day event example
let teamOuting = EventBuilder(summary: "Team Building Outing")
    .allDay(on: Calendar.current.date(byAdding: .day, value: 7, to: Date())!, timeZone: timeZone)
    .location("Adventure Park")
    .description("Annual team building activity")
    .organizer(email: "[email protected]", name: "HR Department")
    .attendee(email: "[email protected]", name: "Team Member 1")
    .attendee(email: "[email protected]", name: "Team Member 2")
    .categories("Team Building", "Fun")
    .confirmed()
    .buildEvent()

calendar.addEvent(teamOuting)

Using the Builder Pattern with Pre-configured Templates

// Create healthcare calendar with EventBuilder
let startTime = Date()
let timeZone = TimeZone(identifier: "America/New_York")!

// Healthcare event
let consultation = EventBuilder(summary: "Patient Consultation - John Doe")
    .starts(at: startTime, timeZone: timeZone)
    .duration(3600) // 1 hour
    .location("Room 205 - Cardiology")
    .description("Routine cardiology consultation")
    .categories("Healthcare", "Consultation")
    .confirmed()
    .privateEvent() // HIPAA compliant
    .addAlarm(action: .display, minutesBefore: 15, description: "Patient consultation starting")
    .buildEvent()

var healthcareCalendar = ICalendar(productId: "-//City Hospital//Healthcare App//EN")
healthcareCalendar.name = "Cardiology Schedule"
healthcareCalendar.calendarDescription = "City Hospital - Cardiology Department"
healthcareCalendar.addEvent(consultation)

// Corporate team calendar
let sprintPlanning = EventBuilder(summary: "Sprint Planning")
    .starts(at: Calendar.current.date(byAdding: .day, value: 1, to: Date())!, timeZone: timeZone)
    .duration(7200) // 2 hours
    .location("Engineering Conference Room")
    .description("Planning for next development sprint")
    .organizer(email: "[email protected]", name: "Scrum Master")
    .attendee(email: "[email protected]", name: "Developer 1", required: true)
    .attendee(email: "[email protected]", name: "Developer 2", required: true)
    .attendee(email: "[email protected]", name: "UX Designer", required: false)
    .categories("Engineering", "Planning")
    .highPriority()
    .confirmed()
    .addAlarm(action: .display, minutesBefore: 30, description: "Sprint planning starting soon")
    .buildEvent()

var corporateCalendar = ICalendar(productId: "-//Acme Corp//Corporate//EN")
corporateCalendar.name = "Engineering Team"
corporateCalendar.calendarDescription = "Acme Corp - Engineering Team Events"
corporateCalendar.addEvent(sprintPlanning)

Working with Time Zones

// Multi-timezone calendar with EventBuilder
let pstTimeZone = TimeZone(identifier: "America/Los_Angeles")!
let estTimeZone = TimeZone(identifier: "America/New_York")!

// West Coast event (9 AM PST)
let westCoastStandup = EventBuilder(summary: "West Coast Team Standup")
    .starts(at: Date(), timeZone: pstTimeZone)
    .duration(3600) // 1 hour
    .location("San Francisco Office")
    .description("Daily standup for west coast team")
    .categories("Work", "Standup")
    .repeats(every: 1, count: 30) // Daily for 30 days
    .confirmed()
    .addAlarm(action: .display, minutesBefore: 5, description: "Standup starting")
    .buildEvent()

// East Coast event (2 PM EST - same time as west coast standup)
let eastCoastCall = EventBuilder(summary: "East Coast Client Call")
    .starts(at: Calendar.current.date(byAdding: .day, value: 1, to: Date())!, timeZone: estTimeZone)
    .duration(3600) // 1 hour
    .location("New York Office")
    .description("Important client call")
    .organizer(email: "[email protected]", name: "Sales Team")
    .categories("Sales", "Client")
    .highPriority()
    .confirmed()
    .addAlarm(action: .display, minutesBefore: 15, description: "Client call starting")
    .buildEvent()

var multiTimeZoneCalendar = ICalendar(productId: "-//Multi-TZ Company//EN")
multiTimeZoneCalendar.name = "Multi-Timezone Events"
multiTimeZoneCalendar.addEvent(westCoastStandup)
multiTimeZoneCalendar.addEvent(eastCoastCall)

Advanced Recurring Events

// Daily stand up meetings
let startTime = Calendar.current.date(from: DateComponents(year: 2024, month: 1, day: 1, hour: 9))!
let timeZone = TimeZone(identifier: "America/New_York")!

// Weekly stand up on weekdays
let weeklyStandUp = EventBuilder(summary: "Team Stand Up")
    .starts(at: startTime, timeZone: timeZone)
    .duration(1800) // 30 minutes
    .location("Conference Room A")
    .description("Daily team stand up meeting")
    .repeatsWeekly(every: 1, on: [.monday, .tuesday, .wednesday, .thursday, .friday])
    .categories("Work", "Stand Up")
    .confirmed()
    .addAlarm(action: .display, minutesBefore: 15, description: "Stand up starting soon")
    .buildEvent()

// Daily recurring pattern
let dailyStandUp = EventBuilder(summary: "Daily Stand Up")
    .starts(at: startTime, timeZone: timeZone)
    .duration(900) // 15 minutes
    .description("Quick daily sync")
    .repeats(every: 1, count: 30) // Daily for 30 days
    .categories("Daily", "Stand Up")
    .confirmed()
    .buildEvent()

// Monthly team sync
let monthlySync = EventBuilder(summary: "Monthly Team Sync")
    .starts(at: startTime, timeZone: timeZone)
    .duration(3600) // 1 hour
    .repeatsMonthly(every: 1, count: 12)
    .organizer(email: "[email protected]", name: "Team Manager")
    .categories("Team", "Sync")
    .confirmed()
    .addAlarm(action: .display, minutesBefore: 30, description: "Monthly sync starting")
    .buildEvent()

var calendar = ICalendar(productId: "-//My Company//Team Events//EN")
calendar.name = "Team Calendar"
calendar.addEvent(weeklyStandUp)
calendar.addEvent(dailyStandUp)
calendar.addEvent(monthlySync)

Creating To-Do Items

// Create todo items using the direct function API
let todoCalendar = ICalendar.calendar(productId: "-//Task Manager//EN") {
    CalendarName("Project Tasks")
    CalendarMethod("PUBLISH")

    ICalendarFactory.createTodo(
        summary: "Complete project documentation",
        dueDate: Date().addingTimeInterval(604800), // 1 week
        priority: 1,
        description: "Write comprehensive documentation for the new feature"
    )

    ICalendarFactory.createTodo(
        summary: "Code review for PR #123",
        startDate: Date(),
        dueDate: Date().addingTimeInterval(172800), // 2 days
        priority: 3,
        description: "Review the authentication module changes"
    )
}

### Working with Alarms - Unified Type-Safe API

The unified alarm API provides compile-time safety and RFC 5545 compliance:

```swift
let attendee = ICalAttendee(email: "[email protected]", commonName: "User")
// Create audio attachment from URL
let soundFile = ICalAttachment(uri: "https://example.com/notification.mp3", mediaType: "audio/mp3")

let event = EventBuilder(summary: "Important Meeting")
    .starts(at: Date(), timeZone: .current)
    .duration(3600)
    
    // DISPLAY alarms - require description
    .addAlarm(.display(description: "Meeting in 30 minutes"), trigger: .minutesBefore(30))
    .addAlarm(.display(description: "Meeting starting now"), trigger: .eventStart)
    
    // EMAIL alarms - require description, summary, and attendee
    .addAlarm(.email(
        description: "Don't forget about the important meeting",
        summary: "Meeting Reminder", 
        to: attendee
    ), trigger: .minutesBefore(60))
    
    // AUDIO alarms - optional attachment (URL or binary data)
    .addAlarm(.audio(attachment: soundFile), trigger: .minutesBefore(10))  // Custom sound from URL
    .addAlarm(.audio(), trigger: .minutesBefore(2))  // System default sound

    // Multiple alarms at once
    .addAlarms([
        .display(description: "Final warning - 5 minutes"),
        .display(description: "Meeting starting - 1 minute"),
        .audio()
    ], triggers: [
        .minutesBefore(5),
        .minutesBefore(1),
        .eventStart
    ])

    // Different trigger types
    .addAlarm(.display(description: "Custom timing"),
              trigger: .duration(ICalDuration(hours: 1, minutes: 30)))
    .addAlarm(.display(description: "Absolute time reminder"),
              trigger: .absoluteTime(specificDate, timeZone: .current))

    .buildEvent()

RFC 7986 Calendar Properties with Legacy Compatibility

iCalendar-Kit automatically provides maximum compatibility by setting both modern RFC 7986 properties and their legacy X-WR equivalents:

var calendar = ICalendar(productId: "-//My App//Modern Calendar//EN")

// Setting RFC 7986 properties automatically includes legacy equivalents
calendar.name = "My Calendar"                    // Sets NAME + X-WR-CALNAME
calendar.calendarDescription = "Description"     // Sets DESCRIPTION + X-WR-CALDESC
calendar.refreshInterval = ICalDuration(hours: 6) // Sets REFRESH-INTERVAL + X-PUBLISHED-TTL

// Additional RFC 7986 properties
calendar.color = "#FF5722"
calendar.image = "https://example.com/calendar.png"
calendar.source = "https://api.example.com/calendar.ics"

// Generated ICS includes both for maximum compatibility:
// NAME:My Calendar
// X-WR-CALNAME:My Calendar
// DESCRIPTION:Description
// X-WR-CALDESC:Description
// REFRESH-INTERVAL:PT6H
// X-PUBLISHED-TTL:PT6H
// Create alarms using direct functions in v2.0
let calendarWithAlarms = ICalendar.calendar(productId: "-//Alarm Example//EN") {
    CalendarName("Events with Alarms")

    EventBuilder(summary: "Important Meeting")
        .startDate(Date())
        .endDate(Date().addingTimeInterval(3600))
        .location("Conference Room")
        .addAlarm(ICalendarFactory.createDisplayAlarm(
            description: "Meeting reminder",
            triggerMinutesBefore: 15
        ))
        .addAlarm(ICalendarFactory.createAudioAlarm(
            triggerMinutesBefore: 5,
            audioFile: "reminder.wav"
        ))
        .addAlarm(ICalendarFactory.createEmailAlarm(
            summary: "Meeting Tomorrow",
            description: "Don't forget about the important meeting tomorrow",
            attendees: [ICalendarFactory.createAttendee(email: "[email protected]")],
            triggerMinutesBefore: 1440 // 24 hours
        ))
}

Migration Guide

From Version 1.x to 2.0

// Old API (v1.x) - still works but deprecated
.addAlarm(action: .display, minutesBefore: 15, description: "Meeting soon")

// New Unified API (v2.0+) - recommended
.addAlarm(.display(description: "Meeting soon"), trigger: .minutesBefore(15))

From Direct Property Setting

// Old approach - only sets one property
calendar.displayName = "My Calendar"        // Only X-WR-CALNAME
calendar.xwrDescription = "Description"     // Only X-WR-CALDESC

// New approach - maximum compatibility
calendar.name = "My Calendar"               // Sets NAME + X-WR-CALNAME
calendar.calendarDescription = "Description" // Sets DESCRIPTION + X-WR-CALDESC

Best Practices

  1. Use RFC 7986 properties (name, calendarDescription, refreshInterval) for new code
  2. Leverage the unified alarm API for type safety and RFC compliance
  3. Let the library handle compatibility - don't manually set both properties
  4. Test across multiple calendar applications to ensure compatibility

Configuration

Parsing and Serialization Options

Version 2.0 provides direct control over parsing and serialization behavior:

// Parse with validation (default)
let calendar = try ICalendarKit.parseCalendar(from: icsContent, validateOnParse: true)

// Parse without validation for performance
let fastCalendar = try ICalendarKit.parseCalendar(from: icsContent, validateOnParse: false)

// Serialize with validation (default)
let validatedICS = try ICalendarKit.serializeCalendar(calendar, validateBeforeSerializing: true)

// Serialize without validation for performance
let fastICS = try ICalendarKit.serializeCalendar(calendar, validateBeforeSerializing: false)

Working with Multiple Calendars

// Parse and work with multiple calendars using v2.0 API
let icsFiles = ["calendar1.ics", "calendar2.ics", "calendar3.ics"]
var allCalendars: [ICalendar] = []

for filename in icsFiles {
    let url = URL(fileURLWithPath: filename)
    let calendar = try ICalendarKit.parseCalendar(from: url, validateOnParse: false) // Skip validation for performance
    allCalendars.append(calendar)
}

// Serialize multiple calendars together
let combinedICS = try ICalendarKit.serializeCalendars(allCalendars, validateBeforeSerializing: true)

// Create a merged calendar from multiple sources
let mergedCalendar = ICalendar.calendar(productId: "-//Merged Calendar//EN") {
    CalendarName("Combined Events")
    CalendarDescription("Events from multiple sources")

    for calendar in allCalendars {
        for event in calendar.events {
            event // Add each event to the new calendar
        }
    }
}

Structured Concurrency Support

The library is built with Swift 6 structured concurrency in mind, with full Sendable conformance:

// Concurrent parsing of multiple calendars using v2.0 API
await withTaskGroup(of: ICalendar.self) { group in
    let contents = [content1, content2, content3]
    var calendars: [ICalendar] = []

    for content in contents {
        group.addTask {
            return try ICalendarKit.parseCalendar(from: content, validateOnParse: true)
        }
    }

    for await calendar in group {
        calendars.append(calendar)
    }

    return calendars
}

// Process calendars concurrently
await withTaskGroup(of: String.self) { group in
    for calendar in calendars {
        group.addTask {
            return try ICalendarKit.serializeCalendar(calendar, validateBeforeSerializing: true)
        }
    }

    for await serialized in group {
        // Handle serialized calendar
        print("Processed calendar: \(serialized.prefix(100))...")
    }
}

Utility Functions

Date and Time Helpers

// Convert Date to ICalDateTime
let dateTime = Date().asICalDateTime()
let dateOnly = Date().asICalDateOnly()
let utcDateTime = Date().asICalDateTimeUTC()

// Convert TimeInterval to Duration
let duration = TimeInterval(3600).asICalDuration // 1 hour

Array Extensions

// Filter events by date range
let todayEvents = calendar.events.events(
    from: Calendar.current.startOfDay(for: Date()),
    to: Calendar.current.date(byAdding: .day, value: 1, to: Date())!
)

// Get recurring events
let recurringEvents = calendar.events.recurringEvents

// Sort events by start date
let sortedEvents = calendar.events.sortedByStartDate

Common Recurrence Patterns

// Pre-defined patterns
let daily = RecurrencePatterns.daily(count: 30)
let weekdays = RecurrencePatterns.weekdays(count: 20)
let monthly = RecurrencePatterns.monthly(dayOfMonth: 1, count: 12)
let yearly = RecurrencePatterns.yearly(count: 5)

// Monthly on first Monday
let firstMonday = RecurrencePatterns.monthly(
    ordinal: 1,
    weekday: .monday,
    count: 12
)

RFC Compliance

iCalendar-Kit provides comprehensive support for multiple RFC specifications with automatic compatibility handling:

Core Specifications

RFC Description Support Level Key Features
RFC 5545 iCalendar Core βœ… Complete Events, TODOs, Journals, Alarms, Recurrence
RFC 7986 Calendar Extensions βœ… Complete NAME, DESCRIPTION, COLOR, IMAGE properties
RFC 6868 Parameter Encoding βœ… Complete Special character encoding in parameters
RFC 7808 Time Zone Data βœ… Complete VTIMEZONE generation with automatic TZURL

RFC 7986 β†’ Legacy Compatibility

Automatic dual-property support for maximum compatibility:

// Setting one property creates both RFC 7986 and legacy versions
calendar.name = "My Calendar"
// Creates: NAME:My Calendar + X-WR-CALNAME:My Calendar

calendar.calendarDescription = "Description"
// Creates: DESCRIPTION:Description + X-WR-CALDESC:Description

calendar.refreshInterval = ICalDuration(hours: 6)
// Creates: REFRESH-INTERVAL:PT6H + X-PUBLISHED-TTL:PT6H

VALARM RFC 5545 Compliance

The unified alarm API enforces all RFC 5545 requirements at compile-time:

Alarm Type Required Properties Enforced by API
DISPLAY DESCRIPTION βœ… .display(description:)
EMAIL DESCRIPTION, SUMMARY, ATTENDEE βœ… .email(description:summary:to:)
AUDIO None (ATTACH optional) βœ… .audio(attachment:)
PROCEDURE ATTACH βœ… .procedure(attachment:)

TZURL - Automatic Timezone Updates (RFC 7808)

iCalendar-Kit automatically generates TZURL properties for better timezone handling:

// TimeZoneRegistry automatically adds TZURL to VTIMEZONE components
if let nyTimeZone = TimeZoneRegistry.shared.getTimeZone(for: "America/New_York") {
    calendar.addTimeZone(nyTimeZone)
    // Generated VTIMEZONE will include:
    // TZURL:http://tzurl.org/zoneinfo-outlook/America/New_York
}

Benefits of TZURL:

  • Automatic Updates: Calendar applications can fetch latest timezone rules
  • Government Changes: Handles daylight saving rule changes automatically
  • Cross-Platform Consistency: All clients use the same timezone definitions
  • Future-Proof: Events remain accurate even after timezone rule updates

Generated TZURL Format:

  • TZURL:http://tzurl.org/zoneinfo-outlook/America/Los_Angeles
  • TZURL:http://tzurl.org/zoneinfo-outlook/Europe/London
  • TZURL:http://tzurl.org/zoneinfo-outlook/Asia/Tokyo

Calendar Application Compatibility

Application RFC 7986 Support Legacy X-WR Support TZURL Support iCalendar-Kit Compatibility
Google Calendar βœ… Yes βœ… Yes βœ… Yes βœ… Perfect
Apple Calendar (2020+) βœ… Yes βœ… Yes βœ… Yes βœ… Perfect
Apple Calendar (Legacy) ❌ No βœ… Yes πŸ”Ά Partial βœ… Perfect (via X-WR)
Outlook πŸ”Ά Partial βœ… Yes βœ… Yes βœ… Perfect
CalDAV Servers πŸ”Ά Varies βœ… Yes βœ… Yes βœ… Perfect

Validation and Compliance Testing

All generated calendars are automatically validated for RFC compliance:

// Automatic validation during serialization
let icsContent = try ICalendarSerializer().serialize(calendar)
// βœ… Validates all required properties are present
// βœ… Ensures proper component structure
// βœ… Verifies alarm requirements are met
// βœ… Checks recurrence rule validity

Complete Example - Production-Ready Calendar

Here's a comprehensive example showcasing RFC 7986 compatibility, unified alarms, and enterprise features:

import ICalendar

/// Production-ready calendar with maximum compatibility
func createEnterpriseCalendar() throws -> String {
    // Create calendar with RFC 7986 properties (automatic legacy compatibility)
    var calendar = ICalendar(productId: "-//Enterprise Corp//Business Calendar v2.0//EN")

    // RFC 7986 properties (automatically includes X-WR equivalents)
    calendar.name = "Enterprise Business Calendar"
    calendar.calendarDescription = "Official company calendar with meetings, deadlines, and events"
    calendar.refreshInterval = ICalDuration(hours: 6)  // Refresh every 6 hours
    calendar.color = "#1976D2"  // Corporate blue
    calendar.source = "https://api.enterprise.com/calendar.ics"
    calendar.image = "https://enterprise.com/calendar-icon.png"

    // Create attendees
    let ceo = ICalAttendee(email: "[email protected]", commonName: "CEO")
    let manager = ICalAttendee(email: "[email protected]", commonName: "Project Manager")
    let team = ICalAttendee(email: "[email protected]", commonName: "Development Team")

    // 1. Executive Meeting with Email Notifications
    let executiveMeeting = EventBuilder(summary: "πŸ“Š Quarterly Business Review")
        .starts(at: Date().addingTimeInterval(86400), timeZone: .current)  // Tomorrow
        .duration(7200)  // 2 hours
        .location("Executive Boardroom")
        .description("Comprehensive Q4 performance review with stakeholder presentations")
        .organizer(email: "[email protected]", name: "CEO")
        .addAttendee(email: "[email protected]", name: "Project Manager", role: .requiredParticipant)
        .addAttendee(email: "[email protected]", name: "CFO", role: .requiredParticipant)
        .addAttendee(email: "[email protected]", name: "Development Team", role: .optionalParticipant, userType: .group)
        .highPriority()
        .confirmed()
        // Progressive alarm strategy with type-safe API
        .addAlarm(.email(
            description: "Quarterly review tomorrow - please prepare your reports and presentations",
            summary: "Business Review Preparation",
            to: ceo
        ), trigger: .minutesBefore(1440))  // 24 hours
        .addAlarm(.display(description: "Business review in 2 hours - final preparations"), trigger: .minutesBefore(120))
        .addAlarm(.display(description: "Business review in 30 minutes"), trigger: .minutesBefore(30))
        .addAlarm(.audio(), trigger: .minutesBefore(10))  // Sound notification
        .buildEvent()

    // 2. Recurring Team Standup
    let dailyStandup = EventBuilder(summary: "πŸš€ Daily Team Standup")
        .starts(at: nextMondayAt9AM(), timeZone: .current)
        .duration(900)  // 15 minutes
        .location("Conference Room A / Virtual")
        .description("Daily team coordination and blocker discussion")
        .organizer(email: "[email protected]", name: "Project Manager")
        .addAttendee(email: "[email protected]", name: "Developer 1", role: .requiredParticipant)
        .addAttendee(email: "[email protected]", name: "Developer 2", role: .requiredParticipant)
        .addAttendee(email: "[email protected]", name: "UX Designer", role: .optionalParticipant)
        .repeatsWeekly(every: 1, on: [.monday, .tuesday, .wednesday, .thursday, .friday])
        .confirmed()
        // Multiple alarms for recurring events
        .addAlarms([
            .display(description: "Standup in 10 minutes"),
            .display(description: "Standup starting now"),
            .audio()
        ], triggers: [
            .minutesBefore(10),
            .minutesBefore(1),
            .eventStart
        ])
        .buildEvent()

    // 3. Project Deadline with Escalating Notifications
    let projectDeadline = EventBuilder(summary: "⚠️ Project Alpha Deadline")
        .starts(at: Calendar.current.date(byAdding: .weekOfYear, value: 2, to: Date())!, timeZone: .current)
        .duration(0)  // All-day deadline
        .description("Final deadline for Project Alpha deliverables")
        .organizer(email: "[email protected]", name: "Project Manager")
        .addAttendee(email: "[email protected]", name: "Development Team", role: .requiredParticipant, userType: .group)
        .highPriority()
        .confirmed()
        // Escalating deadline reminders
        .addAlarm(.email(
            description: "Project Alpha deadline in 1 week - please ensure all deliverables are on track",
            summary: "Project Deadline - 1 Week Warning",
            to: manager
        ), trigger: .minutesBefore(10080))  // 1 week
        .addAlarm(.display(description: "Project Alpha deadline in 3 days"), trigger: .minutesBefore(4320))  // 3 days
        .addAlarm(.display(description: "Project Alpha deadline tomorrow"), trigger: .minutesBefore(1440))  // 1 day
        .addAlarm(.display(description: "Project Alpha deadline in 2 hours"), trigger: .minutesBefore(120))  // 2 hours
        .buildEvent()

    // Add timezone components with automatic TZURL generation (RFC 7808)
    if let estTimeZone = TimeZoneRegistry.shared.getTimeZone(for: "America/New_York") {
        calendar.addTimeZone(estTimeZone)
        // Automatically includes: TZURL:http://tzurl.org/zoneinfo-outlook/America/New_York
    }
    
    if let utcTimeZone = TimeZoneRegistry.shared.getTimeZone(for: "UTC") {
        calendar.addTimeZone(utcTimeZone)
        // Automatically includes: TZURL:http://tzurl.org/zoneinfo-outlook/UTC
    }
    
    // Add events to calendar
    calendar.addEvent(executiveMeeting)
    calendar.addEvent(dailyStandup)
    calendar.addEvent(projectDeadline)
    
    // Serialize with validation
    return try ICalendarSerializer().serialize(calendar)
}

// Helper function
private func nextMondayAt9AM() -> Date {
    let calendar = Calendar.current
    let now = Date()
    let nextMonday = calendar.nextDate(after: now, matching: DateComponents(weekday: 2), matchingPolicy: .nextTime)!
    return calendar.date(bySettingHour: 9, minute: 0, second: 0, of: nextMonday)!
}

// Usage
do {
    let enterpriseCalendar = try createEnterpriseCalendar()
    print("Generated enterprise calendar with maximum compatibility:")
    print("- RFC 7986 + X-WR legacy properties")
    print("- Type-safe alarms with RFC 5545 compliance")
    print("- Automatic TZURL generation for timezone updates")
    print("- Works in all major calendar applications")

    // Save to file
    try enterpriseCalendar.write(to: URL(fileURLWithPath: "enterprise_calendar.ics"), atomically: true, encoding: .utf8)
} catch {
    print("Error creating calendar: \(error)")
}

Error Handling

do {
    let calendar = try client.parseCalendar(from: icalContent)
    let serialized = try client.serializeCalendar(calendar)
} catch ICalendarError.invalidFormat(let message) {
    print("Invalid format: \(message)")
} catch ICalendarError.missingRequiredProperty(let property) {
    print("Missing required property: \(property)")
} catch ICalendarError.invalidPropertyValue(let property, let value) {
    print("Invalid value '\(value)' for property '\(property)'")
} catch {
    print("Unexpected error: \(error)")
}

Validation

// Validate a calendar
try client.validateCalendar(calendar)

// Validate email addresses
ValidationUtilities.isValidEmail("[email protected]") // true

// Validate other properties
ValidationUtilities.isValidPriority(5) // true (0-9)
ValidationUtilities.isValidPercentComplete(75) // true (0-100)

Platform-Specific Formatting

// Outlook-compatible format
let outlookFormat = serializer.serializeForOutlook(calendar)

// Google Calendar format
let googleFormat = serializer.serializeForGoogle(calendar)

// Pretty-printed format for debugging
let prettyFormat = serializer.serializePretty(calendar)

Statistics and Analysis

let stats = client.getCalendarStatistics(calendar)
print(stats.description)
// Output:
// Calendar Statistics:
// - Events: 5
// - Todos: 3
// - Journals: 1
// - Time Zones: 2
// - Total Attendees: 12
// - Events with Alarms: 4
// - Recurring Events: 2

RFC 7986 and X-WR Extensions

This library includes comprehensive support for RFC 7986 extensions and popular X-WR (Apple/CalDAV) extensions:

RFC 7986 Properties

Calendar-Level Properties

// Create calendar with RFC 7986 properties
var calendar = ICalendar(productId: "-//My Company//My Calendar//EN")

// RFC 7986 calendar properties
calendar.name = "My Calendar"
calendar.calendarDescription = "Personal events and appointments"

// Add RFC 7986 extended properties
calendar.properties.append(contentsOf: [
    ICalProperty(name: "COLOR", value: "blue"),
    ICalProperty(name: "REFRESH-INTERVAL", value: "PT1H"),
    ICalProperty(name: "SOURCE", value: "https://example.com/calendar.ics"),
    ICalProperty(name: "IMAGE", value: "https://example.com/calendar-icon.png")
])

Event Extensions

// Create event with RFC 7986 extensions
let conference = EventBuilder(summary: "International Conference 2024")
    .starts(at: Date(), timeZone: TimeZone.current)
    .duration(28800) // 8 hours
    .location("Convention Center")
    .description("Annual technology conference")
    .categories("Conference", "Technology")
    .confirmed()
    .buildEvent()

// Add RFC 7986 extended properties to the event
conference.properties.append(contentsOf: [
    ICalProperty(name: "COLOR", value: "red"),
    ICalProperty(name: "GEO", value: "37.7749;-122.4194"),
    ICalProperty(name: "IMAGE", value: "https://conference.com/logo.png"),
    ICalProperty(name: "CONFERENCE", value: "https://meet.google.com/conference-room")
])

calendar.addEvent(conference)

Todo and Journal Extensions

// Todo with RFC 7986 extensions
var todo = ICalTodo()
todo.summary = "Design review"
todo.dueDate = ICalDateTime(date: Date().addingTimeInterval(604800)) // 1 week
todo.properties.append(contentsOf: [
    ICalProperty(name: "COLOR", value: "purple"),
    ICalProperty(name: "IMAGE", value: "https://company.com/design-spec.pdf"),
    ICalProperty(name: "CONFERENCE", value: "https://company.slack.com/channels/design")
])

// Journal with extensions
var journal = ICalJournal()
journal.summary = "Trip notes"
journal.description = "Notes from business trip to New York"
journal.dateTimeStart = ICalDateTime(date: Date())
journal.properties.append(contentsOf: [
    ICalProperty(name: "COLOR", value: "green"),
    ICalProperty(name: "GEO", value: "40.7128;-74.0060"),
    ICalProperty(name: "IMAGE", value: "https://photos.com/trip-photo.jpg")
])

calendar.addTodo(todo)
calendar.addJournal(journal)

X-WR Extensions (Apple/CalDAV)

// Create calendar with X-WR extensions (Apple/CalDAV)
var calendar = ICalendar(productId: "-//My Company//Work Calendar//EN")

// X-WR calendar extensions
calendar.properties.append(contentsOf: [
    ICalProperty(name: "X-WR-CALNAME", value: "Work Calendar"),
    ICalProperty(name: "X-WR-CALDESC", value: "Work-related events"),
    ICalProperty(name: "X-WR-TIMEZONE", value: "America/New_York"),
    ICalProperty(name: "X-WR-RELCALID", value: "parent-cal-123"),
    ICalProperty(name: "X-PUBLISHED-TTL", value: "PT1H")
])

Enhanced Calendar Creation

// Create comprehensive calendar with all extensions
var extendedCalendar = ICalendar(productId: "-//Corporate//Extended Calendar//EN")
extendedCalendar.name = "Extended Calendar"
extendedCalendar.calendarDescription = "Full-featured calendar"

// Add comprehensive properties
extendedCalendar.properties.append(contentsOf: [
    // RFC 7986 properties
    ICalProperty(name: "COLOR", value: "corporate-blue"),
    ICalProperty(name: "REFRESH-INTERVAL", value: "P1D"),
    ICalProperty(name: "SOURCE", value: "https://api.company.com/calendar.ics"),

    // X-WR extensions
    ICalProperty(name: "X-WR-CALNAME", value: "Corp Cal"),
    ICalProperty(name: "X-WR-TIMEZONE", value: "America/Los_Angeles"),
    ICalProperty(name: "METHOD", value: "PUBLISH")
])

Timezone Support

The library provides comprehensive timezone support:

// Add timezone components to calendar
if let nyTimeZone = TimeZoneRegistry.shared.getTimeZone(for: "America/New_York") {
    calendar.addTimeZone(nyTimeZone)
}

if let londonTimeZone = TimeZoneRegistry.shared.getTimeZone(for: "Europe/London") {
    calendar.addTimeZone(londonTimeZone)
}

// Create events with specific timezones
let nyEvent = EventBuilder(summary: "New York Meeting")
    .starts(at: Date(), timeZone: TimeZone(identifier: "America/New_York")!)
    .duration(3600)
    .buildEvent()

let londonEvent = EventBuilder(summary: "London Conference Call")
    .starts(at: Date().addingTimeInterval(3600), timeZone: TimeZone(identifier: "Europe/London")!)
    .duration(1800)
    .buildEvent()

calendar.addEvent(nyEvent)
calendar.addEvent(londonEvent)

Enhanced ATTACH Property Support

The library supports both URI references and base64-encoded binary attachments:

// Create event with attachments
let presentationEvent = EventBuilder(summary: "Quarterly Presentation")
    .starts(at: Date(), timeZone: TimeZone.current)
    .duration(5400) // 90 minutes
    .location("Board Room")
    .buildEvent()

// Add URI attachment
let uriAttachment = ICalAttachment()
uriAttachment.uri = "https://example.com/presentation.pdf"
uriAttachment.parameters["FMTTYPE"] = "application/pdf"
presentationEvent.attachments.append(uriAttachment)

// Add binary attachment with base64 encoding (if you have image data)
let imageData = Data(/* your image data */)
let binaryAttachment = ICalAttachment()
binaryAttachment.binaryData = imageData
binaryAttachment.parameters["ENCODING"] = "BASE64"
binaryAttachment.parameters["FMTTYPE"] = "image/png"
presentationEvent.attachments.append(binaryAttachment)

calendar.addEvent(presentationEvent)

Utility Methods

// Geographic coordinates helper
extension ICalProperty {
    static func geo(latitude: Double, longitude: Double) -> ICalProperty {
        return ICalProperty(name: "GEO", value: "\(latitude);\(longitude)")
    }

    static func refreshInterval(hours: Int) -> ICalProperty {
        return ICalProperty(name: "REFRESH-INTERVAL", value: "PT\(hours)H")
    }
}

// Usage examples
let locationEvent = EventBuilder(summary: "San Francisco Meetup")
    .starts(at: Date(), timeZone: TimeZone.current)
    .duration(7200)
    .location("Golden Gate Park")
    .buildEvent()

// Add geographic coordinates
locationEvent.properties.append(.geo(latitude: 37.7749, longitude: -122.4194))

// Add refresh interval to calendar
calendar.properties.append(.refreshInterval(hours: 2))

calendar.addEvent(locationEvent)

Advanced RFC Features

This section showcases the comprehensive RFC implementation capabilities:

πŸš€ Multi-RFC Integration

iCalendar Kit integrates features from multiple RFCs for enterprise functionality:

import ICalendar

// RFC 5545 + RFC 7986 Integration
var calendar = ICalendar(productId: "-//Corporate//Events//EN")

// RFC 5545: Core calendar properties
calendar.name = "Corporate Events"
calendar.calendarDescription = "Company-wide events and meetings"
calendar.method = "PUBLISH"

// RFC 7986: Enhanced properties
calendar.properties.append(contentsOf: [
    ICalProperty(name: "COLOR", value: "blue"),
    ICalProperty(name: "REFRESH-INTERVAL", value: "PT1H"),
    ICalProperty(name: "SOURCE", value: "https://company.com/calendar.ics")
])

// Create enhanced event
let conference = EventBuilder(summary: "International Conference 2024")
    .starts(at: Date(), timeZone: TimeZone.current)
    .duration(7200) // 2 hours
    .location("Convention Center")
    .description("Annual technology conference")
    .categories("Conference", "Technology")
    .confirmed()
    .buildEvent()

// Add RFC 7986 enhancements
conference.properties.append(contentsOf: [
    ICalProperty(name: "IMAGE", value: "https://company.com/event-banner.jpg"),
    ICalProperty(name: "GEO", value: "40.7589;-73.9851"),
    ICalProperty(name: "CONFERENCE", value: "https://zoom.us/j/123456789")
])

calendar.addEvent(conference)

πŸ“Š Calendar Analytics

Built-in analysis using RFC-compliant properties:

// Calendar statistics
extension ICalendar {
    var eventCount: Int { events.count }
    var todoCount: Int { todos.count }
    var recurringEventCount: Int {
        events.filter { $0.recurrenceRule != nil }.count
    }

    func eventsByCategory() -> [String: [ICalEvent]] {
        Dictionary(grouping: events) { event in
            event.categories.first ?? "Uncategorized"
        }
    }

    func eventsByLocation() -> [String: [ICalEvent]] {
        Dictionary(grouping: events) { event in
            event.location ?? "No Location"
        }
    }
}

// Usage
print("Total Events: \(calendar.eventCount)")
print("Recurring Events: \(calendar.recurringEventCount)")

let categoryStats = calendar.eventsByCategory()
for (category, events) in categoryStats {
    print("Category: \(category) - Events: \(events.count)")
}

🌐 Advanced Timezone Support

Comprehensive timezone handling with multiple zones:

// Multiple timezone calendar
var globalCalendar = ICalendar(productId: "-//Global Corp//Multi-TZ//EN")

// Add timezone components
let timezones = ["America/New_York", "Europe/London", "Asia/Tokyo"]
for tzId in timezones {
    if let tz = TimeZoneRegistry.shared.getTimeZone(for: tzId) {
        globalCalendar.addTimeZone(tz)
    }
}

// Create events across timezones
let nycEvent = EventBuilder(summary: "NYC Team Meeting")
    .starts(at: Date(), timeZone: TimeZone(identifier: "America/New_York")!)
    .duration(3600)
    .organizer(email: "[email protected]", name: "NYC Team")
    .buildEvent()

let londonEvent = EventBuilder(summary: "London Strategy Session")
    .starts(at: Date().addingTimeInterval(3600), timeZone: TimeZone(identifier: "Europe/London")!)
    .duration(5400) // 90 minutes
    .organizer(email: "[email protected]", name: "London Team")
    .buildEvent()

globalCalendar.addEvent(nycEvent)
globalCalendar.addEvent(londonEvent)

🎯 Advanced Recurrence Patterns

Complex recurrence handling with proper RRULE generation:

// Quarterly business reviews (first Monday of quarter)
let quarterlyReview = EventBuilder(summary: "Quarterly Business Review")
    .starts(at: Date(), timeZone: TimeZone.current)
    .duration(10800) // 3 hours
    .location("Executive Conference Room")
    .description("Quarterly review meeting")
    .organizer(email: "[email protected]", name: "Executive Team")
    .categories("Business", "Review")
    .highPriority()
    .confirmed()
    .buildEvent()

// Custom recurrence rule - every 3 months on first Monday
quarterlyReview.recurrenceRule = ICalRecurrenceRule(
    frequency: .monthly,
    interval: 3,
    byWeekday: [.monday],
    bySetpos: [1]
)

// Holiday schedule with exceptions
let holidayEvent = EventBuilder(summary: "Company Holiday")
    .starts(at: Date(), timeZone: TimeZone.current)
    .duration(86400) // All day
    .description("Company-wide holiday")
    .categories("Holiday")
    .buildEvent()

// Yearly recurrence
holidayEvent.recurrenceRule = ICalRecurrenceRule(
    frequency: .yearly,
    byMonth: [12],
    byMonthday: [25]
)

calendar.addEvent(quarterlyReview)
calendar.addEvent(holidayEvent)

πŸ“± Serialization and Export

Multiple export formats with full RFC compliance:

// Standard iCalendar format (RFC 5545)
let serializer = ICalendarSerializer()
let icsString = try serializer.serialize(calendar)
let icsData = icsString.data(using: .utf8)!

// Save to file
try icsData.write(to: URL(fileURLWithPath: "calendar.ics"))

// Different serialization options
let compactSerializer = ICalendarSerializer(options: .compact)
let compactICS = try compactSerializer.serialize(calendar)

// Platform-specific formatting
let outlookSerializer = ICalendarSerializer()
let outlookICS = outlookSerializer.serializeForOutlook(calendar)

let googleICS = outlookSerializer.serializeForGoogle(calendar)

// Statistics
let stats = serializer.getStatistics(calendar)
print("Generated \(stats.totalLines) lines")
print("Total size: \(stats.totalCharacters) characters")

πŸ” Validation and Security

Built-in validation for RFC compliance and security:

// Calendar validation using the parser
let parser = ICalendarParser()

do {
    try parser.validate(calendar)
    print("Calendar is RFC 5545 compliant")
} catch {
    print("Validation error: \(error)")
}

// Content sanitization helpers
extension String {
    func sanitized() -> String {
        // Remove potential script content
        return self
            .replacingOccurrences(of: "<script", with: "&lt;script", options: .caseInsensitive)
            .replacingOccurrences(of: "javascript:", with: "", options: .caseInsensitive)
    }
}

// Secure event creation
let secureEvent = EventBuilder(summary: "Secure Meeting".sanitized())
    .description("Meeting description".sanitized())
    .location("Conference Room A".sanitized())
    .starts(at: Date(), timeZone: TimeZone.current)
    .duration(3600)
    .buildEvent()

// Validate serialized output
let serializer = ICalendarSerializer(options: .init(validateBeforeSerializing: true))
let safeICS = try serializer.serialize(calendar)

RFC Compliance

This library provides comprehensive support for all major iCalendar specifications, making it one of the most complete iCalendar implementations available for Swift:

βœ… Fully Implemented RFCs

Core iCalendar Specifications

  • RFC 5545 - Internet Calendaring and Scheduling Core Object Specification (iCalendar)

    • Complete VCALENDAR, VEVENT, VTODO, VJOURNAL, VALARM, VTIMEZONE components
    • All standard properties with proper validation and formatting
    • Full recurrence rules (RRULE) with all BY* parameters
    • ATTACH property with both URI and base64 binary support
    • Comprehensive timezone definitions and references
  • RFC 7986 - New Properties for iCalendar

    • NAME, COLOR, IMAGE, SOURCE, REFRESH-INTERVAL properties
    • CONFERENCE property for video/audio meeting integration
    • Enhanced calendar-level DESCRIPTION, UID, LAST-MODIFIED
    • DISPLAY, EMAIL, FEATURE, LABEL parameters
  • RFC 6868 - Parameter Value Encoding in iCalendar and vCard

    • Complete parameter value escaping and encoding
    • Support for special characters, newlines, and quotes in parameters
  • RFC 7808 - Time Zone Data Distribution Service

    • TZURL property with automatic generation for timezone identifiers
    • Full integration with tzurl.org service URLs
    • Foundation TimeZone integration with round-trip compatibility
    • X-LIC-LOCATION support for Lotus Notes compatibility

Enhanced Protocol Support

  • RFC 5546 - iCalendar Transport-Independent Interoperability Protocol (iTIP)
    • βœ… Complete METHOD support: REQUEST, REPLY, CANCEL, PUBLISH, COUNTER, DECLINECOUNTER, REFRESH
    • βœ… Full iTIP workflow validation with proper message structure validation
    • βœ… Advanced ATTENDEE handling with DELEGATED-FROM/DELEGATED-TO parameters
    • βœ… Automatic SEQUENCE handling and incrementation for updates
    • βœ… Meeting workflow helpers: Accept/decline invitations, counter-proposals, refresh requests
    • βœ… Participation status management with proper PARTSTAT updates

Advanced Calendar Features

  • RFC 7529 - Non-Gregorian Recurrence Rules

    • βœ… RSCALE parameter for Hebrew, Islamic, Chinese, Buddhist, Japanese, Persian, Indian, Coptic, and Ethiopic calendars
    • βœ… Foundation Calendar integration leveraging Apple's comprehensive calendar system
    • βœ… Non-Gregorian recurrence calculations with proper date handling
    • Impact: Essential for international and religious calendar applications
  • RFC 9074 - VALARM Extensions for iCalendar

    • βœ… PROXIMITY-TRIGGER for location-based alarms (entering/leaving areas)
    • βœ… ACKNOWLEDGED property for alarm acknowledgment tracking
    • βœ… RELATED-TO support for alarm relationships and dependencies
    • βœ… Enhanced alarm actions with proximity-based triggers
    • Impact: Modern mobile alarm management with location awareness
  • RFC 9073 - Event Publishing Extensions

    • βœ… STRUCTURED-DATA property for JSON/XML metadata embedding
    • βœ… VVENUE component for structured venue information
    • βœ… VLOCATION component for enhanced location details
    • βœ… VRESOURCE component for equipment and resource management
    • βœ… Rich metadata support for modern event publishing
    • Impact: Social media integration, rich event data, resource booking
  • RFC 9253 - Support for iCalendar Relationships

    • βœ… Enhanced RELATED-TO types: FINISHTOSTART, FINISHTOFINISH, STARTTOSTART, STARTTOFINISH, DEPENDSON, BLOCKS
    • βœ… LINK property for external resource references with full parameter support
    • βœ… CONCEPT property for semantic categorization and tagging
    • βœ… REFID property for reference identifiers and component grouping
    • Impact: Advanced project management, task dependencies, semantic organization

Availability and Transport

  • RFC 7953 - Calendar Availability

    • βœ… VAVAILABILITY component for publishing availability information
    • βœ… AVAILABLE/BUSY components for detailed time slot definitions
    • βœ… Free/busy time management with comprehensive period support
    • βœ… Integration with scheduling systems
    • Impact: Meeting room booking, scheduling systems, availability publishing
  • RFC 6047 - iCalendar Message-Based Interoperability Protocol (iMIP)

    • βœ… Email transport bindings for iTIP message delivery
    • βœ… MIME structure generation with proper content types
    • βœ… Automatic email subject generation based on iTIP methods
    • βœ… Message-ID and threading support for conversation tracking
    • Impact: Email-based calendar invitation systems

πŸ”„ Extension Support

  • Apple/CalDAV Extensions (Complete)
    • X-WR-CALNAME, X-WR-CALDESC, X-WR-TIMEZONE
    • X-WR-RELCALID, X-PUBLISHED-TTL
    • X-LIC-LOCATION for Lotus Notes compatibility

πŸ—οΈ Architecture Highlights

  • Swift 6 Sendable Compliance - Full structured concurrency support
  • Foundation Integration - Leverages TimeZone, Calendar, and Date APIs
  • Zero Code Duplication - Reuses Apple's robust calendar implementations
  • Round-trip Compatibility - Serialize β†’ Parse β†’ Serialize produces identical output
  • Comprehensive Testing - 100% test coverage with complex integration scenarios

πŸ§ͺ Compliance Testing

The library includes comprehensive test suites with 100% coverage validating:

Core Infrastructure Testing

  • Timezone handling accuracy - Critical timezone parsing, serialization, and TZID parameter support
  • Round-trip serialization fidelity - Ensures no data loss during parse/serialize cycles
  • Property parameter encoding - Proper escaping of special characters per RFC 6868
  • Binary data handling - Base64 encoding/decoding with MIME type support

RFC-Specific Validation

  • RFC 5545 compliance - All component structures, properties, and validation rules
  • RFC 7529 non-Gregorian calendars - Hebrew, Islamic, Chinese calendar recurrence calculations
  • RFC 5546 iTIP workflows - Complete message validation for all iTIP methods
  • RFC 9074 advanced alarms - Proximity triggers and acknowledgment tracking
  • RFC 9073 event publishing - Structured data, venue, and resource component handling
  • RFC 9253 relationships - Enhanced relationship types and external linking
  • RFC 7953 availability - Free/busy time management and scheduling
  • RFC 6047 iMIP transport - Email binding and MIME structure generation

Integration Testing

  • Complex multi-RFC scenarios - Events combining multiple RFC features
  • Real-world usage patterns - Meeting invitations, resource booking, international calendars
  • Error handling and edge cases - Malformed input, missing properties, timezone edge cases

Performance and Reliability

  • Memory efficiency - Optimized for server-side Swift applications
  • Concurrency safety - Full Swift 6 structured concurrency support
  • Large calendar handling - Efficient parsing and serialization of complex calendars

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass: swift test
  5. Submit a pull request

RFC Enhancement Contributions Welcome! If you'd like to implement support for additional RFCs, please:

  1. Open an issue to discuss the scope
  2. Reference the specific RFC sections
  3. Include comprehensive test coverage
  4. Follow existing architectural patterns

License

This project is licensed under the MIT License. See LICENSE for details.

Support