# `Tempo.ICal`
[🔗](https://github.com/kipcole9/tempo/blob/v0.20.0/lib/ical.ex#L2)

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

This module wraps the [`ical`](https://github.com/expothecary/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](https://www.rfc-editor.org/rfc/rfc5545) — the iCalendar spec.
* `guides/set-operations.md` for how the imported data is used downstream.

## RFC 5545 coverage

| Property  | Status                                            |
| --------- | ------------------------------------------------- |
| `RRULE`   | Fully supported — every `BY*` part, `WKST`, and   |
|           | `BYSETPOS` flow through the interpreter.          |
| `RDATE`   | Supported — extra occurrences with the event's    |
|           | own span.                                         |
| `EXDATE`  | Supported — start-moment match removes the        |
|           | corresponding occurrence.                         |
| `EXRULE`  | Not surfaced by the underlying `ical` library, so |
|           | not implementable at this layer. EXRULE is also   |
|           | RFC-deprecated (RFC 2445 → 5545).                 |
| Multiple  | RFC 5545 says SHOULD NOT; some exports do it      |
| `RRULE`   | anyway. The `ical` library exposes only the       |
| per       | first `RRULE` on `event.rrule`, so we materialise |
| `VEVENT`  | that 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.

# `from_ical`

```elixir
@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 `t:Tempo.t/0`, `t:Tempo.Interval.t/0`, or
  `t: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`

```elixir
@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}`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
