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
- Have a recurring all-day annual event (a Birthdays-style yearly event) on an iCloud calendar.
- 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.
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
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
--start/--endtimes are ignored (it normalizes to 00:00–23:59 on the date).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;
updatereports success.Where (v1.5.0 source)
Sources/ekctlCore/EventKitManager.swift,updateEvent(...):On a recurring master, saving with
span: .thisEventwhile togglingisAllDayappears to make EventKit re-clamp the dates to all-day boundaries, dropping the supplied times. CLI parsing is not the cause —update eventdeclares@Option var allDay: Bool?, so--all-day falseis correctly conveyed asallDay = 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.