From 01a44952e3f20b6e895cc7f75fb5d840d24b9878 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Fri, 9 Oct 2026 08:38:17 -0400 Subject: [PATCH 1/2] khal: generate a custom UID to events without one assigned The `UID` property is optional in RFC2445. --- khal/controllers.py | 2 +- khal/khalendar/event.py | 5 ++++- tests/event_test.py | 5 +++++ tests/ics/event_uid_missing.ics | 8 ++++++++ 4 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 tests/ics/event_uid_missing.ics diff --git a/khal/controllers.py b/khal/controllers.py index 6148f29ec..54e98903b 100644 --- a/khal/controllers.py +++ b/khal/controllers.py @@ -765,7 +765,7 @@ def print_ics(conf, name, ics, format): events = [item for item in cal.walk() if item.name == "VEVENT"] events_grouped = defaultdict(list) for event in events: - events_grouped[event["UID"]].append(event) + events_grouped[event.get("UID", f'khal-vevent-without-uid-{id(event)}')].append(event) vevents = [] for uid in events_grouped: diff --git a/khal/khalendar/event.py b/khal/khalendar/event.py index 8fda097e5..51c3aaa22 100644 --- a/khal/khalendar/event.py +++ b/khal/khalendar/event.py @@ -347,7 +347,10 @@ def duration(self) -> dt.timedelta: @property def uid(self) -> str: - return self._vevents[self.ref]["UID"] + try: + return self._vevents[self.ref]["UID"] + except KeyError: + return f'khal-vevent-without-uid-{id(self.ref)}' @property def organizer(self) -> str: diff --git a/tests/event_test.py b/tests/event_test.py index c33e4ed94..de7c05eba 100644 --- a/tests/event_test.py +++ b/tests/event_test.py @@ -736,6 +736,11 @@ def test_sort_event_summary(): assert event1 < event2 +def test_uid_missing(): + event_uid = _get_text("event_uid_missing") + assert event_uid.uid.startswith('khal-vevent-without-uid-') + + def test_create_timezone_in_future(): """Events too far into the future (after the next DST transition) used to be created with invalid timezones""" diff --git a/tests/ics/event_uid_missing.ics b/tests/ics/event_uid_missing.ics new file mode 100644 index 000000000..123422e26 --- /dev/null +++ b/tests/ics/event_uid_missing.ics @@ -0,0 +1,8 @@ +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SUMMARY:Missing uid +DTSTART;TZID=Europe/Berlin:20140707T090000 +DTEND;TZID=Europe/Berlin:20140707T140000 +END:VEVENT +END:VCALENDAR From 6e5cb5b826ee3010bce2632dc1580e3285768db5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 12:46:11 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- khal/controllers.py | 2 +- khal/khalendar/event.py | 2 +- tests/event_test.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/khal/controllers.py b/khal/controllers.py index 54e98903b..ae10c6059 100644 --- a/khal/controllers.py +++ b/khal/controllers.py @@ -765,7 +765,7 @@ def print_ics(conf, name, ics, format): events = [item for item in cal.walk() if item.name == "VEVENT"] events_grouped = defaultdict(list) for event in events: - events_grouped[event.get("UID", f'khal-vevent-without-uid-{id(event)}')].append(event) + events_grouped[event.get("UID", f"khal-vevent-without-uid-{id(event)}")].append(event) vevents = [] for uid in events_grouped: diff --git a/khal/khalendar/event.py b/khal/khalendar/event.py index 51c3aaa22..029c5aac8 100644 --- a/khal/khalendar/event.py +++ b/khal/khalendar/event.py @@ -350,7 +350,7 @@ def uid(self) -> str: try: return self._vevents[self.ref]["UID"] except KeyError: - return f'khal-vevent-without-uid-{id(self.ref)}' + return f"khal-vevent-without-uid-{id(self.ref)}" @property def organizer(self) -> str: diff --git a/tests/event_test.py b/tests/event_test.py index de7c05eba..d1a8bdb9b 100644 --- a/tests/event_test.py +++ b/tests/event_test.py @@ -738,7 +738,7 @@ def test_sort_event_summary(): def test_uid_missing(): event_uid = _get_text("event_uid_missing") - assert event_uid.uid.startswith('khal-vevent-without-uid-') + assert event_uid.uid.startswith("khal-vevent-without-uid-") def test_create_timezone_in_future():