Update Todoist all day event handling following best practices (#90491)
parent
9be9defbb8
commit
4c5746d6ed
|
@ -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
|
||||
|
|
|
@ -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() == []
|
||||
|
|
Loading…
Reference in New Issue