Project 15: iCalendar Availability and Conflict Detector

Parse a deliberately bounded iCalendar subset, normalize instants correctly, expose unsupported constructs, and find conflicts and meeting windows without lying about time.

Quick Reference

Attribute Value
Difficulty Level 1 - Beginner/Tinkerer
Suggested Seniority Junior
Time Estimate 8-12 hours
Main Programming Language Elixir
Alternative Programming Languages Erlang, Gleam
Coolness Level Level 3 - Genuinely Clever
Business Potential Level 2 - Micro-SaaS/Pro Tool
Prerequisites Basic pattern matching, strings, lists, sorting
Key Topics Date, Time, NaiveDateTime, DateTime, Calendar, interval algorithms, tagged errors

1. Learning Objectives

By completing this project, you will:

  1. Model calendar values explicitly instead of treating all date-time text as interchangeable strings.
  2. Parse a documented RFC 5545 subset with line unfolding and property parameters while reporting unsupported recurrence and timezone forms.
  3. Normalize comparable instants, preserve all-day events as a distinct domain concept, and detect overlap with clear boundary semantics.
  4. Merge busy intervals and subtract them from working windows to produce useful availability.
  5. Separate standards parsing, temporal policy, and terminal rendering so each can be tested independently.

2. All Theory Needed (Per-Concept Breakdown)

Temporal Types and Interval Reasoning

Fundamentals

Time-related bugs often begin when distinct concepts share one representation. Elixir intentionally provides Date, Time, NaiveDateTime, and DateTime because a calendar date, wall-clock time, local date-time, and globally comparable instant are not equivalent. DateTime also depends on a time-zone database for named-zone conversions; the standard library does not silently invent daylight-saving rules. An iCalendar event can contain a UTC value, a floating local value, a value tied to a TZID parameter, or a date-only all-day value. A safe beginner project declares which forms it supports and returns structured unsupported errors for the rest. Once events become normalized intervals, overlap is a precise ordering problem. Using half-open intervals, where the start is included and the end excluded, allows an event ending at 10:00 and another starting at 10:00 to coexist without conflict.

Deep Dive into the concept

The first design task is to distinguish syntax from meaning. RFC 5545 content lines contain a property name, optional semicolon-separated parameters, a colon, and a value. Long logical lines may be folded across physical lines; a continuation starts with whitespace and must be unfolded before property parsing. Parsing text into a generic property record is only the first layer. Interpreting DTSTART, DTEND, UID, SUMMARY, and TZID belongs in an event decoder that can produce domain events or explicit diagnostics.

An instant answers “when on the global timeline?” A naive date-time answers “what wall-clock fields?” but cannot by itself distinguish 09:00 in Curitiba from 09:00 in Berlin. UTC values ending in Z can become DateTime values directly. A TZID local value requires a configured time-zone database and must confront ambiguous or nonexistent local times around daylight-saving transitions. A floating time has no zone; the CLI must require a policy such as “interpret floating values in America/Sao_Paulo” or reject them. Silently using the computer’s local zone makes results non-reproducible.

All-day events use date values and are not simply midnight UTC. Their meaning follows calendar dates in an attendee’s context. Keep them as all-day intervals until a chosen reporting zone and working-day policy converts them into busy windows. RFC end values are commonly exclusive, which aligns with half-open intervals. A one-day event beginning July 15 and ending July 16 occupies July 15. If the learner instead subtracts one second from end values, boundary math becomes fragile.

Normalize timed events into one comparison zone, usually UTC, but retain original value and zone metadata for explanations. Define an interval struct with start instant, exclusive end instant, source calendar, UID, summary, and flags. Validation must reject end-before-start and decide whether zero-duration events block time. The overlap rule for half-open intervals is: left starts before right ends and right starts before left ends. This avoids a large tree of special cases.

