Browse Source

recurring events brought to rfc4791

Georgiy 2 years ago
parent
commit
72103c30c2
1 changed files with 62 additions and 15 deletions
  1. 62 15
      radicale/app/report.py

+ 62 - 15
radicale/app/report.py

@@ -23,6 +23,7 @@ import posixpath
 import socket
 import copy
 import xml.etree.ElementTree as ET
+from vobject.base import ContentLine
 from http import client
 from typing import (
     Callable, Iterable, Iterator,
@@ -197,28 +198,74 @@ def _expand(
 ) -> List[ET.Element]:
     expanded = [element]
 
-    for component in item.vobject_item.components():
-        if component.name != 'VEVENT':
-            continue
+    if hasattr(item.vobject_item.vevent, "rrule"):
+        rulleset = item.vobject_item.vevent.getrruleset()
+        recurrences = rulleset.between(start, end)
+        recurring_item = _make_vobject_recurring_item(item)
+
+        expanded = []
+        for recurrence_dt in recurrences:
+            try:
+                delattr(recurring_item.vobject_item.vevent, 'recurrence-id')
+            except AttributeError:
+                pass
 
-        if hasattr(component, "rrule"):
-            rulleset = component.getrruleset()
-            recurrences = rulleset.between(start, end)
+            recurrence_utc = recurrence_dt.astimezone(datetime.timezone.utc)
 
-            expanded = []
-            for recurrence_dt in recurrences:
-                try:
-                    delattr(item.vobject_item.vevent, 'recurrence-id')
-                except AttributeError:
-                    pass
+            recurring_item.vobject_item.vevent.recurrence_id = ContentLine(
+                name='RECURRENCE-ID',
+                value=recurrence_utc.strftime('%Y%m%dT%H%M%SZ'), params={}
+            )
 
-                item.vobject_item.vevent.add('RECURRENCE-ID').value = recurrence_dt
-                element.text = item.vobject_item.serialize()
-                expanded.append(element)
+            element = copy.copy(element)
+            element.text = recurring_item.vobject_item.serialize()
+            expanded.append(element)
 
     return expanded
 
 
+def _make_vobject_recurring_item(
+        item: radicale_item.Item
+) -> radicale_item.Item:
+    # https://www.rfc-editor.org/rfc/rfc4791#section-9.6.5
+    # The returned calendar components MUST NOT use recurrence
+    #       properties (i.e., EXDATE, EXRULE, RDATE, and RRULE) and MUST NOT
+    #       have reference to or include VTIMEZONE components.  Date and local
+    #       time with reference to time zone information MUST be converted
+    #       into date with UTC time.
+
+    item = copy.copy(item)
+    vevent = item.vobject_item.vevent
+
+    start_utc = vevent.dtstart.value.astimezone(datetime.timezone.utc)
+    end_utc = vevent.dtend.value.astimezone(datetime.timezone.utc)
+
+    vevent.dtstart = ContentLine(
+        name='DTSTART',
+        value=start_utc.strftime('%Y%m%dT%H%M%SZ'), params={})
+    vevent.dtend = ContentLine(
+        name='DTEND',
+        value=end_utc.strftime('%Y%m%dT%H%M%SZ'), params={})
+
+    timezones_to_remove = []
+    for component in item.vobject_item.components():
+        if component.name == 'VTIMEZONE':
+            timezones_to_remove.append(component)
+
+    for timezone in timezones_to_remove:
+        item.vobject_item.remove(timezone)
+
+    try:
+        delattr(item.vobject_item.vevent, 'rrule')
+        delattr(item.vobject_item.vevent, 'exdate')
+        delattr(item.vobject_item.vevent, 'exrule')
+        delattr(item.vobject_item.vevent, 'rdate')
+    except AttributeError:
+        pass
+
+    return item
+
+
 def xml_item_response(base_prefix: str, href: str,
                       found_props: Sequence[ET.Element] = (),
                       not_found_props: Sequence[ET.Element] = (),