A formal, machine-readable specification of inter-property dependencies for iCalendar VEVENT components, extracted from RFC 5545, RFC 7986, RFC 5546, RFC 6047, and RFC 6638.
RFC 5545 defines property interaction rules in prose scattered across hundreds of pages of MUST/SHOULD language. No published, standalone representation of these rules exists in a form suitable for machine consumption. This project extracts them into a formal dependency graph that enables:
- Property-level merge in CalDAV sync engines (beyond whole-event ETag conflicts)
- Validation of iCalendar objects at parse and edit time
- Correct RRULE expansion by making DTSTART dependencies explicit
CalDAV treats calendar resources as opaque blobs. When two clients modify the same event, the only options are whole-event replacement or manual resolution. Property-level merge requires knowing which properties depend on which -- and that knowledge exists only in RFC prose today.
The EXDATE-type-must-match-DTSTART rule alone accounts for 7+ separate bugs across different projects. The RRULE-DTSTART interaction has bugs dating back 13 years. We documented 25 bugs across 17 projects spanning 8 languages that trace to violated property dependencies.
| File | Format | Purpose |
|---|---|---|
rfc5545-deps.yaml |
YAML | Human-readable specification with inline commentary |
rfc5545-deps.json |
JSON | Machine-consumable equivalent for cross-language use |
RFC5545_DEPENDENCY_GRAPH.md |
Markdown | Full paper: prose rationale, evidence catalog, related work |
The YAML and JSON are structurally equivalent (27 properties, 20 edges, same merge safety classifications). The JSON uses _note fields for annotations and a _schema object documenting all field types. Fields prefixed with _ are non-normative.
import yaml
with open('rfc5545-deps.yaml') as f:
graph = yaml.safe_load(f)
# Which properties does RRULE depend on?
rrule = graph['properties']['RRULE']
for edge in rrule['depends_on']:
print(f"RRULE depends on {edge['target']} (strength: {edge['strength']})")
# Output: RRULE depends on DTSTART (strength: must)
# What is EXDATE's merge safety?
print(graph['properties']['EXDATE']['merge_safety']) # "dependent"
# Can SUMMARY be independently merged?
print(graph['properties']['SUMMARY']['merge_safety']) # "safe"| Edge Type | Meaning |
|---|---|
depends_on |
B's interpretation requires A. Changing A makes B's cached result stale. |
derived_from |
B's value was computed from A at creation time. A can change afterward. |
requires |
A requires B to be present. A without B is a protocol violation. |
mutually_exclusive_with |
A and B MUST NOT both be present. |
type_consistency |
A and B must use the same value type (DATE or DATE-TIME). Bidirectional. |
computes_with |
A and B are used together in computation. Neither needs updating when the other changes. |
| Category | Properties | Meaning |
|---|---|---|
| safe | SUMMARY, DESCRIPTION, LOCATION, URL, GEO, PRIORITY, CATEGORIES, COLOR, CLASS, TRANSP, STATUS | No cross-property dependencies. Independent merge always valid. |
| dependent | DTSTART, DTEND, DURATION, RRULE, EXDATE, RDATE, VALARM | Can be merged if graph validation passes. |
| scheduling | ATTENDEE, ORGANIZER, REQUEST-STATUS | Requires iTIP workflow (RFC 6638). See scheduling_fallback for non-RFC-6638 servers. |
| immutable | UID, CREATED, RECURRENCE-ID | Set once, never changed. |
| always_update | SEQUENCE, DTSTAMP, LAST-MODIFIED | Automatically set on any edit. |
| Strength | Meaning |
|---|---|
must |
RFC MUST/MUST NOT. Violation is a protocol error. |
should |
RFC SHOULD/SHOULD NOT. Suboptimal but conformant. |
advisory |
Not a conformance requirement. Useful for warnings. |
informational |
Annotation only. No conformance implication. |
- VEVENT only. VTODO and VJOURNAL have different dependency structures.
- Inter-property dependencies only. Single-property cardinality (DTSTART is REQUIRED) and value-level constraints (DTEND >= DTSTART) are not modeled.
- No conditional edges. VALARM TRIGGER's dependency on DTSTART vs DTEND depends on the RELATED parameter; this is noted in comments but not captured structurally.
- Cross-VEVENT edges (RECURRENCE-ID referencing master's RRULE/RDATE) are marked with
cross_vevent: true. A merge engine must operate on the full UID group.
The version field uses semantic versioning:
- Patch (1.0.x): Corrects an incorrect edge. No new properties or edge types.
- Minor (1.x.0): Adds new properties, edge types, or schema fields. Existing edges unchanged.
- Major (x.0.0): Changes existing edge semantics or restructures the schema.
- iCalDAV -- Kotlin CalDAV library. Reference implementation consumer of this graph.
- KashCal -- Android calendar app. Implementation validation across 6 CalDAV servers with 295+ RFC-specific tests.
See CONTRIBUTING.md.
Edge corrections and new properties should include:
- The RFC section number justifying the edge
- A test vector (input VEVENT + change + expected violation/result)
- Whether the edge affects merge safety classification
If you use this graph in academic work:
@misc{kashyap2026ical,
title = {Formalizing Implicit Property Dependencies in {RFC} 5545: Toward Safe Property-Level Calendar Synchronization},
author = {Kashyap, Ravi},
year = {2026},
doi = {10.5281/zenodo.19299690},
url = {https://doi.org/10.5281/zenodo.19299690},
note = {Preprint. Repository: https://github.com/iCalDAV/ical-dependency-graph}
}