Conflict detection begins by sorting intervals by start then end. A simple adjacent comparison is insufficient when one long event contains several shorter events; maintain an active interval or sweep through start/end boundaries. For a beginner scope, sorting and carrying the interval with the greatest end is enough to detect whether a new interval overlaps any active busy range, while preserving pairs for explanations. For availability, merge touching or overlapping busy intervals according to policy, clip them to the requested working window, then emit gaps between the cursor and each merged interval.

Travel buffers expand intervals before conflict testing. A 15-minute buffer can extend the effective end of one event, the effective start of the next, or both depending on policy. Keep original and effective intervals separate so reports can say “no direct overlap; 10-minute travel shortfall.” Do not mutate the event or lose its true timestamps.

Recurrence is the major scope hazard. RRULE, EXDATE, RDATE, RECURRENCE-ID, and VTIMEZONE form a substantial subsystem. This project must detect these constructs and report them as unsupported rather than ignore them. Ignoring recurrence would claim a recurring meeting is absent and generate false availability—the worst possible failure for a scheduling tool.

The invariants are: every accepted timed event has a comparable start before an exclusive end; unsupported semantics never become accepted empty data; timezone assumptions are explicit in the run manifest; sorting and output are deterministic; generated free windows never overlap a busy interval; direct conflicts and buffer violations remain distinguishable. Failure modes include comparing naive values from different zones, mishandling DST ambiguity, treating all-day events as UTC midnights, ignoring line folding, accepting recurrence without expansion, and using inclusive ends that produce false conflicts at boundaries.

How this fits in the project

Temporal types define the parser’s output, interval algebra powers conflict and availability calculations, and tagged diagnostics make the supported RFC subset honest.

Definitions & key terms

  • Instant: A point globally comparable on a timeline.
  • Floating time: Local date-time with no timezone association.
  • TZID: iCalendar parameter naming the timezone for a local value.
  • Half-open interval: Range including its start but excluding its end.
  • Line unfolding: Reconstructing one logical content line from RFC-folded physical lines.
  • Busy union: Merged, non-overlapping intervals that cover all busy time.

Mental model diagram

calendar A.ics ----\
                    -> unfold -> parse properties -> decode events
calendar B.ics ----/                              |
                                                  v
                              timezone policy -> normalized intervals
                                                  |
                      +---------------------------+------------------+
                      v                                              v
                overlap sweep                                 busy union
                      |                                              |
                      v                                              v
            conflict explanations                   working window - busy
                                                                     |
                                                                     v
                                                           available slots

How it works

  1. Unfold physical lines into logical content lines.
  2. Parse property names, parameters, and escaped values.
  3. Collect VEVENT properties and reject malformed component nesting.
  4. Decode supported date/date-time forms using explicit timezone policy.
  5. Validate and sort half-open intervals.
  6. Detect direct overlaps and expanded travel-buffer violations.
  7. Merge busy intervals, clip to working hours, and generate gaps.
  8. Render deterministic output plus warnings and unsupported constructs.

Minimal concrete example

event A: [09:00, 10:00)
event B: [10:00, 10:30)
direct overlap? false

with 15-minute travel buffer after A:
effective A: [09:00, 10:15)
buffer violation with B? true, shortfall=15 minutes

Common misconceptions

  • NaiveDateTime is not “DateTime without formatting”; it lacks timezone meaning.
  • UTC normalization does not remove the need to retain source-zone context.
  • Events that touch at an exclusive endpoint do not overlap.
  • Ignoring RRULE is not a partial success; it is a potentially false schedule.

Check-your-understanding questions

  1. Why should floating times require an explicit CLI policy?
  2. Why are half-open intervals useful for adjacent meetings?
  3. Why must recurrence produce an unsupported diagnostic?

Check-your-understanding answers

  1. The same wall-clock value maps to different instants in different zones, and host-local defaults break reproducibility.
  2. End-at-10 and start-at-10 do not share an instant, eliminating false boundary conflicts.
  3. Silently ignoring recurrence would omit real events and advertise invalid free time.

