Update Todoist all day event handling following best practices (#90491)
This commit is contained in:
parent
9be9defbb8
commit
4c5746d6ed
2 changed files with 154 additions and 20 deletions
|
@ -9,7 +9,7 @@ import uuid
|
||||||
from todoist_api_python.api_async import TodoistAPIAsync
|
from todoist_api_python.api_async import TodoistAPIAsync
|
||||||
from todoist_api_python.endpoints import get_sync_url
|
from todoist_api_python.endpoints import get_sync_url
|
||||||
from todoist_api_python.headers import create_headers
|
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
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.calendar import (
|
from homeassistant.components.calendar import (
|
||||||
|
@ -590,25 +590,19 @@ class TodoistProjectData:
|
||||||
for task in project_task_data:
|
for task in project_task_data:
|
||||||
if task.due is None:
|
if task.due is None:
|
||||||
continue
|
continue
|
||||||
due_date = dt.parse_datetime(
|
start = get_start(task.due)
|
||||||
task.due.datetime if task.due.datetime else task.due.date
|
if start is None:
|
||||||
)
|
|
||||||
if not due_date:
|
|
||||||
continue
|
continue
|
||||||
due_date = dt.as_utc(due_date)
|
event = CalendarEvent(
|
||||||
if start_date < due_date < end_date:
|
summary=task.content,
|
||||||
due_date_value: datetime | date = due_date
|
start=start,
|
||||||
midnight = dt.start_of_local_day(due_date)
|
end=start + timedelta(days=1),
|
||||||
if due_date == midnight:
|
)
|
||||||
# If the due date has no time data, return just the date so that it
|
if event.start_datetime_local >= end_date:
|
||||||
# will render correctly as an all day event on a calendar.
|
continue
|
||||||
due_date_value = due_date.date()
|
if event.end_datetime_local < start_date:
|
||||||
event = CalendarEvent(
|
continue
|
||||||
summary=task.content,
|
events.append(event)
|
||||||
start=due_date_value,
|
|
||||||
end=due_date_value + timedelta(days=1),
|
|
||||||
)
|
|
||||||
events.append(event)
|
|
||||||
return events
|
return events
|
||||||
|
|
||||||
async def async_update(self) -> None:
|
async def async_update(self) -> None:
|
||||||
|
@ -663,3 +657,15 @@ class TodoistProjectData:
|
||||||
return
|
return
|
||||||
self.event = event
|
self.event = event
|
||||||
_LOGGER.debug("Updated %s", self._name)
|
_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-03-28T00:00:00.000Z",
|
||||||
"2023-04-01T00:00:00.000Z",
|
"2023-04-01T00:00:00.000Z",
|
||||||
[get_events_response({"date": "2023-03-30"}, {"date": "2023-03-31"})],
|
[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(
|
async def test_all_day_event(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@ -259,3 +290,100 @@ async def test_create_task_service_call(hass: HomeAssistant, api: AsyncMock) ->
|
||||||
api.add_task.assert_called_with(
|
api.add_task.assert_called_with(
|
||||||
"task", project_id="12345", labels=["Label1"], assignee_id="1"
|
"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…
Add table
Reference in a new issue