diff --git a/CHANGES.rst b/CHANGES.rst index 51b0119..cccee37 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -21,7 +21,10 @@ New features: Bug fixes: -- ... +- Calendar components are now properly compared + Ref: #550 + Fixes: #526 + [jacadzaca] 5.0.7 (2023-05-29) ------------------ diff --git a/src/icalendar/cal.py b/src/icalendar/cal.py index 35a288b..4088971 100644 --- a/src/icalendar/cal.py +++ b/src/icalendar/cal.py @@ -436,6 +436,25 @@ class Component(CaselessDict): subs = ', '.join(str(it) for it in self.subcomponents) return f"{self.name or type(self).__name__}({dict(self)}{', ' + subs if subs else ''})" + def __eq__(self, other): + if not len(self.subcomponents) == len(other.subcomponents): + return False + + properties_equal = super().__eq__(other) + if not properties_equal: + return False + + # The subcomponents might not be in the same order, + # neither there's a natural key we can sort the subcomponents by nor + # are the subcomponent types hashable, so we cant put them in a set to + # check for set equivalence. We have to iterate over the subcomponents + # and look for each of them in the list. + for subcomponent in self.subcomponents: + if subcomponent not in other.subcomponents: + return False + + return True + ####################################### # components defined in RFC 5545 diff --git a/src/icalendar/tests/calendars/issue_526_calendar_with_different_events.ics b/src/icalendar/tests/calendars/issue_526_calendar_with_different_events.ics new file mode 100644 index 0000000..67b32f5 --- /dev/null +++ b/src/icalendar/tests/calendars/issue_526_calendar_with_different_events.ics @@ -0,0 +1,18 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:icalendar-2023 +BEGIN:VEVENT +UID:ical-jacadzaca-3 +SUMMARY: Some very different event ':' +DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T160000 +DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T163000 +DTSTAMP:20211004T150245Z +END:VEVENT +BEGIN:VEVENT +UID:ical-jacadzaca-4 +SUMMARY: Some very different other event +DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T164000 +DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T165000 +DTSTAMP:20211004T150245Z +END:VEVENT +END:VCALENDAR diff --git a/src/icalendar/tests/calendars/issue_526_calendar_with_event_subset.ics b/src/icalendar/tests/calendars/issue_526_calendar_with_event_subset.ics new file mode 100644 index 0000000..4624f2f --- /dev/null +++ b/src/icalendar/tests/calendars/issue_526_calendar_with_event_subset.ics @@ -0,0 +1,11 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:icalendar-2023 +BEGIN:VEVENT +UID:1 +SUMMARY: Some event ':' +DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T160000 +DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T163000 +DTSTAMP:20211004T150245Z +END:VEVENT +END:VCALENDAR diff --git a/src/icalendar/tests/calendars/issue_526_calendar_with_events.ics b/src/icalendar/tests/calendars/issue_526_calendar_with_events.ics new file mode 100644 index 0000000..2406497 --- /dev/null +++ b/src/icalendar/tests/calendars/issue_526_calendar_with_events.ics @@ -0,0 +1,18 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:icalendar-2023 +BEGIN:VEVENT +UID:1 +SUMMARY: Some event ':' +DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T160000 +DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T163000 +DTSTAMP:20211004T150245Z +END:VEVENT +BEGIN:VEVENT +UID:2 +SUMMARY: Some other event +DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T164000 +DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T165000 +DTSTAMP:20211004T150245Z +END:VEVENT +END:VCALENDAR diff --git a/src/icalendar/tests/calendars/issue_526_calendar_with_shuffeled_events.ics b/src/icalendar/tests/calendars/issue_526_calendar_with_shuffeled_events.ics new file mode 100644 index 0000000..6833bda --- /dev/null +++ b/src/icalendar/tests/calendars/issue_526_calendar_with_shuffeled_events.ics @@ -0,0 +1,18 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:icalendar-2023 +BEGIN:VEVENT +UID:2 +SUMMARY: Some other event +DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T164000 +DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T165000 +DTSTAMP:20211004T150245Z +END:VEVENT +BEGIN:VEVENT +UID:1 +SUMMARY: Some event ':' +DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T160000 +DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T163000 +DTSTAMP:20211004T150245Z +END:VEVENT +END:VCALENDAR diff --git a/src/icalendar/tests/test_unit_cal.py b/src/icalendar/tests/test_unit_cal.py index 3d09f04..410abce 100644 --- a/src/icalendar/tests/test_unit_cal.py +++ b/src/icalendar/tests/test_unit_cal.py @@ -1,7 +1,10 @@ +import itertools from datetime import datetime from datetime import timedelta import unittest +import pytest + import icalendar import pytz import re @@ -455,3 +458,33 @@ class TestCal(unittest.TestCase): icalendar.vUTCOffset.ignore_exceptions = True self.assertEqual(icalendar.Calendar.from_ical(cal_str).to_ical(), cal_str) icalendar.vUTCOffset.ignore_exceptions = False + + +@pytest.mark.parametrize( + 'calendar, other_calendar', + itertools.product([ + 'issue_156_RDATE_with_PERIOD_TZID_khal', + 'issue_156_RDATE_with_PERIOD_TZID_khal_2', + 'issue_178_custom_component_contains_other', + 'issue_178_custom_component_inside_other', + 'issue_526_calendar_with_events', + 'issue_526_calendar_with_different_events', + 'issue_526_calendar_with_event_subset', + ], repeat=2) +) +def test_comparing_calendars(calendars, calendar, other_calendar): + are_calendars_equal = calendars[calendar] == calendars[other_calendar] + are_calendars_actually_equal = calendar == other_calendar + assert are_calendars_equal == are_calendars_actually_equal + + +@pytest.mark.parametrize('calendar, shuffeled_calendar', [ + ( + 'issue_526_calendar_with_events', + 'issue_526_calendar_with_shuffeled_events', + ), +]) +def test_calendars_with_same_subcomponents_in_different_order_are_equal(calendars, calendar, shuffeled_calendar): + assert not calendars[calendar].subcomponents == calendars[shuffeled_calendar].subcomponents + assert calendars[calendar] == calendars[shuffeled_calendar] +