Real-world applications

Scheduling assistants, booking validators, room calendars, workforce planning, service-window checks, and SLA blackout calculations all use temporal normalization and interval algebra.

Where you will apply it

You will use it in property decoding, timezone policy, event validation, conflict detection, buffer rules, and free-window generation. This does not overlap Projects 1-13: it is a finite standards-and-algorithms tool, not presence, notifications, LiveView, distributed state, telemetry, or backpressure.

References

Key insight

Reliable scheduling begins by representing temporal meaning explicitly and refusing to guess when input semantics are incomplete.

Summary

Calendar parsing becomes manageable when syntax, temporal interpretation, interval policy, and reporting remain separate. Elixir’s temporal structs and pattern matching make those boundaries visible.

Homework/Exercises

  1. Trace a UTC event and a floating event through normalization.
  2. Prove the half-open overlap predicate on touching and containing intervals.
  3. Design diagnostics for RRULE, unknown TZID, and end-before-start.

Solutions

  1. UTC is already an instant; the floating value needs a declared zone before conversion.
  2. Touching intervals fail one strict comparison; containing intervals satisfy both.
  3. Use stable reason codes with UID, property, line, and safe details; none should become accepted events.

3. Project Specification

3.1 What You Will Build

Build “calcheck”, a CLI accepting two or more ICS files, a reporting timezone, a date range, working hours, meeting duration, and travel buffer. It prints conflicts, unsupported constructs, and available meeting windows.

Scope boundary: Support unfolded VEVENT records with UID, SUMMARY, DTSTART, and DTEND plus a documented set of date-time forms. Detect but do not implement recurrence expansion, alarms, invitations, or full VTIMEZONE interpretation.

3.2 Functional Requirements

  1. Unfold RFC content lines and parse property parameters.
  2. Support UTC, declared floating-zone, date-only, and approved TZID values.
  3. Reject or flag RRULE, RDATE, EXDATE, RECURRENCE-ID, unknown zones, and malformed components.
  4. Detect direct overlaps separately from travel-buffer violations.
  5. Generate slots of a requested duration within working windows.
  6. Preserve source file, logical line, UID, and original temporal text in diagnostics.

3.3 Non-Functional Requirements

  • Identical files, range, timezone database, and options produce identical output.
  • Parser failures never crash the whole command.
  • Output states the supported subset and timezone assumptions.
  • A 10,000-event fixture completes within a measured learner-defined budget.

3.4 Example Usage / Output

$ mix calcheck team.ics customer.ics --from 2026-07-15 --days 2 \
    --zone America/Sao_Paulo --work 09:00-18:00 --duration 45 --travel 15
events=12 accepted=10 unsupported=2
direct_conflicts=1 travel_violations=1
CONFLICT 2026-07-15 14:00-14:30 "Design Review" <> "Customer Call"
BUFFER   2026-07-16 09:00 shortfall=10m after "Airport Transfer"
AVAILABLE
  2026-07-15 10:15-11:00 America/Sao_Paulo
  2026-07-16 15:30-16:15 America/Sao_Paulo
status=review_required

3.5 Data Formats / Schemas / Protocols

Represent generic content lines, decoded events, normalized timed intervals, all-day intervals, conflict evidence, availability windows, and diagnostics as separate structs. A run manifest records zone, date range, buffer policy, supported subset version, and timezone database identifier.

3.6 Edge Cases

  • Folded summaries, escaped commas/newlines, CRLF, and missing final newline.
  • Adjacent events, zero-duration events, containment, and exact duplicates.
  • All-day events crossing the query boundary.
  • Ambiguous/nonexistent local times around DST.
  • DTSTART without DTEND, mixed VALUE=DATE and date-time fields, unknown TZID.

3.7 Real World Outcome

3.7.1 How to Run

Use small hand-auditable fixtures, set the reporting zone explicitly, run the command, then compare output with a manually drawn timeline.

