Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Google Calendar API: update() does not apply recurrence changes for some events

I'm trying to update the recurrence rule of a recurring event in Google Calendar using the Python SDK.

Specifically, I want to extend the event by changing the UNTIL date in the RRULE and calling events().update().

Expected behavior : the event should update with the new recurrence rule and reflect the extended date range.

Actual behavior:

  • For most events, the update works as expected
  • For some events, the API returns 200 OK, but the event remains unchanged — only the etag is updated

What I've tried:

  • Used events().patch() with only the recurrence field — same result.
  • Removed the recurrence in one update, then re-added it in a second update — this works, but likely deletes and recreates all instances, which is not ideal.

Code example

event = gcc.events().get(calendarId=calendar_id, eventId=event_id).execute()
print(f'Event before: {event}')

event['recurrence'] = ['RRULE:FREQ=WEEKLY;UNTIL=20251228T000000Z;BYDAY=WE']
event = gcc.events().update(calendarId=calendar_id, eventId=event_id, body=event).execute()

print(f'Event after: {event}')

Sample Output

Good:

Event before: {'kind': 'calendar#event', 'etag': '"3518127759046942"', 'id': '[redacted]', 'status': 'confirmed', 'htmlLink': 'https://www.google.com/calendar/event?eid=[redacted]', 'created': '2024-12-05T20:33:42.000Z', 'updated': '2025-09-28T12:51:19.523Z', 'summary': 'Available', 'creator': {'email': '[redacted]', 'self': True}, 'organizer': {'email': '[redacted]', 'self': True}, 'start': {'dateTime': '2024-12-04T12:00:00-05:00', 'timeZone': 'America/New_York'}, 'end': {'dateTime': '2024-12-04T13:00:00-05:00', 'timeZone': 'America/New_York'}, 'recurrence': ['RRULE:FREQ=WEEKLY;UNTIL=20251227T000000Z;BYDAY=WE'], 'iCalUID': '[redacted]@google.com', 'sequence': 39, 'extendedProperties': {'private': {'[redacted]': '[redacted]'}}, 'reminders': {'useDefault': True}, 'eventType': 'default'}
Event after: {'kind': 'calendar#event', 'etag': '"3521325742046891"', 'id': '[redacted]', 'status': 'confirmed', 'htmlLink': 'https://www.google.com/calendar/event?eid=[redacted]', 'created': '2024-12-05T20:33:42.000Z', 'updated': '2025-09-28T12:51:19.523Z', 'summary': 'Available', 'creator': {'email': '[redacted]', 'self': True}, 'organizer': {'email': '[redacted]', 'self': True}, 'start': {'dateTime': '2024-12-04T12:00:00-05:00', 'timeZone': 'America/New_York'}, 'end': {'dateTime': '2024-12-04T13:00:00-05:00', 'timeZone': 'America/New_York'}, 'recurrence': ['RRULE:FREQ=WEEKLY;UNTIL=20251228T000000Z;BYDAY=WE'], 'iCalUID': '[redacted]@google.com', 'sequence': 39, 'extendedProperties': {'private': {'[redacted]': '[redacted]'}}, 'reminders': {'useDefault': True}, 'eventType': 'default'}

Bad:

