Tempo.ICal (Tempo v0.20.0)

Copy Markdown View Source

Import iCalendar (RFC 5545) data into %Tempo.IntervalSet{}.

This module wraps the ical parser and translates each VEVENT into a %Tempo.Interval{} with full event metadata (summary, description, location, attendees, status, …) attached to the interval's :metadata map. The VCALENDAR envelope's metadata (product id, version, calendar scale, method) attaches to the IntervalSet's :metadata map.

Every pipeline step that accepts a Tempo value — set operations, Enum.take/2, resolution alignment — preserves the metadata through to the result. That lets free/busy and scheduling queries stay connected to their source events.

The ical dependency is declared optional: true in mix.exs. Tempo.ICal is only compiled when ical is available in the dependency tree; projects that don't import calendar data don't pay the compile cost.

Required reading

  • RFC 5545 — the iCalendar spec.
  • guides/set-operations.md for how the imported data is used downstream.

RFC 5545 coverage

PropertyStatus
RRULEFully supported — every BY* part, WKST, and
BYSETPOS flow through the interpreter.
RDATESupported — extra occurrences with the event's
own span.
EXDATESupported — start-moment match removes the
corresponding occurrence.
EXRULENot surfaced by the underlying ical library, so
not implementable at this layer. EXRULE is also
RFC-deprecated (RFC 2445 → 5545).
MultipleRFC 5545 says SHOULD NOT; some exports do it
RRULEanyway. The ical library exposes only the
perfirst RRULE on event.rrule, so we materialise
VEVENTthat one and silently ignore the rest.

Zoned, UTC, and floating times

A DATE-TIME value takes one of three RFC 5545 §3.3.5 forms, and each maps to a faithful %Tempo{}:

  • UTC (20220615T100000Z) → a zoned value with extended.zone_id: "Etc/UTC".
  • Zoned (TZID=Europe/Paris:20220615T100000) → a zoned value carrying that IANA zone.
  • Floating (20220615T100000, no Z and no TZID) → a genuinely zone-less value (extended: nil) — the same wall clock in whatever zone the reader is in.

Tempo supports ical ~> 2.0 or ~> 3.0. The zone-less mapping of floating times holds on ical 3.0+, which surfaces them as NaiveDateTime; on 2.x the parser coerced floating times to a zoned DateTime, so they arrive already anchored to a zone. Tempo maps whatever the installed parser produces — it does not itself invent a zone.

Summary

Functions

Parse an iCalendar string and return a %Tempo.IntervalSet{}.

Parse an iCalendar file and return a %Tempo.IntervalSet{}.

Functions

from_ical(ics, options \\ [])

@spec from_ical(
  binary(),
  keyword()
) :: {:ok, Tempo.IntervalSet.t()} | {:error, term()}

Parse an iCalendar string and return a %Tempo.IntervalSet{}.

Every VEVENT becomes one %Tempo.Interval{} in the result. All-day events (DTSTART as a Date) use day-resolution endpoints; datetime events use the matching datetime resolution. The event's SUMMARY, DESCRIPTION, LOCATION, UID, STATUS, TRANSPARENCY, CATEGORIES, attendees, and organizer all flow into the interval's :metadata map.

The calendar-level metadata (PRODID, VERSION, CALSCALE, METHOD, and the user-visible name from X-WR-CALNAME when present) attaches to the IntervalSet's :metadata map.

Arguments

  • ics is an iCalendar string (the contents of an .ics file).

Options

  • :bound — a Tempo.t/0, Tempo.Interval.t/0, or Tempo.IntervalSet.t/0 within which recurring events (those with an RRULE) are expanded. Required when any event in the input has a recurrence rule; ignored when there are none. An unbounded recurrence is infinite and refused at set-op time.

Returns

  • {:ok, interval_set} — sorted, coalesced IntervalSet of the events.
  • {:error, reason} when parsing fails or a recurring event requires a :bound that wasn't supplied.

Examples

iex> ics = """
...> BEGIN:VCALENDAR
...> VERSION:2.0
...> PRODID:-//Test//Test//EN
...> BEGIN:VEVENT
...> UID:test-1
...> DTSTAMP:20220101T000000Z
...> DTSTART:20220615T100000Z
...> DTEND:20220615T110000Z
...> SUMMARY:Test meeting
...> LOCATION:Paris
...> END:VEVENT
...> END:VCALENDAR
...> """
iex> {:ok, set} = Tempo.ICal.from_ical(ics)
iex> length(set.intervals)
1
iex> [iv] = set.intervals
iex> iv.metadata.summary
"Test meeting"
iex> iv.metadata.location
"Paris"

from_ical_file(path, options \\ [])

@spec from_ical_file(
  binary(),
  keyword()
) :: {:ok, Tempo.IntervalSet.t()} | {:error, term()}

Parse an iCalendar file and return a %Tempo.IntervalSet{}.

Wraps from_ical/2 with File.read/1.

Arguments

  • path is a path to an .ics file.

Options

See from_ical/2.

Returns

  • {:ok, interval_set} or {:error, reason}.