mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-17 10:15:27 -04:00
fix(notes): handle time-first due_date phrases in parse_due_for_user (#3319)
parse_due_for_user only matched day-first format ('today at 3pm').
Time-first strings like '3pm today' or '11pm today' — which the tool
schema and tool_index both advertise as valid examples — fell through
all branches, hit dateutil or the legacy _parse_dt fallback, and in
many cases raised ValueError. do_manage_notes then stored the raw
string verbatim, and the ISO-only reminder scanner (action_ping_notes)
never fired the note.
Add a time-first regex branch immediately after the day-first branch
to handle '<time> today|tonight|tomorrow|tmrw|yesterday'. Existing
day-first parsing is unchanged.
Fixes #3302
This commit is contained in:
@@ -258,6 +258,17 @@ def parse_due_for_user(s: str) -> str:
|
|||||||
if t is not None:
|
if t is not None:
|
||||||
return base.replace(hour=t[0], minute=t[1]).isoformat()
|
return base.replace(hour=t[0], minute=t[1]).isoformat()
|
||||||
|
|
||||||
|
# Time-first: "3pm today", "11pm today", "9am tomorrow"
|
||||||
|
m = _re.match(r'^(.+?)\s+(today|tonight|tomorrow|tmrw|yesterday)$', lower)
|
||||||
|
if m:
|
||||||
|
time_part, word = m.group(1).strip(), m.group(2)
|
||||||
|
base = today
|
||||||
|
if word in ("tomorrow", "tmrw"): base = today + _td(days=1)
|
||||||
|
elif word == "yesterday": base = today - _td(days=1)
|
||||||
|
t = _parse_time(time_part)
|
||||||
|
if t is not None:
|
||||||
|
return base.replace(hour=t[0], minute=t[1]).isoformat()
|
||||||
|
|
||||||
m = _re.match(r'^in\s+(\d+)\s*(hour|hr|minute|min|day)s?\s*$', lower)
|
m = _re.match(r'^in\s+(\d+)\s*(hour|hr|minute|min|day)s?\s*$', lower)
|
||||||
if m:
|
if m:
|
||||||
n = int(m.group(1)); unit = m.group(2)
|
n = int(m.group(1)); unit = m.group(2)
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
"""Regression: parse_due_for_user must handle time-first phrasings.
|
||||||
|
|
||||||
|
The tool schema and tool_index both advertise '11pm today' as a valid
|
||||||
|
due_date example. The parser's natural-language branch only matched
|
||||||
|
day-first format ('today at 11pm'), so time-first strings like '3pm today'
|
||||||
|
raised ValueError, fell back to the raw string, and the ISO-only reminder
|
||||||
|
scanner never fired the note. Fixes #3302.
|
||||||
|
"""
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
import routes.calendar_routes as calendar_routes
|
||||||
|
from src.user_time import clear_user_time_context, set_user_tz_name, set_user_tz_offset
|
||||||
|
|
||||||
|
|
||||||
|
class _FixedNow(datetime):
|
||||||
|
"""Freeze server clock at 2026-06-07T10:00:00 UTC for deterministic tests."""
|
||||||
|
@classmethod
|
||||||
|
def now(cls, tz=None):
|
||||||
|
value = datetime(2026, 6, 7, 10, 0, 0, tzinfo=timezone.utc)
|
||||||
|
if tz is not None:
|
||||||
|
return value.astimezone(tz)
|
||||||
|
return value.replace(tzinfo=None)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_function():
|
||||||
|
clear_user_time_context()
|
||||||
|
set_user_tz_offset(0)
|
||||||
|
set_user_tz_name("UTC")
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_function():
|
||||||
|
clear_user_time_context()
|
||||||
|
|
||||||
|
|
||||||
|
def test_time_first_today(monkeypatch):
|
||||||
|
monkeypatch.setattr(calendar_routes, "datetime", _FixedNow)
|
||||||
|
result = calendar_routes.parse_due_for_user("3pm today")
|
||||||
|
assert result.startswith("2026-06-07T15:00:00")
|
||||||
|
|
||||||
|
|
||||||
|
def test_time_first_today_11pm(monkeypatch):
|
||||||
|
monkeypatch.setattr(calendar_routes, "datetime", _FixedNow)
|
||||||
|
result = calendar_routes.parse_due_for_user("11pm today")
|
||||||
|
assert result.startswith("2026-06-07T23:00:00")
|
||||||
|
|
||||||
|
|
||||||
|
def test_time_first_tomorrow(monkeypatch):
|
||||||
|
monkeypatch.setattr(calendar_routes, "datetime", _FixedNow)
|
||||||
|
result = calendar_routes.parse_due_for_user("9am tomorrow")
|
||||||
|
assert result.startswith("2026-06-08T09:00:00")
|
||||||
|
|
||||||
|
|
||||||
|
def test_time_first_with_minutes(monkeypatch):
|
||||||
|
monkeypatch.setattr(calendar_routes, "datetime", _FixedNow)
|
||||||
|
result = calendar_routes.parse_due_for_user("2:30pm tomorrow")
|
||||||
|
assert result.startswith("2026-06-08T14:30:00")
|
||||||
|
|
||||||
|
|
||||||
|
def test_day_first_still_works(monkeypatch):
|
||||||
|
"""Existing day-first format must not regress."""
|
||||||
|
monkeypatch.setattr(calendar_routes, "datetime", _FixedNow)
|
||||||
|
result = calendar_routes.parse_due_for_user("today at 3pm")
|
||||||
|
assert result.startswith("2026-06-07T15:00:00")
|
||||||
Reference in New Issue
Block a user