From ae41547b73e4d1354ec1c43dd0021531aeff564c Mon Sep 17 00:00:00 2001
From: Allen Porter <allen@thebends.org>
Date: Tue, 28 Mar 2023 03:25:44 -0700
Subject: [PATCH] Update calendar to always request start/end dates in local
 time rather than UTC (#90386)

---
 homeassistant/components/calendar/__init__.py |  2 +-
 homeassistant/components/google/calendar.py   |  4 +--
 .../components/local_calendar/calendar.py     | 11 +++----
 .../local_calendar/test_calendar.py           | 33 ++++++++++++++++---
 4 files changed, 37 insertions(+), 13 deletions(-)

diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py
index 9af32446566..0b1c37cea5f 100644
--- a/homeassistant/components/calendar/__init__.py
+++ b/homeassistant/components/calendar/__init__.py
@@ -523,7 +523,7 @@ class CalendarEventView(http.HomeAssistantView):
 
         try:
             calendar_event_list = await entity.async_get_events(
-                request.app["hass"], start_date, end_date
+                request.app["hass"], dt.as_local(start_date), dt.as_local(end_date)
             )
         except HomeAssistantError as err:
             _LOGGER.debug("Error reading events: %s", err)
diff --git a/homeassistant/components/google/calendar.py b/homeassistant/components/google/calendar.py
index 1e1072940ad..363b75c2c54 100644
--- a/homeassistant/components/google/calendar.py
+++ b/homeassistant/components/google/calendar.py
@@ -283,8 +283,8 @@ class CalendarSyncUpdateCoordinator(DataUpdateCoordinator[Timeline]):
                 "Unable to get events: Sync from server has not completed"
             )
         return self.data.overlapping(
-            dt_util.as_local(start_date),
-            dt_util.as_local(end_date),
+            start_date,
+            end_date,
         )
 
     @property
diff --git a/homeassistant/components/local_calendar/calendar.py b/homeassistant/components/local_calendar/calendar.py
index 718c65ffce2..4b6d9444fd8 100644
--- a/homeassistant/components/local_calendar/calendar.py
+++ b/homeassistant/components/local_calendar/calendar.py
@@ -85,17 +85,16 @@ class LocalCalendarEntity(CalendarEntity):
         self, hass: HomeAssistant, start_date: datetime, end_date: datetime
     ) -> list[CalendarEvent]:
         """Get all events in a specific time frame."""
-        events = self._calendar.timeline_tz(dt_util.DEFAULT_TIME_ZONE).overlapping(
-            dt_util.as_local(start_date),
-            dt_util.as_local(end_date),
+        events = self._calendar.timeline_tz(start_date.tzinfo).overlapping(
+            start_date,
+            end_date,
         )
         return [_get_calendar_event(event) for event in events]
 
     async def async_update(self) -> None:
         """Update entity state with the next upcoming event."""
-        events = self._calendar.timeline_tz(dt_util.DEFAULT_TIME_ZONE).active_after(
-            dt_util.now()
-        )
+        now = dt_util.now()
+        events = self._calendar.timeline_tz(now.tzinfo).active_after(now)
         if event := next(events, None):
             self._event = _get_calendar_event(event)
         else:
diff --git a/tests/components/local_calendar/test_calendar.py b/tests/components/local_calendar/test_calendar.py
index 6bdb58cf65d..a2f13ea289d 100644
--- a/tests/components/local_calendar/test_calendar.py
+++ b/tests/components/local_calendar/test_calendar.py
@@ -37,10 +37,27 @@ async def test_empty_calendar(
     }
 
 
+@pytest.mark.parametrize(
+    ("dtstart", "dtend"),
+    [
+        ("1997-07-14T18:00:00+01:00", "1997-07-15T05:00:00+01:00"),
+        ("1997-07-14T17:00:00+00:00", "1997-07-15T04:00:00+00:00"),
+        ("1997-07-14T11:00:00-06:00", "1997-07-14T22:00:00-06:00"),
+        ("1997-07-14T10:00:00-07:00", "1997-07-14T21:00:00-07:00"),
+    ],
+)
 async def test_api_date_time_event(
-    ws_client: ClientFixture, setup_integration: None, get_events: GetEventsFn
+    ws_client: ClientFixture,
+    setup_integration: None,
+    get_events: GetEventsFn,
+    dtstart: str,
+    dtend: str,
 ) -> None:
-    """Test an event with a start/end date time."""
+    """Test an event with a start/end date time.
+
+    Events created in various timezones are ultimately returned relative
+    to local home assistant timezone.
+    """
     client = await ws_client()
     await client.cmd_result(
         "create",
@@ -48,8 +65,8 @@ async def test_api_date_time_event(
             "entity_id": TEST_ENTITY,
             "event": {
                 "summary": "Bastille Day Party",
-                "dtstart": "1997-07-14T17:00:00+00:00",
-                "dtend": "1997-07-15T04:00:00+00:00",
+                "dtstart": dtstart,
+                "dtend": dtend,
             },
         },
     )
@@ -63,6 +80,8 @@ async def test_api_date_time_event(
         }
     ]
 
+    # Query events in UTC
+
     # Time range before event
     events = await get_events("1997-07-13T00:00:00Z", "1997-07-14T16:00:00Z")
     assert len(events) == 0
@@ -77,6 +96,12 @@ async def test_api_date_time_event(
     events = await get_events("1997-07-15T03:00:00Z", "1997-07-15T06:00:00Z")
     assert len(events) == 1
 
+    # Query events overlapping with start and end but in another timezone
+    events = await get_events("1997-07-12T23:00:00-01:00", "1997-07-14T17:00:00-01:00")
+    assert len(events) == 1
+    events = await get_events("1997-07-15T02:00:00-01:00", "1997-07-15T05:00:00-01:00")
+    assert len(events) == 1
+
 
 async def test_api_date_event(
     ws_client: ClientFixture, setup_integration: None, get_events: GetEventsFn
-- 
GitLab