Something that happens at a particular time (RFC 8984 ยง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}}
Summary
Functions
The event's duration, defaulting as RFC 8984 requires.
When the event ends, in its own time zone.
Read this object from a decoded JSON map.
The @type value RFC 8984 gives this object.
Write this object back to a JSON-ready map.
Types
@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()) => JSCalendar.Patch.t()} | nil, locations: %{optional(String.t()) => JSCalendar.Location.t()} | nil, method: term(), participants: %{optional(String.t()) => JSCalendar.Participant.t()} | nil, priority: term(), privacy: term(), prod_id: term(), recurrence_id: term(), recurrence_id_time_zone: term(), recurrence_overrides: %{optional(NaiveDateTime.t()) => 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()) => JSCalendar.TimeZone.t()} | nil, title: String.t(), uid: String.t() | nil, updated: term(), use_default_alerts: term(), virtual_locations: term() }
A calendar event.
Functions
@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
eventis at/0.
Returns
- a
Duration.t/0.
Examples
iex> JSCalendar.Event.duration(%JSCalendar.Event{duration: %Duration{hour: 1}})
%Duration{hour: 1}
iex> JSCalendar.Event.duration(%JSCalendar.Event{})
%Duration{}
@spec ends_at(t()) :: NaiveDateTime.t() | nil
When the event ends, in its own time zone.
Arguments
eventis at/0.
Returns
- a
NaiveDateTime.t/0, ornilwhen the event has nostart.
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]
Read this object from a decoded JSON map.
Arguments
mapis a map with string keys, as:json.decode/1returns.
Returns
{:ok, object}; or{:error, reason}when a property does not match its declared type.
@spec jscalendar_type() :: String.t()
The @type value RFC 8984 gives this object.
Returns
- the type name as a string.
Write this object back to a JSON-ready map.
Arguments
objectis at/0.
Returns
- a map with string keys, ready for
:json.encode/1.