3.7.2 Golden Path Demo

The golden fixture includes adjacent meetings, one direct overlap, one buffer-only violation, one all-day event, one UTC event, and two unsupported recurring events. Availability never crosses busy intervals.

3.7.3 Exact Terminal Transcript

$ mix calcheck fixtures/a.ics fixtures/b.ics --zone Etc/UTC --from 2026-07-15 --days 1
accepted=6 unsupported=2 direct_conflicts=1 travel_violations=1 slots=3
$ echo $?
2
$ mix calcheck fixtures/clean.ics --zone Etc/UTC --from 2026-07-15 --days 1
accepted=4 unsupported=0 direct_conflicts=0 travel_violations=0 slots=4
status=ok

4. Solution Architecture

4.1 High-Level Design

bytes -> line unfolder -> property parser -> VEVENT decoder
                                               |
                                  timezone policy/normalizer
                                               |
                        sorted intervals -> conflicts + busy union
                                                                |
                                                 working windows subtraction

4.2 Key Components

  • RFC line unfolder and content-line parser
  • VEVENT accumulator with source locations
  • Temporal decoder and timezone policy
  • Interval validator, overlap detector, and busy merger
  • Availability generator
  • Terminal renderer and diagnostic reporter

4.3 Data Structures

Use structs for content lines, events, intervals, conflicts, slots, diagnostics, and run policy. Store timed and all-day events separately until query-zone clipping.

4.4 Algorithm Overview

Parse files independently, accumulate explicit results, normalize accepted events, sort intervals, sweep for conflicts, expand copies for buffer checks, merge clipped busy intervals, and subtract them from per-day working windows.

5. Implementation Guide

5.1 Development Environment Setup

Use supported Elixir/OTP, configure an explicit Calendar.TimeZoneDatabase implementation if named zones are included, and add RFC-derived fixtures without personal calendar data.

5.2 Project Structure

lib/calcheck/
  content_line
  parser
  event_decoder
  temporal_policy
  interval
  availability
  cli
test/fixtures/

5.3 The Core Question You’re Answering

How can Elixir’s temporal types and explicit error values help a small scheduler produce honest answers from complicated calendar data?

5.4 Concepts You Must Understand First

Know binary/string splitting, structs, sorting comparators, Date versus DateTime, timezone database boundaries, half-open intervals, and tagged errors.

5.5 Questions to Guide Your Design

  1. Which RFC forms are accepted, rejected, or unsupported?
  2. What timezone assumption applies to floating values?
  3. Do touching busy intervals merge, and why?
  4. Does travel buffer block both sides or only after meetings?
  5. Which unsupported input changes the process exit status?

5.6 Thinking Exercise

Draw one day as a line and place four events: adjacent, overlapping, contained, and all-day. Expand two with travel buffers. Mark exact direct conflicts, buffer violations, and 45-minute gaps before writing the algorithm.

5.7 The Interview Questions They’ll Ask

  1. What is the difference between NaiveDateTime and DateTime?
  2. Why do named timezone conversions require a database?
  3. How does a half-open interval simplify overlap logic?
  4. How would you find availability efficiently after sorting?
  5. Why is silently ignoring recurrence dangerous?
  6. How would you test DST ambiguity without depending on the host clock?

5.8 Hints in Layers

Hint 1: Begin with UTC DTSTART/DTEND and no recurrence.

Hint 2: Parse generic content lines before interpreting VEVENT properties.

Hint 3: Normalize accepted timed events, then make interval functions unaware of ICS syntax.

Hint 4: Merge busy intervals before generating gaps; keep original events for explanations.

5.9 Books That Will Help

Topic Book Chapter
Elixir dates and data “Elixir in Action” by Saša Jurić Ch. 2, Building Blocks; Ch. 3, Control Flow
Functional modeling “Domain Modeling Made Functional” by Scott Wlaschin Ch. 4, Understanding Types; Ch. 6, Domain Integrity
Specification-driven design “Release It!, 2nd Edition” by Michael Nygard Ch. 4, Stability Antipatterns; Ch. 5, Stability Patterns

