diff --git a/homeassistant/components/caldav/calendar.py b/homeassistant/components/caldav/calendar.py index 2d5c7217043..3fe5aab59c8 100644 --- a/homeassistant/components/caldav/calendar.py +++ b/homeassistant/components/caldav/calendar.py @@ -1,7 +1,7 @@ """Support for WebDav Calendar.""" from __future__ import annotations -from datetime import datetime, timedelta +from datetime import date, datetime, timedelta import logging import re @@ -185,8 +185,8 @@ class WebDavCalendarData: event_list.append( CalendarEvent( summary=self.get_attr_value(vevent, "summary") or "", - start=vevent.dtstart.value, - end=self.get_end_date(vevent), + start=self.to_local(vevent.dtstart.value), + end=self.to_local(self.get_end_date(vevent)), location=self.get_attr_value(vevent, "location"), description=self.get_attr_value(vevent, "description"), ) @@ -269,8 +269,8 @@ class WebDavCalendarData: ) self.event = CalendarEvent( summary=summary, - start=vevent.dtstart.value, - end=self.get_end_date(vevent), + start=self.to_local(vevent.dtstart.value), + end=self.to_local(self.get_end_date(vevent)), location=self.get_attr_value(vevent, "location"), description=self.get_attr_value(vevent, "description"), ) @@ -308,15 +308,23 @@ class WebDavCalendarData: def to_datetime(obj): """Return a datetime.""" if isinstance(obj, datetime): - if obj.tzinfo is None: - # floating value, not bound to any time zone in particular - # represent same time regardless of which time zone is currently being observed - return obj.replace(tzinfo=dt.DEFAULT_TIME_ZONE) - return obj + return WebDavCalendarData.to_local(obj) return dt.dt.datetime.combine(obj, dt.dt.time.min).replace( tzinfo=dt.DEFAULT_TIME_ZONE ) + @staticmethod + def to_local(obj: datetime | date) -> datetime | date: + """Return a datetime as a local datetime, leaving dates unchanged. + + This handles giving floating times a timezone for comparison + with all day events and dropping the custom timezone object + used by the caldav client and dateutil so the datetime can be copied. + """ + if isinstance(obj, datetime): + return dt.as_local(obj) + return obj + @staticmethod def get_attr_value(obj, attribute): """Return the value of the attribute if defined.""" diff --git a/tests/components/caldav/test_calendar.py b/tests/components/caldav/test_calendar.py index e9c58034cbe..c7031fa9c04 100644 --- a/tests/components/caldav/test_calendar.py +++ b/tests/components/caldav/test_calendar.py @@ -226,6 +226,35 @@ DTEND:20151127T003000Z RRULE:FREQ=HOURLY;INTERVAL=1;COUNT=12 END:VEVENT END:VCALENDAR +""", + """BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Global Corp.//CalDAV Client//EN +BEGIN:VTIMEZONE +TZID:Europe/London +BEGIN:STANDARD +DTSTART:19961027T020000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +TZNAME:GMT +TZOFFSETFROM:+0100 +TZOFFSETTO:+0000 +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19810329T010000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +TZNAME:BST +TZOFFSETFROM:+0000 +TZOFFSETTO:+0100 +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +UID:15 +DTSTAMP:20221125T000000Z +DTSTART;TZID=Europe/London:20221127T000000 +DTEND;TZID=Europe/London:20221127T003000 +SUMMARY:Event with a provided Timezone +END:VEVENT +END:VCALENDAR """, ] @@ -929,7 +958,7 @@ async def test_get_events(hass, calendar, get_api_events): await hass.async_block_till_done() events = await get_api_events("calendar.private") - assert len(events) == 15 + assert len(events) == 16 assert calendar.call