Skip to content

Calendars, events, todos, and journals ​

Calendar metadata and queries ​

Calendar represents one valid VCALENDAR. Its public metadata is version, productId, method, calendarScale, and floatingTimezone. Missing optional metadata is null.

php
$calendar->events();
$calendar->events('uid@example.test');
$calendar->event('uid@example.test');
$calendar->hasEvents('uid@example.test');

$calendar->todos();
$calendar->todos('task@example.test');
$calendar->todo('task@example.test');
$calendar->hasTodos('task@example.test');

$calendar->journals();
$calendar->journals('entry@example.test');
$calendar->journal('entry@example.test');
$calendar->hasJournals('entry@example.test');

UID filters are exact, case-sensitive, and are not trimmed. Plural methods return every match in document order. Singular methods prefer the component without RECURRENCE-ID; when no master exists, they return the first matching override. A missing match returns null, and plural queries return an empty Collection.

Journal fields ​

Journal is a typed VJOURNAL snapshot. It exposes uid, timestamp, classification, createdAt, startsAt, startIsDate, startIsFloating, lastModifiedAt, organizer, recurrenceId, recurrenceIdIsDate, recurrenceIdIsFloating, sequence, status, summary, url, recurrenceRule, attachments, attendees, categories, comments, contacts, descriptions, exceptionDates, relatedTo, recurrenceDates, and requestStatuses.

Repeated DESCRIPTION values are exposed as Collection<int, string> $descriptions in document order. Journals preserve recurrence properties but do not provide alarms, range queries, or recurrence expansion.

Fields shared by Event and Todo ​

FieldTypeData returned
uid?stringExact UID.
summary, description, location, url?stringCommon text and URI values.
startsAt?CarbonImmutableDTSTART interpreted with its UTC, TZID, floating, or DATE semantics.
startIsDateboolWhether DTSTART uses VALUE=DATE.
startIsFloatingboolWhether DTSTART is DATE or a DATE-TIME without TZID or Z.
duration?DateIntervalExplicit or boundary-derived effective duration.
timestamp, createdAt, lastModifiedAt?CarbonImmutableDTSTAMP, CREATED, and LAST-MODIFIED.
classification, status?stringUppercase CLASS and STATUS source tokens.
priority, sequence?intInteger metadata.
recurrenceId?CarbonImmutableRECURRENCE-ID.
recurrenceIdIsDate, recurrenceIdIsFloatingboolSource value-type and floating flags for RECURRENCE-ID.
organizer?OrganizerOrganizer address and parameters.
attendeesCollection<int, Attendee>Repeated attendees in document order.
alarmsCollection<int, Alarm>Direct VALARM children.
categoriesCollection<int, string>Flattened CATEGORIES text-list values in order.
geo?array{latitude: float, longitude: float}An in-range GEO pair; malformed or out-of-range data returns null.
comments, contactsCollection<int, string>One decoded string per repeated COMMENT or CONTACT.
resourcesCollection<int, string>Flattened RESOURCES text-list values in order.
recurrenceRule?PropertyFirst RRULE, including values, parameters, and raw text.
attachmentsCollection<int, Property>Every ATTACH.
exceptionDatesCollection<int, Property>Every EXDATE.
requestStatusesCollection<int, Property>Every REQUEST-STATUS.
relatedToCollection<int, Property>Every RELATED-TO.
recurrenceDatesCollection<int, Property>Every RDATE.

These convenience fields do not remove their generic properties. For example, an invalid typed geo remains available through property('GEO').

Event-only fields ​

FieldTypeData returned
endsAt?CarbonImmutableExclusive DTEND, or an end derived from duration/all-day rules.
endIsDate, endIsFloatingboolValue-type and floating flags for the explicit or derived end.
allDayboolWhether DTSTART uses VALUE=DATE; identical to isAllDay().
lastDay?CarbonImmutableInclusive final date for an all-day event.
transparency?stringUppercase source TRANSP token; absent stays null.

Do not infer an all-day event from midnight or a 24-hour duration. Use allDay, startIsDate, or isAllDay().

Todo-only fields ​

FieldTypeData returned
completedAt?CarbonImmutableUTC COMPLETED.
dueAt?CarbonImmutableExplicit DUE, or DTSTART + DURATION.
dueIsDate, dueIsFloatingboolFlags from DUE, or inherited from DTSTART when due is derived.
percentComplete?intPERCENT-COMPLETE.

Todo has no implicit one-day duration. Without enough DTSTART, DUE, or DURATION data, dueAt and duration remain null.

Date and duration behavior ​

  • UTC values retain UTC; resolvable TZID values retain that timezone.
  • Floating DATE-TIME values use Calendar::$floatingTimezone.
  • An unresolved document TZID adds a warning and leaves the typed date field null; the original Property remains available.
  • DTEND is exclusive. All-day lastDay is one calendar day before endsAt.
  • An all-day Event without DTEND gets an implicit one-calendar-day end.
  • Derived Event end flags and Todo due flags inherit their start flags.
  • RECURRENCE-ID flags always describe that property itself.
  • DateInterval is mutable in PHP; clone it before changing a value you need to retain.

Recurrence properties remain available on the original Event objects. Use the occurrence query below when you need the concrete instances produced by those properties.

Event range queries ​

php
$events = $calendar->eventsBetween($from, $until);

Both boundaries accept DateTimeInterface. The interval is half-open: $from is included and an event starting exactly at $until is excluded. $from must be earlier than $until, or InvalidArgumentException is thrown. Events without a usable start are excluded, and only VEVENT components actually present in the calendar are returned.

Recurring event occurrences ​

occurrencesBetween() returns the events that occur within a range: non-recurring events and recurring events expanded from RRULE, RDATE, EXDATE, overrides, and cancellations. The result is a start-time-sorted Collection<int, Event>.

Pass the start and end of the query range:

php
use Carbon\CarbonImmutable;

$occurrences = $calendar->occurrencesBetween(
    CarbonImmutable::parse('2026-08-01 00:00:00', 'Asia/Taipei'),
    CarbonImmutable::parse('2026-09-01 00:00:00', 'Asia/Taipei'),
);

Both arguments accept DateTimeInterface, including native DateTime / DateTimeImmutable and Carbon. The query uses [from, until), and $from must be earlier than $until.

Limitations:

  • Only VEVENT is expanded, not VTODO or VJOURNAL.
  • One query evaluates at most 3,500 occurrence candidates; narrow large date ranges.
  • Some recurrence combinations are unsupported, including RECURRENCE-ID;RANGE=THISANDFUTURE and multiple RRULE properties.
  • Results are not added automatically to Calendar array or JSON output.

Last updated:

Released under the MIT License.