Skip to content

Commit fea0dcd

Browse files
committed
Correctly infer timezone from event begin time
Timezones can come from the YAML file (`timezone: ...`), or from the event start time: `2025-07-15 17:00:00 +00:00` However it is specified, this timezone should be propagated to the RRULE. This PR is also a bit more strict, failing when the timezone specifier is unrecognized.
1 parent b31ea58 commit fea0dcd

File tree

2 files changed

+21
-6
lines changed

2 files changed

+21
-6
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ events:
6060
In this meeting we will ...
6161
```
6262
63+
Valid timezones are listed at https://datetime.app/iana-timezones
64+
6365
## Contributing
6466
6567
Contributions are welcomed! This project is still in active development

yaml2ics.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import dateutil.rrule
1414
import ics
1515
import yaml
16-
from dateutil.tz import gettz
16+
from dateutil.tz import gettz as _gettz
1717

1818
interval_type = {
1919
"seconds": dateutil.rrule.SECONDLY,
@@ -41,6 +41,13 @@ def utcnow():
4141
return datetime.datetime.utcnow().replace(tzinfo=dateutil.tz.UTC)
4242

4343

44+
def gettz(tzname):
45+
tz = _gettz(tzname)
46+
if tz is None:
47+
raise ValueError(f"Invalid timezone encountered: `{tzname}`")
48+
return tz
49+
50+
4451
# See RFC2445, 4.8.5 REcurrence Component Properties
4552
# This function can be used to add a list of e.g. exception dates (EXDATE) or
4653
# recurrence dates (RDATE) to a reoccurring event
@@ -62,7 +69,8 @@ def event_from_yaml(event_yaml: dict, tz: datetime.tzinfo = None) -> ics.Event:
6269
ics_custom = d.pop("ics", None)
6370

6471
if "timezone" in d:
65-
tz = gettz(d.pop("timezone"))
72+
tzname = d.pop("timezone")
73+
tz = gettz(tzname)
6674

6775
# Strip all string values, since they often end on `\n`
6876
for key in d:
@@ -76,6 +84,15 @@ def event_from_yaml(event_yaml: dict, tz: datetime.tzinfo = None) -> ics.Event:
7684
# organizer, geo, classification
7785
event = ics.Event(**d)
7886

87+
event.dtstamp = utcnow()
88+
if tz and event.floating and not event.all_day:
89+
event.replace_timezone(tz)
90+
91+
# At this point, we are sure that our event has a timezone
92+
# Either it was set in the YAML file under `timezone: ...`,
93+
# or it was inferred from event start time.
94+
tz = event.timespan.begin_time.tzinfo
95+
7996
# Handle all-day events
8097
if not ("duration" in d or "end" in d):
8198
event.make_all_day()
@@ -129,10 +146,6 @@ def event_from_yaml(event_yaml: dict, tz: datetime.tzinfo = None) -> ics.Event:
129146
rdates = [datetime2utc(rdate) for rdate in repeat["also_on"]]
130147
add_recurrence_property(event, "RDATE", rdates, tz)
131148

132-
event.dtstamp = utcnow()
133-
if tz and event.floating and not event.all_day:
134-
event.replace_timezone(tz)
135-
136149
if ics_custom:
137150
for line in ics_custom.split("\n"):
138151
if not line:

0 commit comments

Comments
 (0)