# `Tempo.JSCalendar`
[🔗](https://github.com/elixir-tempo/tempo/blob/v2.1.0/lib/tempo/jscalendar.ex#L2)

Reads JSCalendar (RFC 8984) documents into Tempo: each `Event` an
interval or, when it recurs, its occurrences, with the event's title,
status and the rest as metadata; a `Group` its member events; a `Task`
nothing, a task being work to do and not time taken.

The JSON is decoded by the optional
[`jscalendar`](https://hex.pm/packages/jscalendar) dependency, and this
module is compiled only when it is present. An event's `start` is a
local time in its `timeZone`, floating when it has none. Its recurrence
rules are lowered to Tempo recurrences by `Tempo.RRule`, its excluded
rules take occurrences away, and its recurrence overrides replace or
remove the occurrence they name, or add one.

Not read: a rule's `rscale` other than Gregorian, a `skip` other than
`omit`, and a first day of the week other than Monday.

# `from_jscalendar`

```elixir
@spec from_jscalendar(struct(), keyword()) ::
  {:ok, Tempo.t()} | {:error, Exception.t()}
```

The events of a JSCalendar object already decoded, as `parse/2` gives
them.

### Arguments

* `object` is a `JSCalendar.Event`, `JSCalendar.Task` or
  `JSCalendar.Group`.

* `options` is a keyword list of options.

### Options

* `:within` is as for `parse/2`.

### Returns

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

### Examples

    iex> {:ok, object} =
    ...>   JSCalendar.decode(
    ...>     ~s({"@type":"Event","uid":"e","updated":"2026-06-01T09:00:00Z","start":"2026-06-02T09:00:00","duration":"PT1H"})
    ...>   )
    iex> {:ok, events} = Tempo.JSCalendar.from_jscalendar(object)
    iex> Tempo.IntervalSet.map(events, &Tempo.to_iso8601!/1)
    ["2026Y6M2DT9H0M0S/T10H0M0S"]

# `from_jscalendar!`

```elixir
@spec from_jscalendar!(struct(), keyword()) :: Tempo.t()
```

`from_jscalendar/2`, returning its answer or raising the error.

### Arguments

* The arguments are those of `from_jscalendar/2`.

### Options

* The options of `from_jscalendar/2`.

### Returns

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

### Examples

    iex> json = ~s({"@type":"Event","uid":"e","updated":"2026-06-01T09:00:00Z","start":"2026-06-02T09:00:00","duration":"PT1H"})
    iex> {:ok, object} = JSCalendar.decode(json)
    iex> object |> Tempo.JSCalendar.from_jscalendar!() |> Tempo.IntervalSet.count()
    1

# `parse`

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

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

### Arguments

* `json` is a JSCalendar document as text.

* `options` is a keyword list of options.

### Options

* `:within` is a `t:Tempo.t/0`, the window whose events are wanted:
  every occurrence that overlaps it is kept. It is required when an
  event recurs with neither `count` nor `until`.

### Returns

* `{:ok, set}`, a `t:Tempo.t/0` of the `:all` form, each member's
  metadata its event's (`:uid`, `:title`, `:description`, `:status`,
  `:free_busy_status`, `:privacy`, `:time_zone`, `:sequence`).

* `{:error, %ArgumentError{}}` when the document cannot be decoded,
  naming the reason the `jscalendar` library gives, and
  `{:error, exception}` when a zone is unknown, a rule is not supported
  or a recurrence has no end and no window is given.

### Examples

    iex> json = ~s({
    ...>   "@type": "Event",
    ...>   "uid": "review",
    ...>   "updated": "2026-06-01T09:00:00Z",
    ...>   "title": "Quarterly review",
    ...>   "start": "2026-06-02T09:00:00",
    ...>   "duration": "PT1H"
    ...> })
    iex> {:ok, events} = Tempo.JSCalendar.parse(json)
    iex> [review] = Tempo.IntervalSet.members(events)
    iex> {Tempo.to_iso8601!(review), Tempo.metadata(review).title}
    {"2026Y6M2DT9H0M0S/T10H0M0S", "Quarterly review"}

# `parse!`

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

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

### Arguments

* The arguments are those of `parse/2`.

### Options

* The options of `parse/2`.

### Returns

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

### Examples

    iex> json = ~s({"@type":"Event","uid":"e","updated":"2026-06-01T09:00:00Z","start":"2026-06-02T09:00:00","duration":"PT1H"})
    iex> json |> Tempo.JSCalendar.parse!() |> Tempo.IntervalSet.count()
    1

---

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