5.10 Implementation Phases

Phase 1: Foundation (3-4 hours)

Implement unfolding, content-line parsing, UTC/date-only event decoding, and source-aware diagnostics.

Phase 2: Core Functionality (3-5 hours)

Add timezone policy, interval validation, direct conflicts, buffers, busy merging, and free slots.

Phase 3: Polish & Edge Cases (2-3 hours)

Add unsupported recurrence detection, all-day clipping, DST fixtures, deterministic output, and CLI help.

5.11 Key Implementation Decisions

Document supported RFC properties, zone database, floating-time policy, end exclusivity, zero-duration handling, buffer direction, work windows, recurrence behavior, and exit codes.

6. Testing Strategy

6.1 Test Categories

  • Unfolding and escaped-value unit tests
  • Table-driven temporal decoding tests
  • Interval property tests for symmetry and boundaries
  • Golden conflict/availability fixtures
  • Unsupported-feature tests
  • Performance test on sorted and reverse-sorted large calendars

6.2 Critical Test Cases

  1. End-at-10 and start-at-10 are not a direct conflict.
  2. A containing interval conflicts with all contained events.
  3. Unknown TZID never falls back silently.
  4. RRULE produces an explicit unsupported record.
  5. Availability excludes direct busy and buffer-expanded intervals.
  6. Ambiguous DST local time follows the documented policy.

6.3 Test Data

Use one-property parser fixtures, hand-drawn daily timelines, RFC-inspired folded lines, fixed UTC instants, and synthetic DST cases. Never commit private calendar exports.

6.4 Definition of Done

  • The supported iCalendar subset is explicit.
  • Every unsupported construct is reported with source context.
  • Temporal forms are represented by appropriate Elixir types.
  • Direct conflicts and buffer violations are distinct.
  • Availability is verified against hand-drawn golden timelines.
  • Output records timezone assumptions and is deterministic.
  • Tests cover boundaries, containment, all-day events, and DST policy.

7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

False adjacent conflict: Inclusive end logic was used. Adopt half-open intervals consistently.

Events shift by hours: Floating or TZID values were treated as UTC. Trace original form, zone policy, and normalized instant.

Recurring meetings disappear: RRULE was ignored. Make unsupported recurrence visible and affect status.

Parser breaks on valid input: Folded lines were parsed before unfolding.

7.2 Debugging Strategies

Render normalized intervals beside original temporal text and UID. For a conflict, print the two predicates used by the overlap rule. For missing slots, print merged busy ranges after clipping to working hours.

7.3 Performance Traps

Comparing every event with every other event, repeatedly converting the same timezone values, and subtracting unmerged busy intervals create avoidable work. Normalize once, sort once, and merge once.

8. Extensions & Challenges

8.1 Beginner Extensions

Add JSON output, weekend exclusion, configurable slot granularity, and summary filtering.

8.2 Intermediate Extensions

Support multiple attendees, per-calendar travel buffers, holiday calendars, and FREEBUSY output for the documented subset.

8.3 Advanced Extensions

Introduce a recurrence library behind a behaviour, prove interval properties with StreamData, and compare timezone database versions in the run manifest.

9. Real-World Connections

9.1 Industry Applications

Meeting assistants, appointment marketplaces, resource booking, dispatch scheduling, workforce calendars, and maintenance-window planners all depend on honest temporal semantics.

Study RFC 5545 libraries and timezone database adapters for boundary choices. Treat them as references; the learning value is implementing and defending a bounded subset.

9.3 Interview Relevance

This project demonstrates specification reading, explicit scope control, Elixir temporal types, sorting-based algorithms, and the maturity to report unsupported semantics instead of producing false confidence.

10. Resources

10.1 Essential Reading

10.2 Official Documentation and Talks