Manu 6887a4419e
Add calendar platform to Habitica integration (#128248)
* Add calendar platform

* Add tests

* add missing reminders filter by date

* Add +1 day to todo end

* add 1 day to dailies, remove unused line of code

* Removing reminders calendar to a separate PR

* fix upcoming event for dailies

* util function for rrule string

* Add test for get_recurrence_rule

* use habitica daystart and account for isDue flag

* yesterdaily is still an active event

* Fix yesterdailies and add attribute

* Update snapshot

* Use iter, return attribute with None value

* various changes

* update snapshot

* fix merge error

* update snapshot

* change date range filtering for todos

* use datetimes instead of date in async_get_events

* Sort events

* Update snapshot

* add method for todos

* filter for upcoming events

* dailies

* refactor todos

* update dailies logic

* dedent loops
2024-10-29 20:53:49 -07:00

142 lines
3.9 KiB
Python

"""Utility functions for Habitica."""
from __future__ import annotations
import datetime
from typing import TYPE_CHECKING, Any
from dateutil.rrule import (
DAILY,
FR,
MO,
MONTHLY,
SA,
SU,
TH,
TU,
WE,
WEEKLY,
YEARLY,
rrule,
)
from homeassistant.components.automation import automations_with_entity
from homeassistant.components.script import scripts_with_entity
from homeassistant.core import HomeAssistant
from homeassistant.util import dt as dt_util
def next_due_date(task: dict[str, Any], last_cron: str) -> datetime.date | None:
"""Calculate due date for dailies and yesterdailies."""
if task["everyX"] == 0 or not task.get("nextDue"): # grey dailies never become due
return None
today = to_date(last_cron)
startdate = to_date(task["startDate"])
if TYPE_CHECKING:
assert today
assert startdate
if task["isDue"] and not task["completed"]:
return to_date(last_cron)
if startdate > today:
if task["frequency"] == "daily" or (
task["frequency"] in ("monthly", "yearly") and task["daysOfMonth"]
):
return startdate
if (
task["frequency"] in ("weekly", "monthly")
and (nextdue := to_date(task["nextDue"][0]))
and startdate > nextdue
):
return to_date(task["nextDue"][1])
return to_date(task["nextDue"][0])
def to_date(date: str) -> datetime.date | None:
"""Convert an iso date to a datetime.date object."""
try:
return dt_util.as_local(datetime.datetime.fromisoformat(date)).date()
except ValueError:
# sometimes nextDue dates are JavaScript datetime strings instead of iso:
# "Mon May 06 2024 00:00:00 GMT+0200"
try:
return dt_util.as_local(
datetime.datetime.strptime(date, "%a %b %d %Y %H:%M:%S %Z%z")
).date()
except ValueError:
return None
def entity_used_in(hass: HomeAssistant, entity_id: str) -> list[str]:
"""Get list of related automations and scripts."""
used_in = automations_with_entity(hass, entity_id)
used_in += scripts_with_entity(hass, entity_id)
return used_in
FREQUENCY_MAP = {"daily": DAILY, "weekly": WEEKLY, "monthly": MONTHLY, "yearly": YEARLY}
WEEKDAY_MAP = {"m": MO, "t": TU, "w": WE, "th": TH, "f": FR, "s": SA, "su": SU}
def build_rrule(task: dict[str, Any]) -> rrule:
"""Build rrule string."""
rrule_frequency = FREQUENCY_MAP.get(task["frequency"], DAILY)
weekdays = [
WEEKDAY_MAP[day] for day, is_active in task["repeat"].items() if is_active
]
bymonthday = (
task["daysOfMonth"]
if rrule_frequency == MONTHLY and task["daysOfMonth"]
else None
)
bysetpos = None
if rrule_frequency == MONTHLY and task["weeksOfMonth"]:
bysetpos = task["weeksOfMonth"]
weekdays = weekdays if weekdays else [MO]
return rrule(
freq=rrule_frequency,
interval=task["everyX"],
dtstart=dt_util.start_of_local_day(
datetime.datetime.fromisoformat(task["startDate"])
),
byweekday=weekdays if rrule_frequency in [WEEKLY, MONTHLY] else None,
bymonthday=bymonthday,
bysetpos=bysetpos,
)
def get_recurrence_rule(recurrence: rrule) -> str:
r"""Extract and return the recurrence rule portion of an RRULE.
This function takes an RRULE representing a task's recurrence pattern,
builds the RRULE string, and extracts the recurrence rule part.
'DTSTART:YYYYMMDDTHHMMSS\nRRULE:FREQ=YEARLY;INTERVAL=2'
Parameters
----------
recurrence : rrule
An RRULE object.
Returns
-------
str
The recurrence rule portion of the RRULE string, starting with 'FREQ='.
Example
-------
>>> rule = get_recurrence_rule(task)
>>> print(rule)
'FREQ=YEARLY;INTERVAL=2'
"""
return str(recurrence).split("RRULE:")[1]