# `JSCalendar.Event`
[🔗](https://github.com/elixir-tempo/jscalendar/blob/v0.1.0/lib/jscalendar/event.ex#L1)

Something that happens at a particular time
([RFC 8984 §5.1](https://www.rfc-editor.org/rfc/rfc8984.html#section-5.1)).

An event has a `start` and a `duration` rather than a start and an
end. That is not a stylistic choice: the end of an event is
`start + duration` *in the event's own time zone*, so an hour-long
meeting stays an hour long across a daylight-saving boundary, where
a stored end time would silently become two hours or none.

`start` is a `LocalDateTime` — wall-clock, with no offset — and
`time_zone` names the zone it is read in. A `nil` time zone means a
floating event: the same wall-clock time wherever the reader is,
which is what "New Year's Eve, midnight" actually means.

### Example

    iex> {:ok, event} = JSCalendar.Event.from_map(%{
    ...>   "@type" => "Event",
    ...>   "uid" => "review-2026-06",
    ...>   "updated" => "2026-06-01T09:00:00Z",
    ...>   "title" => "Quarterly review",
    ...>   "start" => "2026-06-02T09:00:00",
    ...>   "timeZone" => "Australia/Sydney",
    ...>   "duration" => "PT1H"
    ...> })
    iex> {event.title, event.start, event.duration}
    {"Quarterly review", ~N[2026-06-02 09:00:00], %Duration{hour: 1}}

# `t`

```elixir
@type t() :: %JSCalendar.Event{
  alerts: term(),
  categories: term(),
  color: term(),
  created: term(),
  description: String.t(),
  description_content_type: term(),
  duration: Duration.t() | nil,
  excluded: term(),
  excluded_recurrence_rules: term(),
  extra: map(),
  free_busy_status: term(),
  keywords: term(),
  links: term(),
  locale: term(),
  localizations: %{optional(String.t()) =&gt; JSCalendar.Patch.t()} | nil,
  locations: %{optional(String.t()) =&gt; JSCalendar.Location.t()} | nil,
  method: term(),
  participants: %{optional(String.t()) =&gt; JSCalendar.Participant.t()} | nil,
  priority: term(),
  privacy: term(),
  prod_id: term(),
  recurrence_id: term(),
  recurrence_id_time_zone: term(),
  recurrence_overrides:
    %{optional(NaiveDateTime.t()) =&gt; JSCalendar.Patch.t()} | nil,
  recurrence_rules: [JSCalendar.RecurrenceRule.t()] | nil,
  related_to: term(),
  reply_to: term(),
  request_status: term(),
  sent_by: term(),
  sequence: term(),
  show_without_time: term(),
  start: NaiveDateTime.t() | nil,
  status: String.t() | nil,
  time_zone: String.t() | nil,
  time_zones: %{optional(String.t()) =&gt; JSCalendar.TimeZone.t()} | nil,
  title: String.t(),
  uid: String.t() | nil,
  updated: term(),
  use_default_alerts: term(),
  virtual_locations: term()
}
```

A calendar event.

# `duration`

```elixir
@spec duration(t()) :: Duration.t()
```

The event's duration, defaulting as RFC 8984 requires.

`duration` is optional and defaults to `PT0S` when absent, so an
event with no duration is an instant rather than one of unknown
length. Reading the field directly gives `nil`; this gives the
meaning.

### Arguments

* `event` is a `t:t/0`.

### Returns

* a `t:Duration.t/0`.

### Examples

    iex> JSCalendar.Event.duration(%JSCalendar.Event{duration: %Duration{hour: 1}})
    %Duration{hour: 1}

    iex> JSCalendar.Event.duration(%JSCalendar.Event{})
    %Duration{}

# `ends_at`

```elixir
@spec ends_at(t()) :: NaiveDateTime.t() | nil
```

When the event ends, in its own time zone.

### Arguments

* `event` is a `t:t/0`.

### Returns

* a `t:NaiveDateTime.t/0`, or `nil` when the event has no `start`.

### Examples

    iex> event = %JSCalendar.Event{start: ~N[2026-06-02 09:00:00], duration: %Duration{hour: 1}}
    iex> JSCalendar.Event.ends_at(event)
    ~N[2026-06-02 10:00:00]

# `from_map`

```elixir
@spec from_map(map()) :: {:ok, struct()} | {:error, term()}
```

Read this object from a decoded JSON map.

### Arguments

* `map` is a map with string keys, as `:json.decode/1` returns.

### Returns

* `{:ok, object}`; or

* `{:error, reason}` when a property does not match its declared
  type.

# `jscalendar_type`

```elixir
@spec jscalendar_type() :: String.t()
```

The `@type` value RFC 8984 gives this object.

### Returns

* the type name as a string.

# `to_map`

```elixir
@spec to_map(struct()) :: map()
```

Write this object back to a JSON-ready map.

### Arguments

* `object` is a `t:t/0`.

### Returns

* a map with string keys, ready for `:json.encode/1`.

---

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