Tempo.ICal (Tempo v2.1.0)

Copy Markdown View Source

Reads iCalendar (RFC 5545) data into Tempo: each VEVENT an interval carrying the event's summary, location and the rest as metadata, each recurring event its occurrences, and RFC 7953 VAVAILABILITY components the time they offer.

The text is parsed by the optional ical dependency, and this module is compiled only when it is present. An event's RRULE is lowered to a Tempo recurrence by Tempo.RRule, so its occurrences are those any Tempo recurrence gives; RDATE adds occurrences of the event's own length and EXDATE removes the one that starts at it. An all-day event (DTSTART a DATE) is a span of days, written to its last day; BYHOUR, BYMINUTE and BYSECOND are ignored on one, as RFC 5545 §3.3.10 has it.

Events say what is taken and availability what is offered, and the two compose:

{:ok, free} = Tempo.ICal.available(ics, within: week)
{:ok, busy} = Tempo.ICal.parse(ics, within: week)
{:ok, bookable} = Tempo.difference(free, busy)

Summary

Functions

The time an iCalendar's VAVAILABILITY components offer (RFC 7953).

available/2, returning its answer or raising the error.

The events of a calendar the ical library has already parsed, as parse/2 gives them.

from_ical/2, returning its answer or raising the error.

Reads iCalendar text into a set of intervals, one for each event or occurrence of an event, in order of start.

parse/2, returning its answer or raising the error.

Reads an iCalendar file as parse/2 reads text.

parse_file/2, returning its answer or raising the error.

Functions

available(ics, options \\ [])

@spec available(binary() | ICal.t(), keyword()) ::
  {:ok, Tempo.t()} | {:error, Exception.t()}

The time an iCalendar's VAVAILABILITY components offer (RFC 7953).

Each component covers a period, DTSTART to DTEND, and within it each AVAILABLE subcomponent offers time, repeating under its own RRULE. Where components overlap the one of higher PRIORITY (1 the highest, 0 or none the lowest) decides its whole period.

Arguments

  • ics is iCalendar text or an %ICal{}.

  • options is a keyword list of options.

Options

  • :within is a Tempo.t/0, the window the time offered is taken in. It is required.

Returns

  • {:ok, set} of the time offered within the window, merged.

  • {:error, exception} with no window, or as for parse/2.

Examples

iex> ics = """
...> BEGIN:VCALENDAR
...> VERSION:2.0
...> BEGIN:VAVAILABILITY
...> UID:office-hours
...> DTSTAMP:20260601T000000Z
...> DTSTART:20260601T000000Z
...> DTEND:20260608T000000Z
...> BEGIN:AVAILABLE
...> UID:weekday-mornings
...> DTSTAMP:20260601T000000Z
...> DTSTART:20260601T090000Z
...> DTEND:20260601T120000Z
...> RRULE:FREQ=DAILY;COUNT=5
...> END:AVAILABLE
...> END:VAVAILABILITY
...> END:VCALENDAR
...> """
iex> {:ok, free} = Tempo.ICal.available(ics, within: ~o"2026-06-01T00Z/2026-06-08T00Z")
iex> Tempo.IntervalSet.count(free)
5

available!(ics, options \\ [])

@spec available!(binary() | ICal.t(), keyword()) :: Tempo.t()

available/2, returning its answer or raising the error.

Arguments

Options

Returns

  • What available/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> ics = """
...> BEGIN:VCALENDAR
...> VERSION:2.0
...> BEGIN:VAVAILABILITY
...> UID:office-hours
...> DTSTAMP:20260601T000000Z
...> DTSTART:20260601T000000Z
...> DTEND:20260608T000000Z
...> BEGIN:AVAILABLE
...> UID:weekday-mornings
...> DTSTAMP:20260601T000000Z
...> DTSTART:20260601T090000Z
...> DTEND:20260601T120000Z
...> RRULE:FREQ=DAILY;COUNT=5
...> END:AVAILABLE
...> END:VAVAILABILITY
...> END:VCALENDAR
...> """
iex> ics |> Tempo.ICal.available!(within: ~o"2026-06-01T00Z/2026-06-08T00Z") |> Tempo.IntervalSet.count()
5

