The Trap
Google Calendar's API represents an all-day event's end date as exclusive, not inclusive. A single one-day event on October 20th comes back from the API with start.date set to 2026-10-20 and end.date set to 2026-10-21 -- not 2026-10-20, as most people building a Google Calendar integration naturally assume.
This trips up anyone syncing all-day events into a system that expects an inclusive end date -- a CRM's "event ends on" field, a booking tool's calendar view, a spreadsheet export someone reads directly. Pull the raw end.date value and display it as-is, and a one-day event looks like it spans two days. A five-day company retreat looks like it runs six.
Why Google Did It This Way
The exclusive-end convention is a half-open date range: [start, end). It's a standard, well-understood convention in date-range math -- the same one used by many scheduling libraries -- but it reads as counterintuitive to anyone used to thinking about timed events, where a start and end are both literal instants in time, both fully "included."
A timed event's end.dateTime is a point in time: a meeting from 10am to 11am genuinely ends at 11am. But an all-day event's end.date isn't a point in time at all -- it's a boundary marker for the first calendar day not covered by the event. That distinction is easy to miss, because both fields are just called "end."
Where This Actually Breaks
The most common failure mode shows up when creating an event or updating an event through the API and syncing it somewhere that stores an inclusive end date. A 3-day retreat booked for October 20-22 comes back from Google with end.date of October 23rd. Write that straight into a CRM record's "end date" field, and the retreat now displays as running through the 23rd -- a full day too long, every single time, for every multi-day event synced this way.
Oddly, the math for computing an event's duration works out correctly precisely because of this convention -- subtracting start.date from end.date gives you the right day count without any off-by-one adjustment. The bug isn't in duration calculations; it's in treating the raw end.date value as something safe to display or copy to a human-facing field without first correcting for the exclusive boundary.
It compounds the other direction too. If your own app lets a user pick "October 20 to October 22" and you send that literally as end.date: 2026-10-22 to Google Calendar's API, the event that actually gets created in Google Calendar's own interface will be one day short -- it'll show as ending on the 21st, not the 22nd the user picked.
The Fix
Treat every all-day end.date as "the day after the event actually ends" in both directions:
Reading from Google Calendar: before displaying or copying an all-day event's end date anywhere a human will read it, subtract one day from the returned end.date first. That subtraction converts Google's exclusive boundary back into the inclusive "last day of the event" that every other system expects.
Writing to Google Calendar: when a user picks an inclusive end date in your own interface, add one day before sending it as end.date in the create or update request. Skip this and every all-day event your integration creates will visibly run one day short in the attendee's own calendar.
Timed events don't have any of this -- a dateTime field is a literal instant, inclusive by nature, with no adjustment needed. The bug almost always comes from code that was tested against timed events first, where "end means end," and then reused for all-day events without anyone noticing the field type had quietly changed the meaning of "end" along with it.
If your integration also reacts to calendar change notifications to resync events automatically, the same off-by-one needs to be applied consistently every time an all-day event comes through, not just on the initial sync -- otherwise a corrected one-day event can silently drift back to looking two days long the next time it's touched.
For more implementation pitfalls like this one across the apps this site covers, see the rest of our implementation guides.
