fix(calendar): accept time-first datetimes in _parse_dt

Accept calendar datetime phrases such as "3pm tomorrow" by adding a time-first natural-language parser branch mirroring the reminder parser. Add regression coverage proving time-first forms match their existing day-first equivalents.
This commit is contained in:
Miraç Duran
2026-06-27 20:51:18 +03:00
committed by GitHub
parent c098355778
commit 228efbc70a
2 changed files with 45 additions and 0 deletions
+14
View File
@@ -452,6 +452,20 @@ def _parse_dt(s: str) -> datetime:
if t is not None:
return base.replace(hour=t[0], minute=t[1])
# time-first: "3pm today", "9am tomorrow", "11pm tonight"
# (parity with parse_due_for_user, which handles these via the same form)
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 + timedelta(days=1)
elif word == "yesterday":
base = today - timedelta(days=1)
t = _parse_time(time_part)
if t is not None:
return base.replace(hour=t[0], minute=t[1])
# next <weekday> [at] TIME
weekdays = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
m = _re.match(r'^next\s+(\w+)(?:\s+at)?\s*(.*)$', lower)
@@ -0,0 +1,31 @@
"""Regression: _parse_dt must understand "time-first" phrasings like parse_due_for_user does.
parse_due_for_user accepts both day-first ("tomorrow at 9am") and time-first
("9am tomorrow") forms, but _parse_dt (the parser _parse_dt_pair falls back to
for calendar event start/end) only handled the day-first form. A time-first
start like "3pm tomorrow" missed every branch and fell through to dateutil,
which raises ParserError on "3pm tomorrow", so creating an event with that
phrasing failed. Time-first is now handled identically to its day-first
equivalent, mirroring the sibling reminder parser.
"""
from routes.calendar_routes import _parse_dt
def test_time_first_today_equals_day_first():
assert _parse_dt("3pm today") == _parse_dt("today at 3pm")
def test_time_first_tomorrow_equals_day_first():
assert _parse_dt("9am tomorrow") == _parse_dt("tomorrow at 9am")
def test_time_first_with_minutes_equals_day_first():
assert _parse_dt("2:30pm tomorrow") == _parse_dt("tomorrow at 2:30pm")
def test_time_first_tonight_maps_to_today():
assert _parse_dt("11pm tonight") == _parse_dt("today at 11pm")
def test_time_first_yesterday_equals_day_first():
assert _parse_dt("8am yesterday") == _parse_dt("yesterday at 8am")