diff options
| author | Craig Jennings <c@cjennings.net> | 2025-11-16 18:09:17 -0600 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-11-16 18:09:17 -0600 |
| commit | da0bd6883a4032054aef4b59c338f60796a0fd99 (patch) | |
| tree | e95369646441b35058c89dfb9c31bad9410243fa /tests/testutil-calendar-sync.el | |
| parent | 6280a13c87412a6ff50bbaa43e821c518bd2bd0e (diff) | |
feat(calendar-sync): Add automatic timezone detection and chronological sorting
Implemented calendar-sync.el as a complete replacement for org-gcal, featuring:
**Core Functionality:**
- One-way sync from Google Calendar to Org (via .ics URL)
- UTC to local timezone conversion for all event timestamps
- Chronological event sorting (past → present → future)
- Non-blocking sync using curl (works reliably in daemon mode)
**Automatic Timezone Detection:**
- Detects timezone changes when traveling between timezones
- Tracks timezone offset in seconds (-21600 for CST, -28800 for PST, etc.)
- Triggers automatic re-sync when timezone changes detected
- Shows informative messages: "Timezone change detected (UTC-6 → UTC-8)"
**State Persistence:**
- Saves sync state to ~/.emacs.d/data/calendar-sync-state.el
- Persists timezone and last sync time across Emacs sessions
- Enables detection even after closing Emacs before traveling
**User Features:**
- Interactive commands: calendar-sync-now, calendar-sync-start/stop
- Keybindings: C-; g s (sync), C-; g a (start auto-sync), C-; g x (stop)
- Optional auto-sync every 15 minutes (disabled by default)
- Clear status messages for all operations
**Code Quality:**
- Comprehensive test coverage: 51 ERT tests (100% passing)
- Refactored UTC conversion into separate function
- Clean separation of concerns (parsing, conversion, formatting, sorting)
- Well-documented with timezone behavior guide and changelog
**Migration:**
- Removed org-gcal-config.el (archived in modules/archived/)
- Updated init.el to use calendar-sync
- Moved gcal.org to .emacs.d/data/ for machine-independent syncing
- Removed org-gcal appointment capture template
Files modified: modules/calendar-sync.el:442, tests/test-calendar-sync.el:577
Files created: data/calendar-sync-state.el, tests/testutil-calendar-sync.el
Documentation: docs/calendar-sync-timezones.md, docs/calendar-sync-changelog.md
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'tests/testutil-calendar-sync.el')
| -rw-r--r-- | tests/testutil-calendar-sync.el | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/tests/testutil-calendar-sync.el b/tests/testutil-calendar-sync.el new file mode 100644 index 00000000..c83006b5 --- /dev/null +++ b/tests/testutil-calendar-sync.el @@ -0,0 +1,110 @@ +;;; testutil-calendar-sync.el --- Test utilities for calendar-sync -*- lexical-binding: t; -*- + +;;; Commentary: +;; Utilities for testing calendar-sync module, especially dynamic timestamp generation. +;; Following quality-engineer.org guidelines: no hardcoded dates! + +;;; Code: + +(require 'calendar) + +;;; Dynamic Timestamp Generation + +(defun test-calendar-sync-time-today-at (hour minute) + "Generate time for today at HOUR:MINUTE. +Returns (year month day hour minute) list suitable for tests." + (let* ((now (decode-time)) + (year (nth 5 now)) + (month (nth 4 now)) + (day (nth 3 now))) + (list year month day hour minute))) + +(defun test-calendar-sync-time-tomorrow-at (hour minute) + "Generate time for tomorrow at HOUR:MINUTE." + (let* ((tomorrow (time-add (current-time) (* 24 3600))) + (decoded (decode-time tomorrow)) + (year (nth 5 decoded)) + (month (nth 4 decoded)) + (day (nth 3 decoded))) + (list year month day hour minute))) + +(defun test-calendar-sync-time-days-from-now (days hour minute) + "Generate time for DAYS from now at HOUR:MINUTE." + (let* ((future (time-add (current-time) (* days 24 3600))) + (decoded (decode-time future)) + (year (nth 5 decoded)) + (month (nth 4 decoded)) + (day (nth 3 decoded))) + (list year month day hour minute))) + +(defun test-calendar-sync-time-days-ago (days hour minute) + "Generate time for DAYS ago at HOUR:MINUTE." + (let* ((past (time-subtract (current-time) (* days 24 3600))) + (decoded (decode-time past)) + (year (nth 5 decoded)) + (month (nth 4 decoded)) + (day (nth 3 decoded))) + (list year month day hour minute))) + +(defun test-calendar-sync-time-date-only (offset-days) + "Generate date-only timestamp for OFFSET-DAYS from now. +Returns (year month day) list for all-day events." + (let* ((future (time-add (current-time) (* offset-days 24 3600))) + (decoded (decode-time future)) + (year (nth 5 decoded)) + (month (nth 4 decoded)) + (day (nth 3 decoded))) + (list year month day))) + +;;; .ics Test Data Generation + +(defun test-calendar-sync-ics-datetime (time-list) + "Convert TIME-LIST to iCal DATETIME format. +TIME-LIST is (year month day hour minute). +Returns string like '20251116T140000Z'." + (format "%04d%02d%02dT%02d%02d00Z" + (nth 0 time-list) + (nth 1 time-list) + (nth 2 time-list) + (nth 3 time-list) + (nth 4 time-list))) + +(defun test-calendar-sync-ics-date (time-list) + "Convert TIME-LIST to iCal DATE format. +TIME-LIST is (year month day). +Returns string like '20251116'." + (format "%04d%02d%02d" + (nth 0 time-list) + (nth 1 time-list) + (nth 2 time-list))) + +(defun test-calendar-sync-make-vevent (summary start end &optional description location) + "Create a VEVENT block for testing. +START and END are time lists from test-calendar-sync-time-* functions. +Returns .ics formatted VEVENT string." + (let* ((dtstart (if (= (length start) 5) + (test-calendar-sync-ics-datetime start) + (test-calendar-sync-ics-date start))) + (dtend (when end + (if (= (length end) 5) + (test-calendar-sync-ics-datetime end) + (test-calendar-sync-ics-date end))))) + (concat "BEGIN:VEVENT\n" + "SUMMARY:" summary "\n" + "DTSTART:" dtstart "\n" + (when dtend (concat "DTEND:" dtend "\n")) + (when description (concat "DESCRIPTION:" description "\n")) + (when location (concat "LOCATION:" location "\n")) + "END:VEVENT"))) + +(defun test-calendar-sync-make-ics (&rest events) + "Create complete .ics file with EVENTS. +Each event should be a VEVENT string from `test-calendar-sync-make-vevent'." + (concat "BEGIN:VCALENDAR\n" + "VERSION:2.0\n" + "PRODID:-//Test//Test//EN\n" + (string-join events "\n") + "\nEND:VCALENDAR")) + +(provide 'testutil-calendar-sync) +;;; testutil-calendar-sync.el ends here |
