My assistant used to plan from only half of my life.
Personal and work events lived in Google Calendar. Family outings, holidays and school dates belonged in a privately shared Apple Family calendar. Both choices made sense, but the split created a dangerous failure mode: an automated daily brief could look at Google, see an empty afternoon and confidently call it free while the family calendar said otherwise.
The fix was not to merge the calendars. It was to build one read path across both while preserving their different privacy boundaries.
One view does not require one owner
Copying every event into one provider would have been convenient for the software and wrong for the household. Private appointments should not be shared merely because theatre tickets are. Family members should not need access to my work calendar just to see a school event.
The routing rule is now explicit:
- personal, work and medical events stay in Google;
- shared outings, holidays and school commitments go to Apple Family;
- planning reads both.
That distinction matters. Calendar storage answers who should see this event. The combined agenda answers what is actually happening.
Direct CalDAV instead of another sync service
The integration reads the existing Apple Family calendar directly over CalDAV. Credentials remain in a protected store and are only released to the approved Apple calendar hosts. The assistant never receives a plaintext password in a prompt, command line or log.
The first useful test was deliberately unglamorous: discover every similarly named calendar and identify the exact shared collection. There were multiple tempting candidates. Only one was the accepted Family calendar, so its stable identifier became configuration rather than relying on a display name that somebody could rename later.
The combined reader then fetches Google and Apple for the same time window and returns three things:
- a merged event list;
- references to the original providers;
- an explicit coverage result for each source.
That third output is the important one. A failed calendar query is not an empty calendar. If either provider cannot be read, the system preserves uncertainty instead of declaring free time.
Deduplication is more subtle than matching titles
Two calendars can describe the same event with different time-zone offsets or provider-specific identifiers. The reader normalises instants and deduplicates entries that share title, start and end, while retaining both source references.
It also avoids two easy mistakes:
- recurring instances remain distinct;
- an informational all-day school date does not automatically block an entire day.
This is a small example of a wider automation rule: data should be simplified only after preserving the semantics that decisions depend on.
Moving school dates safely
I also moved 32 future school events from Google into Apple Family. The order of operations mattered more than the copying code:
- inventory the source events and search the destination for duplicates;
- create stable destination records;
- read every created event back and compare title, time, notes, reminders and availability;
- remove the Google originals only after all destination checks pass;
- verify exactly one active copy remains.
This prevented a partial migration from turning into missing dates or double reminders. It also produced a reversal map rather than trusting that a successful API response meant the move was complete.
The result
Daily planning, weekly reviews, upcoming-event alerts and booking conflict checks now use the same combined reader. Writes still follow their own routing rules: a tennis booking can remain Google-owned while its conflict check includes Family.
The useful lesson is not “sync Apple and Google”. It is narrower: preserve ownership at the edges, combine evidence at the decision point, and make missing coverage visible.
The best calendar automation is not the one with the most events in one place. It is the one that knows when it cannot safely say the diary is clear.