summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Bruederli <bruederli@kolabsys.com>2015-02-16 20:33:59 +0100
committerThomas Bruederli <bruederli@kolabsys.com>2015-02-16 20:33:59 +0100
commit2eaa92b373c72e677bd72ff2a721c0e0cdcae86d (patch)
tree30121b5e800e34ad8cf9fc0d22d959f78c8a9957
parentdc75af98a0caf858d5e35100f9b6a51bc4b21309 (diff)
downloadpykolab-2eaa92b373c72e677bd72ff2a721c0e0cdcae86d.tar.gz
Support RECURRENCE-ID property for iCal import/export
-rw-r--r--pykolab/xml/event.py64
-rw-r--r--tests/unit/test-003-event.py7
2 files changed, 66 insertions, 5 deletions
diff --git a/pykolab/xml/event.py b/pykolab/xml/event.py
index 6242eaf..78a26dd 100644
--- a/pykolab/xml/event.py
+++ b/pykolab/xml/event.py
@@ -58,6 +58,7 @@ def event_from_message(message):
class Event(object):
type = 'event'
+ thisandfuture = False
status_map = {
None: kolabformat.StatusUndefined,
@@ -191,8 +192,9 @@ class Event(object):
# NOTE: Make sure to list(set()) or duplicates may arise
for attr in list(set(event.singletons)):
- ical_getter = 'get_ical_%s' % (attr.lower())
- default_getter = 'get_%s' % (attr.lower())
+ _attr = attr.lower().replace('-', '')
+ ical_getter = 'get_ical_%s' % (_attr)
+ default_getter = 'get_%s' % (_attr)
retval = None
if hasattr(self, ical_getter):
retval = getattr(self, ical_getter)()
@@ -205,8 +207,9 @@ class Event(object):
# NOTE: Make sure to list(set()) or duplicates may arise
for attr in list(set(event.multiple)):
- ical_getter = 'get_ical_%s' % (attr.lower())
- default_getter = 'get_%s' % (attr.lower())
+ _attr = attr.lower().replace('-', '')
+ ical_getter = 'get_ical_%s' % (_attr)
+ default_getter = 'get_%s' % (_attr)
retval = None
if hasattr(self, ical_getter):
retval = getattr(self, ical_getter)()
@@ -575,6 +578,16 @@ class Event(object):
return [ comment ]
return None
+ def get_ical_recurrenceid(self):
+ rid = self.get_recurrence_id()
+ if isinstance(rid, datetime.datetime) or isinstance(rid, datetime.date):
+ prop = icalendar.vDatetime(rid)
+ if self.thisandfuture:
+ prop.params.update({'RANGE':'THISANDFUTURE'})
+ return prop
+
+ return None
+
def get_location(self):
return self.event.location()
@@ -616,6 +629,14 @@ class Event(object):
self.set_uid(uuid.uuid4())
return self.get_uid()
+ def get_recurrence_id(self):
+ self.thisandfuture = self.event.thisAndFuture();
+ return xmlutils.from_cdatetime(self.event.recurrenceID(), True)
+
+ def get_thisandfuture(self):
+ self.thisandfuture = self.event.thisAndFuture();
+ return self.thisandfuture
+
def get_sequence(self):
return self.event.sequence()
@@ -698,6 +719,7 @@ class Event(object):
attr = attr.replace('-', '')
ical_setter = 'set_ical_' + attr
default_setter = 'set_' + attr
+ params = value.params if hasattr(value, 'params') else {}
if isinstance(value, icalendar.vDDDTypes) and hasattr(value, 'dt'):
value = value.dt
@@ -706,6 +728,8 @@ class Event(object):
self.add_category(value)
elif attr == "class":
self.set_classification(value)
+ elif attr == "recurrenceid":
+ self.set_ical_recurrenceid(value, params)
elif hasattr(self, ical_setter):
getattr(self, ical_setter)(value)
elif hasattr(self, default_setter):
@@ -800,6 +824,13 @@ class Event(object):
def set_ical_uid(self, uid):
self.set_uid(str(uid))
+ def set_ical_recurrenceid(self, value, params):
+ try:
+ self.thisandfuture = params.get('RANGE', '') == 'THISANDFUTURE'
+ self.set_recurrence_id(value, self.thisandfuture)
+ except InvalidEventDateError, e:
+ pass
+
def set_lastmodified(self, _datetime=None):
valid_datetime = False
if isinstance(_datetime, datetime.date):
@@ -875,6 +906,27 @@ class Event(object):
self.uid = uid
self.event.setUid(str(uid))
+ def set_recurrence_id(self, _datetime, _thisandfuture=None):
+ valid_datetime = False
+ if isinstance(_datetime, datetime.date):
+ valid_datetime = True
+
+ if isinstance(_datetime, datetime.datetime):
+ # If no timezone information is passed on, use the one from event start
+ if _datetime.tzinfo == None:
+ _start = self.get_start()
+ _datetime = _datetime.replace(tzinfo=_start.tzinfo)
+
+ valid_datetime = True
+
+ if not valid_datetime:
+ raise InvalidEventDateError, _("Event recurrence-id needs datetime.datetime instance")
+
+ if _thisandfuture is None:
+ _thisandfuture = self.thisandfuture
+
+ self.event.setRecurrenceID(xmlutils.to_cdatetime(_datetime), _thisandfuture)
+
def set_transparency(self, transp):
return self.event.setTransparency(transp)
@@ -1194,11 +1246,13 @@ class Event(object):
instance = Event(from_string=str(self))
instance.set_start(next_start)
instance.set_recurrence(kolabformat.RecurrenceRule()) # remove recurrence rules
- instance.event.setRecurrenceID(instance.event.start(), False)
+ instance.event.setRecurrenceID(xmlutils.to_cdatetime(next_start), False)
next_end = self.get_occurence_end_date(next_start)
if next_end:
instance.set_end(next_end)
+ # TODO: copy data from matching exception
+
return instance
return None
diff --git a/tests/unit/test-003-event.py b/tests/unit/test-003-event.py
index 67481e3..876dd57 100644
--- a/tests/unit/test-003-event.py
+++ b/tests/unit/test-003-event.py
@@ -25,6 +25,7 @@ UID:7a35527d-f783-4b58-b404-b1389bd2fc57
DTSTAMP;VALUE=DATE-TIME:20140407T122311Z
CREATED;VALUE=DATE-TIME:20140407T122245Z
LAST-MODIFIED;VALUE=DATE-TIME:20140407T122311Z
+RECURRENCE-ID;TZID=Europe/Zurich;RANGE=THISANDFUTURE:20140523T110000
DTSTART;TZID=Europe/Zurich;VALUE=DATE-TIME:20140523T110000
DURATION:PT1H30M0S
RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=10
@@ -389,6 +390,8 @@ METHOD:REQUEST
self.assertIsInstance(event.get_exception_dates()[0], datetime.datetime)
self.assertEqual(len(event.get_alarms()), 1)
self.assertEqual(len(event.get_attachments()), 2)
+ self.assertIsInstance(event.get_recurrence_id(), datetime.datetime)
+ self.assertEqual(event.thisandfuture, True)
def test_018_ical_to_message(self):
event = event_from_ical(ical_event)
@@ -436,6 +439,7 @@ END:VEVENT
self.event.set_sequence(3)
self.event.set_classification('CONFIDENTIAL')
self.event.add_custom_property('X-Custom', 'check')
+ self.event.set_recurrence_id(datetime.datetime(2014, 05, 23, 11, 0, 0), True)
ical = icalendar.Calendar.from_ical(self.event.as_string_itip())
event = ical.walk('VEVENT')[0]
@@ -446,6 +450,8 @@ END:VEVENT
self.assertEqual(event['X-CUSTOM'], "check")
self.assertIsInstance(event['dtstamp'].dt, datetime.datetime)
self.assertEqual(event['class'], "CONFIDENTIAL")
+ self.assertIsInstance(event['recurrence-id'].dt, datetime.datetime)
+ self.assertEqual(event['recurrence-id'].params.get('RANGE'), 'THISANDFUTURE')
def test_019_to_message_itip(self):
self.event = Event()
@@ -529,6 +535,7 @@ END:VEVENT
# check get_next_instance() which returns a clone of the base event
next_instance = self.event.get_next_instance(next_date)
self.assertIsInstance(next_instance, Event)
+ self.assertIsInstance(next_instance.get_recurrence_id(), datetime.datetime)
self.assertEqual(self.event.get_summary(), next_instance.get_summary())
self.assertEqual(next_instance.get_start().month, 7)
self.assertFalse(next_instance.is_recurring())