Add robust recurring event deletion with timezone-aware occurrence matching#18
Open
tamm wants to merge 14 commits intoOmar-V2:mainfrom
Open
Add robust recurring event deletion with timezone-aware occurrence matching#18tamm wants to merge 14 commits intoOmar-V2:mainfrom
tamm wants to merge 14 commits intoOmar-V2:mainfrom
Conversation
…tching This PR addresses the issues identified in PR #1 by implementing a more robust solution for deleting recurring event occurrences with proper timezone handling. Key improvements: 1. **Timezone-aware occurrence matching** - New find_event_occurrence() method with exact datetime matching - Automatic UTC fallback for naive datetimes (common in LLM-constructed dates) - Uses ±1 minute search window followed by exact datetime equality check - Prevents wrong occurrence deletion issues from PR #1 2. **Flexible deletion modes** - Delete single occurrence: occurrence_date only (default) - Delete from occurrence forward: occurrence_date + delete_entire_series=True - Delete entire series: delete_entire_series=True only - Delete non-recurring event: event_id only (unchanged behavior) 3. **Comprehensive test coverage** - test_delete_non_recurring_event: Basic deletion case - test_delete_recurring_event_entire_series: Delete all occurrences - test_delete_recurring_event_single_occurrence: Delete one specific occurrence - test_delete_recurring_event_from_occurrence_forward: Delete from occurrence onward - All tests verify expected behavior with occurrence counts 4. **Enhanced MCP tool documentation** - CRITICAL warnings about using exact datetimes from list_events - Clear usage examples for all deletion scenarios - Explanation of timezone importance to prevent user errors Fixes the two main issues from PR #1: - Wrong occurrence being deleted (timezone matching problem) - Events appearing deleted but not actually being removed (matching failure) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…-timezone-handling Add robust recurring event deletion with timezone-aware occurrence matching
Previously, there was no way to update just one occurrence of a recurring event - updates always affected the entire series or all future occurrences. This adds occurrence-specific update support with clear datetime handling. Changes: - Add occurrence_date parameter to target specific occurrences - Add update_future_events parameter to control update scope - When occurrence_date is provided with update_future_events=False (default), only that specific occurrence is updated using EKSpanThisEvent - When occurrence_date is provided with update_future_events=True, that occurrence and all future ones are updated (creates a new series fork) - When no occurrence_date is provided, the entire series is updated - Document that occurrence_date should use naive datetime format (local time without timezone offset), matching EventKit's requirements - Update both update_event and delete_event docs for datetime format consistency Test coverage: - Update single occurrence only (new capability) ✓ - Update from specific occurrence forward (new capability) ✓ - Update entire series (existing behavior verified) ✓ All 19 tests pass including 3 new comprehensive tests for occurrence-specific updates. EventKit behavior note: When update_future_events=True creates a series fork, subsequent updates to the master event only affect the original series, not the forked occurrences. This is correct EventKit behavior. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add support for updating single occurrences of recurring events
This is the foundational fix for timezone handling. All datetime fields in Event objects are now timezone-aware, preventing ambiguity and enabling proper cross-timezone operations. Changes to models.py: - Import timezone from datetime - Update convert_datetime() to always return timezone-aware datetimes: * NSDate from EventKit → UTC then convert to local timezone with tzinfo * ISO strings → parse and add local timezone if naive * datetime objects → add local timezone if naive - Apply convert_datetime() to all datetime fields in from_ekevent(): * start_time, end_time, last_modified * recurrence_rule.end_date - Update __str__() to format datetimes in ISO 8601 format with timezone (e.g., "2025-11-14T14:00:00+11:00" instead of "2025-11-14 14:00:00") Impact: - Fixes Issue Omar-V2#17 root cause: Event times now include timezone information - Addresses Issue Omar-V2#5 root cause: Timezone info is preserved internally - Uses standard ISO 8601 format for universal compatibility - Breaking change: Tests expecting naive datetimes will need updates - Next layers will handle EventKit boundary conversions Note: This commit intentionally breaks some tests - they will be fixed in subsequent layers as we add proper timezone handling throughout the stack. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Adds to_eventkit_datetime() helper function that converts timezone-aware datetimes to naive local time for EventKit APIs, while passing through naive datetimes unchanged. Changes to ical.py: - Add to_eventkit_datetime() helper function: * Converts timezone-aware datetimes to local timezone, strips tzinfo * Passes naive datetimes through unchanged (assumes local time) * Well-documented with examples for UTC, PST, and naive inputs - Use to_eventkit_datetime() at all EventKit boundaries: * list_events(): Convert start_time and end_time before predicate * create_event(): Convert start_time and end_time before setStartDate/setEndDate * update_event(): Convert start_time and end_time if provided Impact: - Claude can now provide datetimes in ANY timezone (UTC, PST, AEDT, etc.) - All conversions happen automatically and correctly - EventKit gets what it expects (naive local time) - Internal Event objects remain timezone-aware (from Layer 1) - Tests still fail (expected) - will be fixed in Layer 5 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Updates occurrence matching to properly handle timezone-aware datetimes from Claude. Now correctly matches occurrences regardless of which timezone Claude provides the datetime in (PST, UTC, AEDT, etc.). Changes to ical.py: - Update _search_occurrence_by_datetime(): * Convert search window to naive local time using to_eventkit_datetime() * Convert target datetime to naive local time for comparison * Match against EventKit's naive datetimes correctly * Add documentation explaining timezone-aware matching approach - find_event_occurrence() unchanged (already has UTC fallback for naive times) Impact: - Cross-timezone occurrence matching now works correctly - Claude can provide occurrence_date in any timezone (PST, UTC, AEDT, etc.) - System automatically converts to local time for EventKit matching - Naive datetime fallback (try local, then UTC) still works - Fixes the core issue preventing occurrence updates/deletes across timezones Example scenarios that now work: - Event created in AEDT, Claude provides PST datetime → matches correctly - Event in UTC, Claude provides local time → matches correctly - Naive datetime provided → tries local first, then UTC (existing behavior) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Updates all tool documentation to reflect the new timezone-aware capabilities. Removes outdated guidance about stripping timezone offsets and clarifies that the system now fully supports timezone-aware datetimes. Changes to server.py: - update_event() docstring: * Remove guidance about stripping timezone offsets (no longer needed) * Add clear statement about timezone-aware datetime support * Update examples to show timezone-aware format * Document automatic timezone conversion * Clarify both formats work: timezone-aware (preferred) and naive (local assumed) - delete_event() docstring: * Same updates as update_event for consistency * Remove outdated "remove timezone suffix" instructions * Add timezone handling note explaining automatic conversion Impact: - Claude will now see correct guidance about timezone handling - Encourages use of timezone-aware datetimes (more explicit, less ambiguous) - Still supports naive datetimes for backward compatibility - Addresses confusion from Issues Omar-V2#5 and Omar-V2#17 about timezone handling 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Updates test fixtures to create timezone-aware datetimes, ensuring tests work correctly with the new timezone-aware Event model. Changes to test_calendar_manager_integration.py: - Import timezone from datetime - Update test_event_base fixture: * Use datetime.now(timezone.utc).astimezone() for timezone-aware local time * Add comment explaining timezone-aware approach * Ensures test datetimes match Event object datetimes (both timezone-aware) Impact: - All 19 tests now pass ✓ - Tests verify timezone-aware functionality works end-to-end - Fixture creates realistic test data (timezone-aware like real usage) - No behavioral changes to tests, just fixes comparison mismatches Test results: - test_create_and_get_event: PASSED (was failing before) - All other tests: PASSED (still passing) - Total: 19/19 tests passing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Streamlined documentation to be clearer and less verbose - Removed overly prescriptive language that could confuse the AI - Maintained support for both timezone-aware and naive datetime formats - All tests passing with full timezone support This completes the comprehensive timezone handling implementation: - ISO 8601 datetimes with timezone offsets throughout - Cross-timezone operation support (UTC, PST, AEDT, etc.) - Single occurrence updates/deletes with timezone-aware matching - Backward compatibility with naive datetimes maintained
…pport - Reject naive datetimes in list_events with a clear error guiding AI to include an explicit timezone (Z or +HH:MM offset) - Show pre-converted local time alongside ISO timestamps in event output so AI consumers don't misread UTC offsets and assign wrong calendar days - Add attendee support via AppleScript (EventKit attendees are read-only) - Update all tool docstrings to require explicit timezone in examples
…try logic - Match events by calendar + title + start date (not just title) - Fix NSDate crash when passing EventKit dates to AppleScript - Rate-limit Calendar.app launches (30s cooldown) to prevent crashes - Split AppleScript into find/add/verify phases (no duplicate writes) - Enrich attendee output to include emails via EKParticipant.URL() - Refresh EventKit store after AppleScript modifications - Strengthen test assertions to verify specific attendee emails
list_events was returning full event details (~33k chars for a week query), exceeding MCP token limits. Now returns compact plaintext summaries with only essential fields: title, local time, id, calendar, truncated location/notes, attendee count, and human-readable recurrence. Full details remain available via __str__ for single-event inspection. Also deduplicates all-day events by title+date (fixes duplicate holidays from multiple subscribed calendars), flags cancelled events, and includes date on all-day events so they get assigned to the correct calendar day.
Location: URLs (Teams/Meet/Zoom links) replaced with "(online meeting link)" — only short venue names shown. Notes: replaced truncated content with a has_notes flag — notes often contain forwarded emails, URLs, and legal boilerplate that bloat output and leak PII.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR addresses the issues identified in #1 by implementing a more robust solution for deleting recurring event occurrences with proper timezone handling.
Key Improvements
1. Timezone-aware occurrence matching
find_event_occurrence()method with exact datetime matching2. Flexible deletion modes
occurrence_dateonly (default)occurrence_date+delete_entire_series=Truedelete_entire_series=Trueonlyevent_idonly (unchanged behavior)3. Comprehensive test coverage (as requested in #1)
Added 4 integration tests in
tests/test_calendar_manager_integration.py:test_delete_non_recurring_event: Basic deletion casetest_delete_recurring_event_entire_series: Delete all occurrencestest_delete_recurring_event_single_occurrence: Delete one specific occurrencetest_delete_recurring_event_from_occurrence_forward: Delete from occurrence onward4. Enhanced MCP tool documentation
list_eventsWhat This Fixes from #1
The original PR had two main issues mentioned in the review:
Implementation Details
The solution uses EventKit's equality operator for datetime matching after narrowing down candidates with a predicate search. When a naive datetime is provided, it automatically tries UTC interpretation as a fallback (common when Claude constructs times).
Example flow for deleting 3rd occurrence of a daily recurring event:
Testing
All existing tests pass, plus 4 new integration tests specifically for recurring event deletion scenarios.
🤖 Generated with Claude Code
Co-Authored-By: Claude noreply@anthropic.com