"""可控时钟(作业 4-6)。 测试不依赖运行机器的当天时间:注入 ``FixedClock`` 即可固定 “当前时间”,校验相对时间、未来时间、边界。 """ from datetime import datetime from typing import Protocol import re class Clock(Protocol): """时钟协议:图节点与校验逻辑只依赖它获取当前时间。""" def now(self) -> datetime: ... class SystemClock: """真实时钟(生产环境)。""" def now(self) -> datetime: return datetime.now() class FixedClock: """固定时钟(测试):now 永远返回构造时给定的时间。""" def __init__(self, fixed: datetime): self._fixed = fixed def now(self) -> datetime: return self._fixed _FUTURE_MARKERS = ("明天", "后天", "未来", "等会", "稍后", "接下来") _ABS_RE = re.compile(r"^\s*(\d{1,2})\s*点(?:\s*(\d{1,2})\s*分?)?\s*$") _REL_MIN_RE = re.compile(r"(\d+)\s*分钟前") _REL_HOUR_RE = re.compile(r"(?:半\s*小时|(\d+)\s*小时)前") def validate_accident_time(value: str, now: datetime) -> bool: """校验事故时间字符串是否合法(可解析且不晚于 now)。 参考实现覆盖的常见口语形态(不依赖机器当天时间,now 由调用方注入): - 相对时间:“刚才/刚刚”、“N 分钟前”、“N 小时前/半小时前”; - 绝对时间:“11点”“11点45分”——按 now 的当天日期解析, 解析结果晚于 now(未来时间)判为非法; - 含明显未来标记(“明天”“稍后”等)一律非法; - 无法解析的形态返回 False,由调用方进入澄清,不得猜值。 已知限制:未覆盖“昨天上午”“上周五”等跨日表达,作业细化时补充。 """ text = (value or "").strip() if not text: return False if any(marker in text for marker in _FUTURE_MARKERS): return False if text in ("刚才", "刚刚"): return True m = _REL_MIN_RE.search(text) if m: return 0 <= int(m.group(1)) <= 24 * 60 # Sanity 上限,防荒谬输入 m = _REL_HOUR_RE.search(text) if m: hours = 1 if m.group(1) is None else int(m.group(1)) return 0 < hours <= 24 m = _ABS_RE.match(text) if m: hour, minute = int(m.group(1)), int(m.group(2) or 0) if hour > 23 or minute > 59: return False candidate = now.replace(hour=hour, minute=minute, second=0, microsecond=0) return candidate <= now return False