Fix caldav TZ interpretation of all day events (#48642)

This commit is contained in:
TOM 2021-06-29 02:07:29 -05:00 committed by GitHub
parent bb4d3bfc60
commit 3f66709882
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 156 additions and 40 deletions

View file

@ -310,7 +310,9 @@ class WebDavCalendarData:
# represent same time regardless of which time zone is currently being observed
return obj.replace(tzinfo=dt.DEFAULT_TIME_ZONE)
return obj
return dt.as_local(dt.dt.datetime.combine(obj, dt.dt.time.min))
return dt.dt.datetime.combine(obj, dt.dt.time.min).replace(
tzinfo=dt.DEFAULT_TIME_ZONE
)
@staticmethod
def get_attr_value(obj, attribute):

View file

@ -222,6 +222,39 @@ CALDAV_CONFIG = {
"custom_calendars": [],
}
ORIG_TZ = dt.DEFAULT_TIME_ZONE
@pytest.fixture(autouse=True)
def reset_tz():
"""Restore the default TZ after test runs."""
yield
dt.DEFAULT_TIME_ZONE = ORIG_TZ
@pytest.fixture
def set_tz(request):
"""Set the default TZ to the one requested."""
return request.getfixturevalue(request.param)
@pytest.fixture
def utc():
"""Set the default TZ to UTC."""
dt.set_default_time_zone(dt.get_time_zone("UTC"))
@pytest.fixture
def new_york():
"""Set the default TZ to America/New_York."""
dt.set_default_time_zone(dt.get_time_zone("America/New_York"))
@pytest.fixture
def baghdad():
"""Set the default TZ to Asia/Baghdad."""
dt.set_default_time_zone(dt.get_time_zone("Asia/Baghdad"))
@pytest.fixture(autouse=True)
def mock_http(hass):
@ -524,30 +557,72 @@ async def test_no_result_with_filtering(mock_now, hass, calendar):
assert state.state == "off"
@patch("homeassistant.util.dt.now", return_value=_local_datetime(17, 30))
async def test_all_day_event_returned(mock_now, hass, calendar):
"""Test that the event lasting the whole day is returned."""
async def _day_event_returned(hass, calendar, config, date_time):
with patch("homeassistant.util.dt.now", return_value=date_time):
assert await async_setup_component(hass, "calendar", {"calendar": config})
await hass.async_block_till_done()
state = hass.states.get("calendar.private_private")
assert state.name == calendar.name
assert state.state == STATE_ON
assert dict(state.attributes) == {
"friendly_name": "Private",
"message": "This is an all day event",
"all_day": True,
"offset_reached": False,
"start_time": "2017-11-27 00:00:00",
"end_time": "2017-11-28 00:00:00",
"location": "Hamburg",
"description": "What a beautiful day",
}
@pytest.mark.parametrize("set_tz", ["utc", "new_york", "baghdad"], indirect=True)
async def test_all_day_event_returned_early(hass, calendar, set_tz):
"""Test that the event lasting the whole day is returned, if it's early in the local day."""
config = dict(CALDAV_CONFIG)
config["custom_calendars"] = [
{"name": "Private", "calendar": "Private", "search": ".*"}
]
assert await async_setup_component(hass, "calendar", {"calendar": config})
await hass.async_block_till_done()
await _day_event_returned(
hass,
calendar,
config,
datetime.datetime(2017, 11, 27, 0, 30).replace(tzinfo=dt.DEFAULT_TIME_ZONE),
)
state = hass.states.get("calendar.private_private")
assert state.name == calendar.name
assert state.state == STATE_ON
assert dict(state.attributes) == {
"friendly_name": "Private",
"message": "This is an all day event",
"all_day": True,
"offset_reached": False,
"start_time": "2017-11-27 00:00:00",
"end_time": "2017-11-28 00:00:00",
"location": "Hamburg",
"description": "What a beautiful day",
}
@pytest.mark.parametrize("set_tz", ["utc", "new_york", "baghdad"], indirect=True)
async def test_all_day_event_returned_mid(hass, calendar, set_tz):
"""Test that the event lasting the whole day is returned, if it's in the middle of the local day."""
config = dict(CALDAV_CONFIG)
config["custom_calendars"] = [
{"name": "Private", "calendar": "Private", "search": ".*"}
]
await _day_event_returned(
hass,
calendar,
config,
datetime.datetime(2017, 11, 27, 12, 30).replace(tzinfo=dt.DEFAULT_TIME_ZONE),
)
@pytest.mark.parametrize("set_tz", ["utc", "new_york", "baghdad"], indirect=True)
async def test_all_day_event_returned_late(hass, calendar, set_tz):
"""Test that the event lasting the whole day is returned, if it's late in the local day."""
config = dict(CALDAV_CONFIG)
config["custom_calendars"] = [
{"name": "Private", "calendar": "Private", "search": ".*"}
]
await _day_event_returned(
hass,
calendar,
config,
datetime.datetime(2017, 11, 27, 23, 30).replace(tzinfo=dt.DEFAULT_TIME_ZONE),
)
@patch("homeassistant.util.dt.now", return_value=_local_datetime(21, 45))
@ -655,33 +730,72 @@ async def test_event_rrule_endless(mock_now, hass, calendar):
}
@patch(
"homeassistant.util.dt.now",
return_value=dt.as_local(datetime.datetime(2016, 12, 1, 17, 30)),
)
async def test_event_rrule_all_day(mock_now, hass, calendar):
"""Test that the recurring all day event is returned."""
async def _event_rrule_all_day(hass, calendar, config, date_time):
with patch("homeassistant.util.dt.now", return_value=date_time):
assert await async_setup_component(hass, "calendar", {"calendar": config})
await hass.async_block_till_done()
state = hass.states.get("calendar.private_private")
assert state.name == calendar.name
assert state.state == STATE_ON
assert dict(state.attributes) == {
"friendly_name": "Private",
"message": "This is a recurring all day event",
"all_day": True,
"offset_reached": False,
"start_time": "2016-12-01 00:00:00",
"end_time": "2016-12-02 00:00:00",
"location": "Hamburg",
"description": "Groundhog Day",
}
@pytest.mark.parametrize("set_tz", ["utc", "new_york", "baghdad"], indirect=True)
async def test_event_rrule_all_day_early(hass, calendar, set_tz):
"""Test that the recurring all day event is returned early in the local day, and not on the first occurrence."""
config = dict(CALDAV_CONFIG)
config["custom_calendars"] = [
{"name": "Private", "calendar": "Private", "search": ".*"}
]
assert await async_setup_component(hass, "calendar", {"calendar": config})
await hass.async_block_till_done()
await _event_rrule_all_day(
hass,
calendar,
config,
datetime.datetime(2016, 12, 1, 0, 30).replace(tzinfo=dt.DEFAULT_TIME_ZONE),
)
state = hass.states.get("calendar.private_private")
assert state.name == calendar.name
assert state.state == STATE_ON
assert dict(state.attributes) == {
"friendly_name": "Private",
"message": "This is a recurring all day event",
"all_day": True,
"offset_reached": False,
"start_time": "2016-12-01 00:00:00",
"end_time": "2016-12-02 00:00:00",
"location": "Hamburg",
"description": "Groundhog Day",
}
@pytest.mark.parametrize("set_tz", ["utc", "new_york", "baghdad"], indirect=True)
async def test_event_rrule_all_day_mid(hass, calendar, set_tz):
"""Test that the recurring all day event is returned in the middle of the local day, and not on the first occurrence."""
config = dict(CALDAV_CONFIG)
config["custom_calendars"] = [
{"name": "Private", "calendar": "Private", "search": ".*"}
]
await _event_rrule_all_day(
hass,
calendar,
config,
datetime.datetime(2016, 12, 1, 17, 30).replace(tzinfo=dt.DEFAULT_TIME_ZONE),
)
@pytest.mark.parametrize("set_tz", ["utc", "new_york", "baghdad"], indirect=True)
async def test_event_rrule_all_day_late(hass, calendar, set_tz):
"""Test that the recurring all day event is returned late in the local day, and not on the first occurrence."""
config = dict(CALDAV_CONFIG)
config["custom_calendars"] = [
{"name": "Private", "calendar": "Private", "search": ".*"}
]
await _event_rrule_all_day(
hass,
calendar,
config,
datetime.datetime(2016, 12, 1, 23, 30).replace(tzinfo=dt.DEFAULT_TIME_ZONE),
)
@patch(