Update Todoist all day event handling following best practices (#90491)

pull/91586/head
Allen Porter 2023-04-17 05:21:49 -07:00 committed by GitHub
parent 9be9defbb8
commit 4c5746d6ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 154 additions and 20 deletions

View File

@ -9,7 +9,7 @@ import uuid
from todoist_api_python.api_async import TodoistAPIAsync
from todoist_api_python.endpoints import get_sync_url
from todoist_api_python.headers import create_headers
from todoist_api_python.models import Label, Task
from todoist_api_python.models import Due, Label, Task
import voluptuous as vol
from homeassistant.components.calendar import (
@ -590,25 +590,19 @@ class TodoistProjectData:
for task in project_task_data:
if task.due is None:
continue
due_date = dt.parse_datetime(
task.due.datetime if task.due.datetime else task.due.date
)
if not due_date:
start = get_start(task.due)
if start is None:
continue
due_date = dt.as_utc(due_date)
if start_date < due_date < end_date:
due_date_value: datetime | date = due_date
midnight = dt.start_of_local_day(due_date)
if due_date == midnight:
# If the due date has no time data, return just the date so that it
# will render correctly as an all day event on a calendar.
due_date_value = due_date.date()
event = CalendarEvent(
summary=task.content,
start=due_date_value,
end=due_date_value + timedelta(days=1),
)
events.append(event)
event = CalendarEvent(
summary=task.content,
start=start,
end=start + timedelta(days=1),
)
if event.start_datetime_local >= end_date:
continue
if event.end_datetime_local < start_date:
continue
events.append(event)
return events
async def async_update(self) -> None:
@ -663,3 +657,15 @@ class TodoistProjectData:
return
self.event = event
_LOGGER.debug("Updated %s", self._name)
def get_start(due: Due) -> datetime | date | None:
"""Return the task due date as a start date or date time."""
if due.datetime:
start = dt.parse_datetime(due.datetime)
if not start:
return None
return dt.as_local(start)
if due.date:
return dt.parse_date(due.date)
return None

View File

@ -228,8 +228,39 @@ async def test_calendar_custom_project_unique_id(
"2023-03-28T00:00:00.000Z",
"2023-04-01T00:00:00.000Z",
[get_events_response({"date": "2023-03-30"}, {"date": "2023-03-31"})],
)
),
(
Due(date="2023-03-30", is_recurring=False, string="Mar 30"),
"2023-03-30T06:00:00.000Z",
"2023-03-31T06:00:00.000Z",
[get_events_response({"date": "2023-03-30"}, {"date": "2023-03-31"})],
),
(
Due(date="2023-03-30", is_recurring=False, string="Mar 30"),
"2023-03-29T08:00:00.000Z",
"2023-03-30T08:00:00.000Z",
[get_events_response({"date": "2023-03-30"}, {"date": "2023-03-31"})],
),
(
Due(date="2023-03-30", is_recurring=False, string="Mar 30"),
"2023-03-30T08:00:00.000Z",
"2023-03-31T08:00:00.000Z",
[get_events_response({"date": "2023-03-30"}, {"date": "2023-03-31"})],
),
(
Due(date="2023-03-30", is_recurring=False, string="Mar 30"),
"2023-03-31T08:00:00.000Z",
"2023-04-01T08:00:00.000Z",
[],
),
(
Due(date="2023-03-30", is_recurring=False, string="Mar 30"),
"2023-03-29T06:00:00.000Z",
"2023-03-30T06:00:00.000Z",
[],
),
],
ids=("included", "exact", "overlap_start", "overlap_end", "after", "before"),
)
async def test_all_day_event(
hass: HomeAssistant,
@ -259,3 +290,100 @@ async def test_create_task_service_call(hass: HomeAssistant, api: AsyncMock) ->
api.add_task.assert_called_with(
"task", project_id="12345", labels=["Label1"], assignee_id="1"
)
@pytest.mark.parametrize(
("due"),
[
# These are all equivalent due dates for the same time in different
# timezone formats.
Due(
date="2023-03-30",
is_recurring=False,
string="Mar 30 6:00 PM",
datetime="2023-03-31T00:00:00Z",
timezone="America/Regina",
),
Due(
date="2023-03-30",
is_recurring=False,
string="Mar 30 7:00 PM",
datetime="2023-03-31T00:00:00Z",
timezone="America/Los_Angeles",
),
Due(
date="2023-03-30",
is_recurring=False,
string="Mar 30 6:00 PM",
datetime="2023-03-30T18:00:00",
),
],
ids=("in_local_timezone", "in_other_timezone", "floating"),
)
async def test_task_due_datetime(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
) -> None:
"""Test for task due at a specific time, using different time formats."""
client = await hass_client()
has_task_response = [
get_events_response(
{"dateTime": "2023-03-30T18:00:00-06:00"},
{"dateTime": "2023-03-31T18:00:00-06:00"},
)
]
# Completely includes the start/end of the task
response = await client.get(
get_events_url(
"calendar.name", "2023-03-30T08:00:00.000Z", "2023-03-31T08:00:00.000Z"
),
)
assert response.status == HTTPStatus.OK
assert await response.json() == has_task_response
# Overlap with the start of the event
response = await client.get(
get_events_url(
"calendar.name", "2023-03-29T20:00:00.000Z", "2023-03-31T02:00:00.000Z"
),
)
assert response.status == HTTPStatus.OK
assert await response.json() == has_task_response
# Overlap with the end of the event
response = await client.get(
get_events_url(
"calendar.name", "2023-03-31T20:00:00.000Z", "2023-04-01T02:00:00.000Z"
),
)
assert response.status == HTTPStatus.OK
assert await response.json() == has_task_response
# Task is active, but range does not include start/end
response = await client.get(
get_events_url(
"calendar.name", "2023-03-31T10:00:00.000Z", "2023-03-31T11:00:00.000Z"
),
)
assert response.status == HTTPStatus.OK
assert await response.json() == has_task_response
# Query is before the task starts (no results)
response = await client.get(
get_events_url(
"calendar.name", "2023-03-28T00:00:00.000Z", "2023-03-29T00:00:00.000Z"
),
)
assert response.status == HTTPStatus.OK
assert await response.json() == []
# Query is after the task ends (no results)
response = await client.get(
get_events_url(
"calendar.name", "2023-04-01T07:00:00.000Z", "2023-04-02T07:00:00.000Z"
),
)
assert response.status == HTTPStatus.OK
assert await response.json() == []