Skip to content

Migrating to v3

v3 has one breaking change, and it affects one specific shape of event. Most calendars upgrade without any changes.

An event’s end is now exclusive: the event runs up to, but not including, the instant you give it.

The practical effect is limited to one case — an end at exactly midnight:

// This is the shape that changes:
{ start: '2025-01-13T00:00', end: '2025-01-14T00:00' }
// v2: painted 13 and 14 January
// v3: paints 13 January only
// This is unchanged — 23:59 falls during the 14th, so the 14th is covered:
{ start: '2025-01-13T00:00', end: '2025-01-14T23:59' }

This matches RFC 5545 §3.6.1, which calls DTEND “the non-inclusive end of the event”, and the Google Calendar API, which documents end the same way. v2’s inclusive reading is why the same event could be exported to another calendar and come back a day longer.

Your events Affected
Timed events inside one day No
All-day events created by the built-in form No — they store 23:59, which still covers that day
Any event whose end is a time during the day No
Events you build yourself with an end at exactly midnight Yes — one day shorter
Code reading CellInfo.end from onCellClick Yes — see below

If nothing in your data ends at exactly midnight, there is nothing to do.

  • Date grids read end exclusively. An event ending at midnight stops as that day begins, exactly as one ending on the hour does not fill that hour. To keep a bar that reaches a given day, end it during that day (2025-01-14T23:59) or at the following midnight (2025-01-15T00:00).

  • The form stores all-day ends differently, and it changes nothing you see. A new all-day event is stored as the midnight after the last covered day rather than 23:59, which is what the RFC requires and what other calendars expect on import. Both forms render the same days, so existing events keep displaying as they always did. The form still shows you the last covered day. This only matters if you read event.end yourself or compare an iCal export: a one-day all-day event now exports as DTSTART;VALUE=DATE:20250804 / DTEND;VALUE=DATE:20250805.

  • CellInfo.end is the next midnight. What onCellClick receives (and the data-end attribute plugins read) now ends on the next boundary rather than at 23:59, matching the hour and 15-minute cells, which already did. If you subtracted a second to get “the end of the day”, you can stop.

  • Search your event data for an end at exactly midnight. Those bars are now one day shorter; end them during the intended last day instead.
  • Check any onCellClick handler that reads info.end.
  • If you compare against another calendar, re-export: the exported DTEND is now the RFC’s exclusive value.