diff --git a/example/test_calendar.yaml b/example/test_calendar.yaml index db4f2c5..8901756 100644 --- a/example/test_calendar.yaml +++ b/example/test_calendar.yaml @@ -1,12 +1,15 @@ +meta: + tz: America/Los_Angeles + events: - summary: Event of the Century - begin: 2021-09-21 15:00:00 -07:00 - end: 2021-09-21 15:30:00 -07:00 + begin: 2021-09-21 15:00:00 + end: 2021-09-21 15:30:00 description: | Meet the team on the northern side of the field. - summary: Half-an-hour meeting - begin: 2021-09-23 15:00:00 + begin: 2021-09-23 15:00:00 -07:00 duration: minutes: 30 location: | diff --git a/tests/test_calendar.py b/tests/test_calendar.py index 46b9e7e..3240f5b 100644 --- a/tests/test_calendar.py +++ b/tests/test_calendar.py @@ -1,3 +1,4 @@ +import datetime import io import textwrap @@ -28,3 +29,39 @@ def test_calendar_event(): assert cal_str.startswith('BEGIN:VCALENDAR') assert 'SUMMARY:Earth Day' in cal_str assert cal_str.endswith('END:VCALENDAR') + +def test_calendar_default_timezone(): + cal = files_to_calendar( + [io.StringIO(textwrap.dedent( + ''' + meta: + tz: Europe/Helsinki + + events: + - summary: New year's day + begin: 2022-01-01 00:00:00 + duration: {hours: 1} + + - summary: February 1 + begin: 2022-02-01 00:00:00 +02:00 + duration: {hours: 1} + ''' + ))] + ) + cal_str = cal.serialize() + assert cal_str.startswith('BEGIN:VCALENDAR') + assert 'SUMMARY:New year' in cal_str + # It is possible that the ics-py TZID string changes, but hopefully this + # substring is fairly safe to test against. + assert 'Europe/Helsinki:20220101T000000' in cal_str + # Second event: explicit UTC offset specified. + assert '"UTC+02:00":20220201T000000' in cal_str + assert cal_str.endswith('END:VCALENDAR') + + # Test again by normalizing to UTC. Helsinki is two hours ahead, so the + # times should be 22:00:00. + cal.normalize(datetime.timezone.utc) + cal_norm_str = cal.serialize() + # 1 Feb midnight + assert 'DTSTART:20211231T220000Z' # 1 jan + assert 'DTSTART:20220131T220000Z' # 1 feb diff --git a/yaml2ics.py b/yaml2ics.py index 7796c60..6aca92b 100644 --- a/yaml2ics.py +++ b/yaml2ics.py @@ -1,11 +1,12 @@ import yaml import sys import os -from datetime import datetime +from datetime import datetime, tzinfo import ics import dateutil import dateutil.rrule +from dateutil.tz import gettz interval_type = { @@ -19,7 +20,7 @@ } -def event_from_yaml(event_yaml: dict) -> ics.Event: +def event_from_yaml(event_yaml: dict, tz: tzinfo=None) -> ics.Event: d = event_yaml repeat = d.pop('repeat', None) @@ -73,6 +74,8 @@ def event_from_yaml(event_yaml: dict) -> ics.Event: )) event.dtstamp = datetime.utcnow().replace(tzinfo=dateutil.tz.UTC) + if tz and event.floating: + event.replace_timezone(tz) return event @@ -85,13 +88,18 @@ def events_to_calendar(events: list) -> str: def files_to_calendar(files: list) -> ics.Calendar: """'main' function: list of files to our result""" all_events = [ ] + name = None for f in files: if hasattr(f, 'read'): calendar_yaml = yaml.load(f.read(), Loader=yaml.FullLoader) else: calendar_yaml = yaml.load(open(f, 'r'), Loader=yaml.FullLoader) + tz = None + if 'meta' in calendar_yaml: + if 'tz' in calendar_yaml['meta']: + tz = gettz(calendar_yaml['meta']['tz']) for event in calendar_yaml['events']: - all_events.append(event_from_yaml(event)) + all_events.append(event_from_yaml(event, tz=tz)) calendar = events_to_calendar(all_events) return calendar