Skip to content

Commit 10d6889

Browse files
authored
Add the possibility to skip dates for recurring events (#24)
* Loop through all except_on dates and remove them from recurrent event Signed-off-by: Eduard Itrich <eduard@itrich.net> * Fix CI errors * Combine EXDATE into a comma seperated list Signed-off-by: Eduard Itrich <eduard@itrich.net> * Add time to EXDATE * Add TZID to EXDATE if timezone is given
1 parent aa811c4 commit 10d6889

3 files changed

Lines changed: 70 additions & 3 deletions

File tree

example/test_calendar.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ events:
3333
weeks: 1
3434
until: 2022-12-31 # required
3535

36+
- summary: Recurring event with exception
37+
begin: 2022-07-01 10:00:00
38+
duration: {minutes: 60}
39+
repeat:
40+
interval:
41+
# seconds, minutes, hours, days, weeks, months, years
42+
hours: 4
43+
until: 2022-07-31 # required
44+
except_on:
45+
- 2022-07-13
46+
- 2022-07-14 06:00:00
47+
3648
# All-day event
3749
- summary: Earth Day
3850
begin: 2021-04-22

tests/test_events.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,33 @@ def test_rrule():
6565
assert "RRULE:FREQ=YEARLY;UNTIL=20300422T000000" in event_str
6666

6767

68+
def test_exception():
69+
event = event_from_yaml(
70+
parse_yaml(
71+
"""
72+
summary: Recurring event with exception
73+
timezone: America/Los_Angeles
74+
begin: 2022-07-01 10:00:00
75+
duration: {minutes: 60}
76+
repeat:
77+
interval:
78+
hours: 4
79+
until: 2022-07-31
80+
except_on:
81+
- 2022-07-13
82+
- 2022-07-14 06:00:00
83+
"""
84+
)
85+
)
86+
event_str = event.serialize()
87+
# DTEND exists and is the next day, calendar tools import this
88+
# correctly as being a one-day event
89+
assert (
90+
"EXDATE;TZID=/ics.py/2020.1/America/Los_Angeles:20220713,20220714T060000"
91+
in event_str
92+
)
93+
94+
6895
def test_event_with_time_range():
6996
event = event_from_yaml(
7097
parse_yaml(

yaml2ics.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
55
CLI to convert yaml into ics.
66
"""
7+
import datetime
78
import os
89
import sys
9-
from datetime import datetime, tzinfo
1010

1111
import dateutil
1212
import dateutil.rrule
@@ -25,7 +25,14 @@
2525
}
2626

2727

28-
def event_from_yaml(event_yaml: dict, tz: tzinfo = None) -> ics.Event:
28+
def strfexception(exdate):
29+
if isinstance(exdate, datetime.datetime):
30+
return datetime.datetime.strftime(exdate, "%Y%m%dT%H%M%S")
31+
elif isinstance(exdate, datetime.date):
32+
return datetime.datetime.strftime(exdate, "%Y%m%d")
33+
34+
35+
def event_from_yaml(event_yaml: dict, tz: datetime.tzinfo = None) -> ics.Event:
2936
d = event_yaml
3037
repeat = d.pop("repeat", None)
3138
if "timezone" in d:
@@ -90,7 +97,28 @@ def event_from_yaml(event_yaml: dict, tz: tzinfo = None) -> ics.Event:
9097
)
9198
)
9299

93-
event.dtstamp = datetime.utcnow().replace(tzinfo=dateutil.tz.UTC)
100+
if "except_on" in repeat:
101+
exdates = map(
102+
lambda exdate: strfexception(exdate),
103+
repeat["except_on"],
104+
)
105+
if tz:
106+
event.extra.append(
107+
ics.ContentLine(
108+
name="EXDATE",
109+
params={"TZID": [str(ics.Timezone.from_tzinfo(tz))]},
110+
value=",".join(exdates),
111+
)
112+
)
113+
else:
114+
event.extra.append(
115+
ics.ContentLine(
116+
name="EXDATE",
117+
value=",".join(exdates),
118+
)
119+
)
120+
121+
event.dtstamp = datetime.datetime.utcnow().replace(tzinfo=dateutil.tz.UTC)
94122
if tz and event.floating and not event.all_day:
95123
event.replace_timezone(tz)
96124
return event

0 commit comments

Comments
 (0)