from_ical(calendar, options \\ [])

@spec from_ical(ICal.t(), keyword()) :: {:ok, Tempo.t()} | {:error, Exception.t()}

The events of a calendar the ical library has already parsed, as parse/2 gives them.

Arguments

  • calendar is an %ICal{}.

  • options is a keyword list of options.

Options

Returns

  • {:ok, set} or {:error, exception}, as for parse/2.

Examples

iex> calendar =
...>   ICal.from_ics("""
...>   BEGIN:VCALENDAR
...>   VERSION:2.0
...>   BEGIN:VEVENT
...>   UID:offsite
...>   DTSTART;VALUE=DATE:20260615
...>   END:VEVENT
...>   END:VCALENDAR
...>   """)
iex> {:ok, events} = Tempo.ICal.from_ical(calendar)
iex> Tempo.IntervalSet.map(events, &Tempo.to_iso8601!/1)
["2026Y6M15D/2026Y6M15D"]

from_ical!(calendar, options \\ [])

@spec from_ical!(ICal.t(), keyword()) :: Tempo.t()

from_ical/2, returning its answer or raising the error.

Arguments

Options

Returns

  • What from_ical/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> ics = """
...> BEGIN:VCALENDAR
...> VERSION:2.0
...> BEGIN:VEVENT
...> UID:offsite
...> DTSTART;VALUE=DATE:20260615
...> END:VEVENT
...> END:VCALENDAR
...> """
iex> ics |> ICal.from_ics() |> Tempo.ICal.from_ical!() |> Tempo.IntervalSet.count()
1

parse(ics, options \\ [])

@spec parse(binary(), keyword()) :: {:ok, Tempo.t()} | {:error, Exception.t()}

Reads iCalendar text into a set of intervals, one for each event or occurrence of an event, in order of start.

Arguments

  • ics is iCalendar text.

  • options is a keyword list of options.

Options

  • :within is a Tempo.t/0, the window whose events are wanted: every occurrence that overlaps it is kept. It is required when an event repeats with neither COUNT nor UNTIL.

Returns

  • {:ok, set}, a Tempo.t/0 of the :all form, its metadata the calendar's (:prodid, :version, :scale, :method, :name) and each member's its event's (:uid, :summary, :description, :location, :status, and so on).

  • {:error, exception} when a recurring event has no end and no window is given, or a value cannot be read.

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, events} = Tempo.ICal.parse(ics)
iex> [meeting] = Tempo.IntervalSet.members(events)
iex> {Tempo.metadata(meeting).summary, Tempo.duration(meeting)}
{"Test meeting", ~o"PT3600S"}

parse!(ics, options \\ [])

@spec parse!(binary(), keyword()) :: Tempo.t()

parse/2, returning its answer or raising the error.

Arguments

  • The arguments are those of parse/2.

Options

Returns

  • What parse/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> ics = """
...> BEGIN:VCALENDAR
...> VERSION:2.0
...> BEGIN:VEVENT
...> UID:offsite
...> DTSTART;VALUE=DATE:20260615
...> END:VEVENT
...> END:VCALENDAR
...> """
iex> ics |> Tempo.ICal.parse!() |> Tempo.IntervalSet.count()
1

parse_file(path, options \\ [])

@spec parse_file(Path.t(), keyword()) :: {:ok, Tempo.t()} | {:error, Exception.t()}

Reads an iCalendar file as parse/2 reads text.

Arguments

  • path is the path of an .ics file.

  • options is a keyword list of options.

Options

Returns

  • {:ok, set}, or {:error, exception}, a File.Error when the file cannot be read.

Examples

iex> {:error, %File.Error{}} = Tempo.ICal.parse_file("no-such-calendar.ics")

parse_file!(path, options \\ [])

@spec parse_file!(Path.t(), keyword()) :: Tempo.t()

parse_file/2, returning its answer or raising the error.

Arguments

Options

Returns

  • What parse_file/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.ICal.parse_file!("no-such-calendar.ics")
** (File.Error) could not read file "no-such-calendar.ics": no such file or directory