Event before: {'kind': 'calendar#event', 'etag': '"3518127759753054"', 'id': '[redacted]', 'status': 'confirmed', 'htmlLink': 'https://www.google.com/calendar/event?eid=[redacted]', 'created': '2024-12-14T03:04:10.000Z', 'updated': '2025-09-28T12:51:19.876Z', 'summary': 'Available', 'creator': {'email': '[redacted]', 'self': True}, 'organizer': {'email': '[redacted]', 'self': True}, 'start': {'dateTime': '2024-12-12T12:00:00-05:00', 'timeZone': 'America/New_York'}, 'end': {'dateTime': '2024-12-12T13:00:00-05:00', 'timeZone': 'America/New_York'}, 'recurrence': ['RRULE:FREQ=WEEKLY;UNTIL=20250109T045959Z;BYDAY=TH'], 'iCalUID': '[redacted]@google.com', 'sequence': 39, 'extendedProperties': {'private': {'[redacted]': '[redacted]'}}, 'reminders': {'useDefault': True}, 'eventType': 'default'}
Event after: {'kind': 'calendar#event', 'etag': '"3529115769737544"', 'id': '[redacted]', 'status': 'confirmed', 'htmlLink': 'https://www.google.com/calendar/event?eid=[redacted]', 'created': '2024-12-14T03:04:10.000Z', 'updated': '2025-09-28T12:51:19.876Z', 'summary': 'Available', 'creator': {'email': '[redacted]', 'self': True}, 'organizer': {'email': '[redacted]', 'self': True}, 'start': {'dateTime': '2024-12-12T12:00:00-05:00', 'timeZone': 'America/New_York'}, 'end': {'dateTime': '2024-12-12T13:00:00-05:00', 'timeZone': 'America/New_York'}, 'recurrence': ['RRULE:FREQ=WEEKLY;UNTIL=20250109T045959Z;BYDAY=TH'], 'iCalUID': '[redacted]@google.com', 'sequence': 39, 'extendedProperties': {'private': {'[redacted]': '[redacted]'}}, 'reminders': {'useDefault': True}, 'eventType': 'default'}

I've redacted email addresses (and other identifiers) here, but to be clear, the issue is event-related and not end-user-related: users who have some events experiencing this problem may have other events which are fine, and we can make other changes without trouble.

These changes are made programmatically once per day, 90 days in advance, so for the "bad" example, though the UNTIL date is now in the past, at some point in time it was 90 days in the future, and because the update was never successful, the date is now in the past.

The extendedProperty I've redacted is something we use internally and it's identical for both the "good" and the "bad" example run.

I believe the sequence number being 39 in both examples is purely coincidental.

Observations

There seems to be no clear pattern to which events fail.

The RRULE and request body appear valid, and the issue is not documented in the official API docs.

Questions

Why does update() silently fail to apply recurrence changes for some events?

Is there a reliable, non-destructive workaround to update the recurrence rule?

like image 561
Nicklas Johnson Avatar asked Oct 30 '25 03:10

Nicklas Johnson


1 Answers

I have discovered the reason this happens, and as is often the case, the answer is that it's insufficient documentation and poor error handling by Google.

The root cause is someone making an edit to the series at any point and choosing "this and future events" for which events they'd like the change to apply. When this happens, Google creates a new series event with an ID like that of the original recurring event, followed by "_R" followed by the timestamp at which the series was split. So for example, 0mku05shes4kef7fg5gvh2me57 is followed by a new recurring event, 0mku05shes4kef7fg5gvh2me57_R20251015T021500.

Once this happens, the original event will refuse any further attempts at extending the UNTIL value in the RRULE, and unfortunately as outlined in the question, it does so silently: the request gets a 200 response and a valid response, but simply discards the change. It's somewhat sensible not to change this value, because then events from the original series would begin to encroach upon events from the new series. (What's not sensible is silent failure: this really should result in a 400 response with a useful error message from the API.)

There is unfortunately no linkage visible via the API between the original recurring event and the replacement recurring event, though at least as long as Google doesn't change their ID scheme, it's possible to figure out the original recurring event ID from the new recurring event ID and to work backwards. This linkage must exist on Google's side, however, because they know to reject the update to UNTIL, and if you cancel the replacement recurring event, it again becomes possible to extend the original recurring event. Also if you cancel the original recurring event, it will cancel both the original and the replacement recurring event (as one would probably expect).

Hopefully this post will be sufficiently searchable that the next time someone runs into this problem, it will save them some time figuring it out.

like image 184
Nicklas Johnson Avatar answered Nov 01 '25 12:11

Nicklas Johnson