@@ -165,7 +165,23 @@ async def ensure_constraints(
165165 # positioned at the end (e.g. "in 10m" leaves "in" before the token).
166166 if isinstance (remaining , str ):
167167 cleaned = remaining .strip (" ,.!" )
168- if cleaned .lower () in {"in" , "to" , "at" , "me" }:
168+ stray_tokens = {
169+ "in" ,
170+ "to" ,
171+ "at" ,
172+ "me" ,
173+ # also treat vague times of day as stray tokens when they are the only leftover word
174+ "evening" ,
175+ "night" ,
176+ "midnight" ,
177+ "morning" ,
178+ "afternoon" ,
179+ "tonight" ,
180+ "noon" ,
181+ "today" ,
182+ "tomorrow" ,
183+ }
184+ if cleaned .lower () in stray_tokens :
169185 remaining = ""
170186
171187 if self .dt < now :
@@ -273,7 +289,10 @@ async def convert(self, ctx: Context, argument: str, *, now=None) -> FriendlyTim
273289 if not status .hasDateOrTime :
274290 raise commands .BadArgument ('Invalid time provided, try e.g. "tomorrow" or "3 days".' )
275291
276- if begin not in (0 , 1 ) and end != len (argument ):
292+ # If the parsed time token is embedded in the text but only followed by
293+ # trailing punctuation/whitespace, treat it as if it's positioned at the end.
294+ trailing = argument [end :].strip (" ,.!" )
295+ if begin not in (0 , 1 ) and trailing != "" :
277296 raise commands .BadArgument (
278297 "Time is either in an inappropriate location, which "
279298 "must be either at the end or beginning of your input, "
@@ -288,6 +307,20 @@ async def convert(self, ctx: Context, argument: str, *, now=None) -> FriendlyTim
288307 if status .accuracy == pdt .pdtContext .ACU_HALFDAY :
289308 dt = dt .replace (day = now .day + 1 )
290309
310+ # Heuristic: If the matched time string is a vague time-of-day (e.g.,
311+ # 'evening', 'morning', 'afternoon', 'night') and there's additional
312+ # non-punctuation text besides that token, assume the user intended a
313+ # closing message rather than scheduling. This avoids cases like
314+ # '?close Have a good evening!' being treated as a scheduled close.
315+ vague_tod = {"evening" , "morning" , "afternoon" , "night" }
316+ matched_text = dt_string .strip ().strip ('"' ).rstrip (" ,.!" ).lower ()
317+ pre_text = argument [:begin ].strip (" ,.!" )
318+ post_text = argument [end :].strip (" ,.!" )
319+ if matched_text in vague_tod and (pre_text or post_text ):
320+ result = FriendlyTimeResult (now )
321+ await result .ensure_constraints (ctx , self , now , argument )
322+ return result
323+
291324 result = FriendlyTimeResult (dt .replace (tzinfo = datetime .timezone .utc ), now )
292325 remaining = ""
293326
0 commit comments