The idempotency key problem, and why column E is your fix
Every time your script runs — whether triggered by a time-based trigger, an `onEdit` hook, or a manual run — CalendarApp.createEvent will happily create a brand-new event with no memory of the last one. Run it ten times, get ten identical entries. The fix is simple in principle: write the event ID that Calendar returns back into the row, then check for that ID before creating anything new.
CalendarApp.createEvent returns a CalendarEvent object. Call .getId() on it immediately and store the result in a dedicated column (column E in this script). On the next run, that cell is non-empty, so the script calls calendar.getEventById(eventId) instead. If the event still exists, it updates title and time in place. If it was deleted from Calendar manually, getEventById returns null, and the script falls through to create a fresh one and write the new ID back.
The first time I wired this up I forgot the null-check on getEventById, which threw a TypeError on rows where someone had deleted the Calendar event by hand. The double guard — check the cell, then check that the event actually exists — covers both cases cleanly.