import re from datetime import datetime, timezone def normalize_posted_text(text: str | None) -> datetime | None: """Parse relative posted-date strings into UTC datetimes. Returns None if the text cannot be parsed. """ if not text: return None t = text.strip().lower() now = datetime.now(timezone.utc) # Exact matches if t in ("today", "just posted", "posted today", "new", "1d", "0d"): return now.replace(hour=0, minute=0, second=0, microsecond=0) # "X hours ago" / "X hour ago" m = re.search(r"(\d+)\s*hours?\s*ago", t) if m: from datetime import timedelta return now - timedelta(hours=int(m.group(1))) # "1 day ago" / "2 days ago" m = re.search(r"(\d+)\s*days?\s*ago", t) if m: from datetime import timedelta days = int(m.group(1)) if days <= 1: return now.replace(hour=0, minute=0, second=0, microsecond=0) return now - timedelta(days=days) # "yesterday" if "yesterday" in t: from datetime import timedelta return now - timedelta(days=1) # "X minutes ago" m = re.search(r"(\d+)\s*minutes?\s*ago", t) if m: from datetime import timedelta return now - timedelta(minutes=int(m.group(1))) # ISO date m = re.match(r"(\d{4}-\d{2}-\d{2})", t) if m: try: return datetime.strptime(m.group(1), "%Y-%m-%d").replace(tzinfo=timezone.utc) except ValueError: pass return None def is_recent(text: str | None, max_days: int = 1) -> bool: """Return True if posted_text represents a posting within max_days days.""" dt = normalize_posted_text(text) if dt is None: return False from datetime import timedelta cutoff = datetime.now(timezone.utc) - timedelta(days=max_days) return dt >= cutoff