JSCalendar.Occurrence (JSCalendar v0.1.0)

Copy Markdown View Source

One instance of a recurring object (RFC 8984 §4.3.5).

A recurring object is a base plus a rule plus a table of exceptions. Expanding the rule gives you a list of recurrence ids — local date-times — and this module turns one of those back into an object: the base, shifted to that moment, with any override applied.

JSCalendar.Occurrence.at(standup, ~N[2026-06-04 09:00:00])
#=> {:ok, %JSCalendar.Event{start: ~N[2026-06-04 09:00:00], ...}}

Three things happen that a plain patch would not do:

  • The anchor moves. An occurrence inherits everything except its start — or, for a task with no start, its due — which becomes the recurrence id. A patch may then override even that, and the patch wins.

  • The recurrence machinery is stripped. §4.3.1 forbids an object with a recurrenceId from also carrying recurrenceRules or recurrenceOverrides, so an occurrence is a single object rather than a recurring one that recurs again.

  • Some pointers are ignored rather than rejected. An override may not change a uid, a prodId, or the rules that generated it; the RFC's word is ignored, so such a patch still applies — minus those pointers — rather than failing.

Exclusion is a separate answer, not an empty one:

JSCalendar.Occurrence.at(standup, ~N[2026-06-05 09:00:00])
#=> :excluded

A caller expanding a rule has to distinguish "this occurrence was cancelled" from "this occurrence could not be built", and a tuple that could mean either would make that impossible.

Summary

Functions

Build the occurrence of object at recurrence_id.

Whether the occurrence at recurrence_id is excluded.

The recurrence ids object overrides, in chronological order.

Functions

at(object, recurrence_id)

@spec at(
  struct(),
  NaiveDateTime.t()
) :: {:ok, struct()} | :excluded | {:error, term()}

Build the occurrence of object at recurrence_id.

Arguments

  • object is a recurring JSCalendar.Event.t/0 or JSCalendar.Task.t/0.

  • recurrence_id is a NaiveDateTime.t/0 produced by the object's recurrence rules, or a key of its recurrence_overrides that the rules did not produce — an additional occurrence, the equivalent of an iCalendar RDATE.

Returns

  • {:ok, occurrence} — the same struct type as object, carrying recurrence_id and recurrence_id_time_zone; or

  • :excluded when the override sets excluded to true; or

  • {:error, reason} when the override is not a valid patch.

Examples

iex> standup = %JSCalendar.Event{
...>   uid: "a", start: ~N[2026-06-01 09:00:00], time_zone: "Australia/Sydney",
...>   recurrence_overrides: %{~N[2026-06-03 09:00:00] => %{"title" => "Retro"}}}
iex> {:ok, occurrence} = JSCalendar.Occurrence.at(standup, ~N[2026-06-03 09:00:00])
iex> {occurrence.start, occurrence.title, occurrence.recurrence_id}
{~N[2026-06-03 09:00:00], "Retro", ~N[2026-06-03 09:00:00]}

iex> standup = %JSCalendar.Event{
...>   start: ~N[2026-06-01 09:00:00],
...>   recurrence_overrides: %{~N[2026-06-03 09:00:00] => %{"excluded" => true}}}
iex> JSCalendar.Occurrence.at(standup, ~N[2026-06-03 09:00:00])
:excluded

excluded?(object, recurrence_id)

@spec excluded?(
  struct(),
  NaiveDateTime.t()
) :: boolean()

Whether the occurrence at recurrence_id is excluded.

Arguments

Returns

  • true or false.

Examples

iex> event = %JSCalendar.Event{
...>   recurrence_overrides: %{~N[2026-06-05 09:00:00] => %{"excluded" => true}}}
iex> JSCalendar.Occurrence.excluded?(event, ~N[2026-06-05 09:00:00])
true

iex> JSCalendar.Occurrence.excluded?(%JSCalendar.Event{}, ~N[2026-06-05 09:00:00])
false

overridden(object)

@spec overridden(struct()) :: [NaiveDateTime.t()]

The recurrence ids object overrides, in chronological order.

Includes both variations on rule-generated occurrences and additional ones the rules do not produce — telling those apart needs the rules expanded, which is the caller's business, not this module's.

Arguments

Returns

Examples

iex> event = %JSCalendar.Event{recurrence_overrides: %{
...>   ~N[2026-06-05 09:00:00] => %{"excluded" => true},
...>   ~N[2026-06-03 09:00:00] => %{"title" => "Retro"}}}
iex> JSCalendar.Occurrence.overridden(event)
[~N[2026-06-03 09:00:00], ~N[2026-06-05 09:00:00]]