Upload
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
"""Quiet hours — when the pet is allowed to make noise on its own.
|
||||
|
||||
Napping only ever suppresses *proactive* noise (heartbeat announcements,
|
||||
forwarded notifications) and wandering. The wake word, click-to-talk and
|
||||
push-to-talk still work: telling it to be quiet shouldn't mean it stops
|
||||
answering when spoken to.
|
||||
|
||||
Pure logic (parsing + a time comparison) so it's testable without waiting
|
||||
for 11pm.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import time as dtime
|
||||
from typing import Iterable, Optional
|
||||
|
||||
|
||||
class QuietHoursError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def _parse_clock(value: str) -> dtime:
|
||||
parts = value.strip().split(":")
|
||||
if len(parts) != 2:
|
||||
raise QuietHoursError(f"expected HH:MM, got {value!r}")
|
||||
try:
|
||||
hour, minute = int(parts[0]), int(parts[1])
|
||||
except ValueError as exc:
|
||||
raise QuietHoursError(f"expected HH:MM, got {value!r}") from exc
|
||||
if not (0 <= hour <= 23 and 0 <= minute <= 59):
|
||||
raise QuietHoursError(f"{value!r} is not a real time of day")
|
||||
return dtime(hour, minute)
|
||||
|
||||
|
||||
def parse_ranges(spec: str) -> list[tuple[dtime, dtime]]:
|
||||
""""23:00-08:00, 13:00-14:00" -> [(23:00, 08:00), (13:00, 14:00)].
|
||||
Empty/blank spec means "no quiet hours"."""
|
||||
ranges: list[tuple[dtime, dtime]] = []
|
||||
for chunk in (spec or "").split(","):
|
||||
chunk = chunk.strip()
|
||||
if not chunk:
|
||||
continue
|
||||
start, sep, end = chunk.partition("-")
|
||||
if not sep:
|
||||
raise QuietHoursError(f"expected HH:MM-HH:MM, got {chunk!r}")
|
||||
ranges.append((_parse_clock(start), _parse_clock(end)))
|
||||
return ranges
|
||||
|
||||
|
||||
def in_ranges(now: dtime, ranges: Iterable[tuple[dtime, dtime]]) -> bool:
|
||||
for start, end in ranges:
|
||||
if start == end:
|
||||
continue # a zero-length range is a typo, not "all day"
|
||||
if start < end:
|
||||
if start <= now < end:
|
||||
return True
|
||||
elif now >= start or now < end: # wraps past midnight
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_quiet(spec: str, now: Optional[dtime] = None, on_error=None) -> bool:
|
||||
"""True if *now* (defaults to the local wall clock) falls inside *spec*.
|
||||
A malformed spec is reported via *on_error* and treated as "not quiet" —
|
||||
a config typo shouldn't silently mute the pet forever."""
|
||||
if not (spec or "").strip():
|
||||
return False
|
||||
try:
|
||||
ranges = parse_ranges(spec)
|
||||
except QuietHoursError as exc:
|
||||
if on_error is not None:
|
||||
on_error(exc)
|
||||
return False
|
||||
if now is None:
|
||||
from datetime import datetime
|
||||
|
||||
now = datetime.now().time()
|
||||
return in_ranges(now, ranges)
|
||||
Reference in New Issue
Block a user