1- from typing import Optional
2-
3- from icalendar import Calendar , Event
1+ from icalendar import Calendar , Event , Journal , Todo
42from x_wr_timezone import to_standard
53
64
@@ -20,19 +18,17 @@ class CalendarMerger:
2018 def __init__ (
2119 self ,
2220 calendars : list [Calendar ],
23- prodid : Optional [ str ] = None ,
21+ prodid : str | None = None ,
2422 version : str = "2.0" ,
2523 calscale : str = "GREGORIAN" ,
26- method : Optional [ str ] = None ,
24+ method : str | None = None ,
2725 ):
2826 self .merged_calendar = Calendar ()
2927
30- # Set required properties
3128 self .merged_calendar .add ("prodid" , prodid or generate_default_prodid ())
3229 self .merged_calendar .add ("version" , version )
3330 self .merged_calendar .add ("calscale" , calscale )
3431
35- # Set optional properties if provided
3632 if method :
3733 self .merged_calendar .add ("method" , method )
3834
@@ -47,32 +43,78 @@ def add_calendar(self, calendar: Calendar) -> None:
4743
4844 def merge (self ) -> Calendar :
4945 """Merge the calendars."""
50- existing_uids : set [tuple [Optional [str ], int , Optional [str ]]] = set ()
46+ seen_events : set [tuple [str | None , int , str | None ]] = set ()
47+ seen_todos : set [tuple [str | None , int , str | None ]] = set ()
48+ seen_journals : set [str ] = set ()
5149 no_uid_events : list [Event ] = []
50+ no_uid_todos : list [Todo ] = []
51+ no_uid_journals : list [Journal ] = []
5252 tzids : set [str ] = set ()
5353 for cal in self .calendars :
54+ # .color resolves COLOR then X-APPLE-CALENDAR-COLOR (RFC 7986 §5.9)
55+ cal_color = cal .color
56+
57+ if cal_color and not self .merged_calendar .color :
58+ self .merged_calendar .color = cal_color
59+
5460 for timezone in cal .timezones :
5561 if timezone .tz_name not in tzids :
5662 self .merged_calendar .add_component (timezone )
5763 tzids .add (timezone .tz_name )
64+
5865 for event in cal .events :
5966 uid = event .get ("uid" , None )
6067 sequence = event .get ("sequence" , 0 )
6168 recurrence_id = event .get ("recurrence-id" , None )
62-
63- # Create a unique identifier for the component
6469 component_id = (uid , sequence , recurrence_id )
6570
6671 if uid is None :
6772 if event in no_uid_events :
6873 continue
6974 no_uid_events .append (event )
70- elif component_id in existing_uids :
75+ elif component_id in seen_events :
7176 continue
7277
73- existing_uids .add (component_id )
78+ if cal_color and not event .color :
79+ event .color = cal_color
80+
81+ seen_events .add (component_id )
7482 self .merged_calendar .add_component (event )
7583
84+ for todo in cal .todos :
85+ uid = todo .get ("uid" , None )
86+ recurrence_id = todo .get ("recurrence-id" , None )
87+ component_id = (uid , todo .get ("sequence" , 0 ), recurrence_id )
88+
89+ if uid is None :
90+ if todo in no_uid_todos :
91+ continue
92+ no_uid_todos .append (todo )
93+ elif component_id in seen_todos :
94+ continue
95+
96+ if cal_color and not todo .color :
97+ todo .color = cal_color
98+
99+ seen_todos .add (component_id )
100+ self .merged_calendar .add_component (todo )
101+
102+ for journal in cal .walk ("VJOURNAL" ):
103+ uid = journal .get ("uid" , None )
104+
105+ if uid is None :
106+ if journal in no_uid_journals :
107+ continue
108+ no_uid_journals .append (journal )
109+ elif uid in seen_journals :
110+ continue
111+
112+ if cal_color and not journal .color :
113+ journal .color = cal_color
114+
115+ seen_journals .add (uid )
116+ self .merged_calendar .add_component (journal )
117+
76118 return self .merged_calendar
77119
78120
0 commit comments