Skip to content

update event --all-day false on a recurring all-day event is silently ignored and corrupts the stored dates #12

@SpoonJimSquareSocks

Description

@SpoonJimSquareSocks

Environment: ekctl 1.5.0 (also observed in production on 1.3.0), macOS 26.x (Tahoe), iCloud/CalDAV calendar.

Summary

Converting a single recurring all-day event to a timed event via update event … --all-day false --start … --end … is silently ignored and corrupts the event's stored dates, instead of either performing the conversion or returning an error.

Repro

  1. Have a recurring all-day annual event (a Birthdays-style yearly event) on an iCloud calendar.
  2. Convert one occurrence to a timed event:
ekctl update event <EVENT_ID> \
  --start 2026-06-26T18:00:00-04:00 \
  --end   2026-06-26T20:00:00-04:00 \
  --all-day false

Expected

The event becomes a timed 18:00–20:00 event — or ekctl returns a clear error if all-day→timed conversion on a recurring series is unsupported.

Actual

  • The event stays all-day; the supplied --start/--end times are ignored (it normalizes to 00:00–23:59 on the date).
  • A subsequent read shows a timezone artifact: the all-day June-26 event reads back as 2026-06-25T20:00:00 → 2026-06-26T19:59:59 -04:00 — i.e. midnight-to-midnight stored in UTC and surfaced across the −04:00 offset.

The event is left corrupted; update reports success.

Where (v1.5.0 source)

Sources/ekctlCore/EventKitManager.swift, updateEvent(...):

if let startDate = startDate { event.startDate = startDate }   // L339
if let endDate   = endDate   { event.endDate   = endDate   }   // L340

if let allDay = allDay { event.isAllDay = allDay }             // L348

try eventStore.save(event, span: .thisEvent)                   // L375

On a recurring master, saving with span: .thisEvent while toggling isAllDay appears to make EventKit re-clamp the dates to all-day boundaries, dropping the supplied times. CLI parsing is not the cause — update event declares @Option var allDay: Bool?, so --all-day false is correctly conveyed as allDay = false.

Suggested fix

Detect an all-day↔timed transition on a recurring event and either handle it correctly or fail loudly with a clear message, rather than silently saving a corrupted event. A clear error is an acceptable outcome; the silent corruption is the real problem.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions