Three files:

  • calendar-data.json β€” the structured source-of-truth (ticket drops, reservation windows, itinerary legs, dog-home-base windows). Edit this when dates/prices change. Mirrors destinations/calendar.md.
  • make_ics.py β€” stdlib-only generator β†’ trip-calendar-2026-2027.ics.
  • trip-calendar-2026-2027.ics β€” the importable file.

How to get it on your phone

  1. python3 make_ics.py
  2. Desktop browser β†’ calendar.google.com β†’ Settings (βš™) β†’ Import & export β†’ Select file β†’ pick trip-calendar-2026-2027.ics β†’ choose or create a calendar (suggest a new β€œTravel Hub”) β†’ Import.
  3. Imported alarms/reminders stay attached. Google auto-syncs to your phone app within minutes.
  4. When research updates (new dates), re-run the script and delete + re-import (or import into a fresh β€œTravel Hub 2028” calendar).

Option B β€” live subscription (read-only, auto-updates)

Host the .ics at a stable URL (GitHub raw, Gist, Dropbox public link), then Google Calendar β†’ Add calendar β†’ From URL β†’ paste link. It re-fetches on its own schedule. Downside: you can’t edit events or rely on re-alarms from the phone side; use Option A for anything you must be notified about.

Option C β€” Google Calendar API (two-way, for automators)

Push via google-api-python-client with OAuth (creates/updates events programmatically). Only worth it if you want the write-back loop (buy a ticket β†’ tick it off in Google β†’ data stays clean). Not set up yet; ask if you want it β€” needs a GCP client_secret.json + scopes.

Data model cheat-sheet (calendar-data.json)

  • allDay: true β†’ DTSTART/DTEND;VALUE=DATE (date-only). JSON end = true last day.
  • For multi-day all-day events the script adds one day to DTEND (RFC all-day end is exclusive), e.g. Jan 6–Jan 19 in JSON β†’ DTEND 20270120.
  • minutes_before β†’ one VALARM each (Google keeps these on import). null = no alarm.
  • Multi-day all-day legs: JSON end stores the true last day (e.g. Jan 19); the script emits DTEND=end+1 because RFC 5545 all-day DTEND is exclusive.
  • tzid unused for allDay events; timed events are emitted UTC (DST-safe for AZ, close enough for NV/CO planning).

Keep in sync

calendar-data.json is the single source. Commit changes alongside destinations/calendar.md edits. Re-generate + re-import per trip season.