diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 66a6577..69beeba 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -97,8 +97,6 @@ def copy_to(self, new_start=None, uid=None): :param uid: UID of new event :return: new event """ - duration = self.end - self.start - if not new_start: new_start = self.start @@ -109,7 +107,11 @@ def copy_to(self, new_start=None, uid=None): ne.summary = self.summary ne.description = self.description ne.start = new_start - ne.end = (new_start + duration) + + if self.end: + duration = self.end - self.start + ne.end = (new_start + duration) + ne.all_day = (self.all_day and (new_start - self.start).seconds == 0) ne.uid = uid @@ -131,10 +133,10 @@ def create_event(component, tz=UTC): if component.get('dtend'): event.end = normalize(component.get('dtend').dt, tz=tz) - elif component.get('duration'): + elif component.get('duration'): # compute implicit end as start + duration event.end = event.start + component.get('duration').dt - else: - raise ValueError("Event has neither end, nor duration property.") + else: # compute implicit end as start + 0 + event.end = event.start event.summary = str(component.get('summary')) event.description = str(component.get('description')) diff --git a/test/test_data/duration.ics b/test/test_data/duration.ics new file mode 100644 index 0000000..b686406 --- /dev/null +++ b/test/test_data/duration.ics @@ -0,0 +1,19 @@ +BEGIN:VCALENDAR +BEGIN:VEVENT +DTSTART:20180110 +DURATION:P3D +DESCRIPTION:Event with duration (3 days), instead of explicit end. +SUMMARY:Duration Event +END:VEVENT +BEGIN:VEVENT +DTSTART:20180115T100000 +DURATION:PT3H +DESCRIPTION:Event with duration (3 hours), instead of explicit end. +SUMMARY:Duration Event +END:VEVENT +BEGIN:VEVENT +DTSTART:20180120T120000 +DESCRIPTION:Event without explicit dtend, nor duration property. +SUMMARY:Short event +END:VEVENT +END:VCALENDAR diff --git a/test/test_data/recurring.ics b/test/test_data/recurring.ics index 1ceb0ff..7c903fd 100644 --- a/test/test_data/recurring.ics +++ b/test/test_data/recurring.ics @@ -5,9 +5,18 @@ END:VTIMEZONE BEGIN:VEVENT DTSTART;TZID=Europe/Berlin:20181003T100000 DTEND;TZID=Europe/Berlin:20181003T120000 -DESCRIPTION:Event recurring on wednesday each week -SUMARRY:Recurring event +DESCRIPTION:Event recurring on wednesday each week, except on 2018-10-29 +SUMARRY:Recurring Event RRULE:FREQ=WEEKLY;BYDAY=MO EXDATE;TZID=Europe/Berlin:20181029T100000 END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Europe/Berlin:20180601T100000 +DTEND;TZID=Europe/Berlin:20180601T120000 +DESCRIPTION:Event recurring on friday each week, except on 2018-06-08/22 +SUMARRY:Recurring Event +RRULE:FREQ=WEEKLY;BYDAY=FR +EXDATE;TZID=Europe/Berlin:20180608T100000 +EXDATE;TZID=Europe/Berlin:20180622T100000 +END:VEVENT END:VCALENDAR diff --git a/test/test_icalevents.py b/test/test_icalevents.py index b7ad17b..f9ee2e6 100644 --- a/test/test_icalevents.py +++ b/test/test_icalevents.py @@ -1,7 +1,10 @@ import unittest from icalevents import icalevents -from datetime import date, timedelta +from datetime import date, timedelta, datetime from time import sleep +from dateutil.relativedelta import relativedelta +from dateutil.tz import UTC +from re import search class ICalEventsTests(unittest.TestCase): @@ -31,6 +34,25 @@ def test_events(self): self.assertEqual(len(evs), 2, "two events are found") + def test_events_duration(self): + ical = "test/test_data/duration.ics" + start = date(2018, 1, 1) + end = date(2018, 2, 1) + + evs = icalevents.events(file=ical, start=start, end=end) + + e1 = evs[0] + self.assertEqual(e1.start.day, 10, "explicit event start") + self.assertEqual(e1.end.day, 13, "implicit event end") + + e2 = evs[1] + self.assertEqual(e2.start.hour, 10, "explicit event start") + self.assertEqual(e2.end.hour, 13, "implicit event end") + + e3 = evs[2] + self.assertEqual(e3.start.hour, 12, "explicit event start") + self.assertEqual(e3.end.hour, 12, "implicit event end") + def test_events_recurring(self): ical = "test/test_data/recurring.ics" start = date(2018, 10, 15) @@ -47,6 +69,17 @@ def test_events_recurring(self): self.assertEqual(e2.start.tzinfo.utcoffset(e2.start), timedelta(seconds=3600), "check UTC offset without DST") self.assertEqual(e2.start.day, 5, "Check observance of exdate.") + + def test_events_exdates(self): + ical = "test/test_data/recurring.ics" + start = date(2018, 6, 1) + end = date(2018, 6, 30) + + evs = icalevents.events(file=ical, start=start, end=end) + + self.assertEqual(evs[0].start.day, 1, "check first recurrence.") + self.assertEqual(evs[1].start.day, 15, "check first exdate.") + self.assertEqual(evs[2].start.day, 29, "check second exdate.") def test_event_attributes(self): ical = "test/test_data/basic.ics" @@ -111,3 +144,23 @@ def test_string_data(self): self.assertTrue(icalevents.all_done(key), "request is finished") self.assertEqual(len(icalevents.latest_events(key)), 2, "two events are found") + + def test_event_str(self): + ical = "test/test_data/duration.ics" + start = date(2018, 1, 1) + end = date(2018, 2, 1) + n = datetime.now(UTC) + m = relativedelta(hour=0, minute=0, second=0, microsecond=0) + + evs = icalevents.events(file=ical, start=start, end=end) + + e1 = evs[0] + self.assertIsNotNone(search(r"ended", str(e1.copy_to(n - relativedelta(days=5) + m))), "stringify past event") + self.assertIsNotNone(search(r"today", str(e1.copy_to(n - relativedelta(days=1) + m))), "stringify ongoing event") + self.assertIsNotNone(search(r"days left", str(e1.copy_to(n + relativedelta(days=3) + m))), "stringify future event") + + e2 = evs[1] + self.assertIsNotNone(search(r"ended", str(e2.copy_to(n - relativedelta(hours=5)))), "stringify past event") + self.assertIsNotNone(search(r"now", str(e2.copy_to(n - relativedelta(hours=1)))), "stringify ongoing event") + self.assertIsNotNone(search(r"hours left", str(e2.copy_to(n + relativedelta(hours=3)))), "stringify future event") + self.assertIsNotNone(search(r"days left", str(e2.copy_to(n + relativedelta(days=3)))), "stringify future event") diff --git a/test/test_icalparser.py b/test/test_icalparser.py index c95745a..6b6b1a4 100644 --- a/test/test_icalparser.py +++ b/test/test_icalparser.py @@ -39,6 +39,7 @@ def test_time_left(self): def test_event_copy_to(self): new_start = datetime(year=2017, month=2, day=5, hour=12, minute=5, tzinfo=UTC) eventC = self.eventA.copy_to(new_start) + new_uid = 1234567890 self.assertNotEqual(eventC.uid, self.eventA.uid, "new event has new UID") self.assertEqual(eventC.start, new_start, "new event has new start") @@ -47,8 +48,8 @@ def test_event_copy_to(self): self.assertEqual(eventC.summary, self.eventA.summary, "copy to: summary") self.assertEqual(eventC.description, self.eventA.description, "copy to: description") - eventD = eventC.copy_to() - self.assertNotEqual(eventD.uid, eventC.uid, "new event has new UID") + eventD = eventC.copy_to(uid=new_uid) + self.assertEqual(eventD.uid, new_uid, "new event has specified UID") self.assertEqual(eventD.start, eventC.start, "new event has same start") self.assertEqual(eventD.end, eventC.end, "new event has same end") self.assertEqual(eventD.all_day, eventC.all_day, "new event is no all day event") @@ -84,3 +85,6 @@ def test_normalize(self): self.assertEqual(3, norm.second, "second") self.assertEqual(0, norm.microsecond, "microsecond") self.assertEqual(UTC, norm.tzinfo, "timezone") + + with self.assertRaises(ValueError, msg="type check effective"): + icalevents.icalparser.normalize(None)