fix(hands,memory): the daily routine folds into one line and stops crowding out single events

This commit is contained in:
hh
2026-09-09 08:24:53 +00:00
parent 8d0f80440e
commit ae0a16bc17
4 changed files with 165 additions and 31 deletions
+46 -4
View File
@@ -10,7 +10,8 @@
FREQ=DAILY/WEEKLY/MONTHLY/YEARLY с INTERVAL, BYDAY, BYMONTH, COUNT и UNTIL;
EXDATE и перенесённые экземпляры (RECURRENCE-ID) вычитаются. Шаг считается по
настенным часам события, а не в абсолютном времени: еженедельная встреча
переживает перевод часов.
переживает перевод часов. Событие, порождённое ежедневным правилом, помечается
`routine`: в справке такие едут одной строкой, чтобы не вытеснять разовые.
"""
from __future__ import annotations
@@ -32,7 +33,16 @@ if TYPE_CHECKING:
from collections.abc import Iterator
from datetime import tzinfo
__all__ = ["Calendars", "Event", "Feed", "Upcoming", "calendars", "feeds", "short"]
__all__ = [
"Calendars",
"Event",
"Feed",
"Upcoming",
"calendars",
"feeds",
"is_routine",
"short",
]
_log = logging.getLogger(__name__)
@@ -108,6 +118,8 @@ class Event:
title: str
place: str
all_day: bool
routine: bool = False
"""Порождено правилом, повторяющимся раз в день или чаще: см. `is_routine`."""
@dataclass(frozen=True, slots=True)
@@ -425,6 +437,25 @@ def _until(value: str | None, zone: tzinfo) -> datetime | None:
return stop + timedelta(days=1) if all_day else stop
ROUTINE_DAYS = 5
"""Со скольких дней недели в BYDAY повтор перестаёт быть событием и делается бытом."""
def is_routine(rule: dict[str, str]) -> bool:
"""Правило, срабатывающее раз в день или чаще: завтрак, зарядка, wind down.
Такие повторы за неделю дают десятки строк и топят в себе то единственное,
ради чего справку и читают, - экзамен, встречу, приём. В конверт они едут
одной свёрнутой строкой, а не списком.
"""
freq = rule.get("FREQ", "").upper()
if max(_int(rule.get("INTERVAL"), 1), 1) != 1:
return False
if freq == "DAILY":
return True
return freq == "WEEKLY" and len(_byday(rule.get("BYDAY", ""))) >= ROUTINE_DAYS
def place_of(event: Vevent) -> str:
"""Зал из описания и адрес из LOCATION - то, что помогает дойти."""
parts: list[str] = []
@@ -471,8 +502,11 @@ def events_in(text: str, *, zone: tzinfo, since: datetime, days: int) -> list[Ev
if rrule is None or "RECURRENCE-ID" in event:
starts = [start]
skip: set[datetime] = set()
routine = False
else:
starts = occurrences(start, _rule(rrule.value), zone, end=end)
rule = _rule(rrule.value)
starts = occurrences(start, rule, zone, end=end)
routine = is_routine(rule)
uid = _first(event, "UID")
skip = _instants(event, "EXDATE", zone) | moved.get(
uid.value if uid else "", set()
@@ -483,7 +517,15 @@ def events_in(text: str, *, zone: tzinfo, since: datetime, days: int) -> list[Ev
floor = day_start if all_day else since
if moving < floor or moving > end or moving.astimezone(UTC) in skip:
continue
out.append(Event(start=moving, title=title, place=place, all_day=all_day))
out.append(
Event(
start=moving,
title=title,
place=place,
all_day=all_day,
routine=routine,
)
)
return out