I’m parsing natural‑language date filters in a chatbot. The input can be “15/09/2025”, “December 9 2025”, “15092025”, or just a year like “2025”. I also need to understand phrases like “after 2021”, “between 2019 and 2022”, “on or before 15/07/2025”. The snippet below does two things: Extracts date tokens with a regex that covers the formats above. Maps cue words (“after”, “before”, “between”, “from … to …”) to comparison operators and returns a dict shaped like {'Date_Raised': {'$gte': '2019-01-01', '$lte': '2022-01-01'}}. Ambiguous numeric dates (e.g., 09/10/2025) are resolved with a DAYFIRST flag; you can flip it for US format. Bare years are normalized to Jan 1 of that year. import re from datetime import date from dateutil import parser as dparser DAYFIRST = True # set False if you want MM/DD/YYYY bias FIELD = "Date_Raised" # target field name DATE_RX = re.compile(r""" (?:\d{8}) # 15092025 | (?:\d{1,2}[/-]\d{1,2}[/-]\d{2,4}) # 15/09/2025 or 09-15-25 | (?:\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)[a-z]*/\d{1,2}/\d{2,4}\b) | (?:\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)[a-z]*\s+\d{1,2},?\s+\d{2,4}\b) | (?:\b(19|20)\d{2}\b) # bare year """, re.IGNORECASE | re.VERBOSE) OPS = [ (r"\bbetween\b", "between"), (r"\bfrom\b", "from"), (r"\bto\b", "to"), (r"\bafter|greater than|later than|newer than|>\b", "gt"), (r"\bon or after|>=|since\b", "gte"), (r"\bbefore|less than|earlier than|older than|<\b", "lt"), (r"\bon or before|<=|till|until|upto?\b", "lte"), ] def _smart_numeric(tok: str, dayfirst: bool) -> date: if re.fullmatch(r"\d{8}", tok): d, m, y = int(tok[:2]), int(tok[2:4]), int(tok[4:]) return date(y, m, d) if re.fullmatch(r"\d{1,2}[/-]\d{1,2}[/-]\d{2,4}", tok): a, b, c = map(int, re.split(r"[/-]", tok)) if c < 100: c += 2000 if c <= 69 else 1900 if a > 12 and b <= 12: d_, m_ = a, b elif b > 12 and a <= 12: d_, m_ = b, a else: d_, m_ = (a, b) if dayfirst else (b, a) return date(c, m_, d_) return dparser.parse(tok, dayfirst=dayfirst, fuzzy=True).date() def to_date(tok: str, dayfirst: bool = DAYFIRST) -> date: tok = tok.strip() if re.fullmatch(r"(19|20)\d{2}", tok): return date(int(tok), 1, 1) return _smart_numeric(tok, dayfirst) def _fmt(d: date) -> str: return d.strftime("%Y-%m-%d") def parse_date_condition(text: str, dayfirst: bool = DAYFIRST, field: str = FIELD): low = text.lower() toks = [m.group(0) for m in DATE_RX.finditer(text)] dates = [to_date(t, dayfirst) for t in toks] for pat, op in OPS: if re.search(pat, low): if op == "between": if len(dates) < 2: raise ValueError("Found 'between' but fewer than 2 dates.") lo, hi = sorted(dates[:2]) return {field: {"$gte": _fmt(lo), "$lte": _fmt(hi)}} if op == "from": if len(dates) >= 2 and re.search(r"\bto\b", low): lo, hi = sorted(dates[:2]) return {field: {"$gte": _fmt(lo), "$lte": _fmt(hi)}} return {field: {"$gte": _fmt(dates[0])}} if op == "to": return {field: {"$lte": _fmt(dates[-1])}} if not dates: raise ValueError("Comparator word present but no date.") mongo_map = {"gt": "$gt", "gte": "$gte", "lt": "$lt", "lte": "$lte"} return {field: {mongo_map[op]: _fmt(dates[0])}} if dates: return {field: {"$eq": _fmt(dates[0])}} return None Continue reading...