{"record":{"id":"e37a78b0004d9b8d","repo":"mem0ai/mem0","slug":"invalid-datetime-value-in-range-filter-for-field","errorCode":null,"errorMessage":"Invalid datetime value in range filter for field '{key}': {e}","messagePattern":"Invalid datetime value in range filter for field '(.+?)': (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"mem0/vector_stores/qdrant.py","lineNumber":307,"sourceCode":"            return FieldCondition(key=key, match=MatchValue(value=value))\n\n        ops = set(value.keys())\n        range_ops = {\"gt\", \"gte\", \"lt\", \"lte\"}\n        non_range_ops = ops - range_ops\n\n        if ops & range_ops:\n            if non_range_ops:\n                raise ValueError(\n                    f\"Cannot mix range operators ({ops & range_ops}) with \"\n                    f\"non-range operators ({non_range_ops}) for field '{key}'. \"\n                    f\"Use AND to combine them as separate conditions.\"\n                )\n            range_kwargs = {op: value[op] for op in range_ops if op in value}\n            if self._is_datetime_range(range_kwargs):\n                try:\n                    return FieldCondition(key=key, range=DatetimeRange(**range_kwargs))\n                except (ValueError, TypeError) as e:\n                    raise ValueError(\n                        f\"Invalid datetime value in range filter for field '{key}': {e}\"\n                    ) from e\n            return FieldCondition(key=key, range=Range(**range_kwargs))\n        elif \"eq\" in value:\n            return FieldCondition(key=key, match=MatchValue(value=value[\"eq\"]))\n        elif \"ne\" in value:\n            return FieldCondition(key=key, match=MatchExcept(**{\"except\": [value[\"ne\"]]}))\n        elif \"in\" in value:\n            return FieldCondition(key=key, match=MatchAny(any=value[\"in\"]))\n        elif \"nin\" in value:\n            return FieldCondition(key=key, match=MatchExcept(**{\"except\": value[\"nin\"]}))\n        elif \"contains\" in value or \"icontains\" in value:\n            # MatchText: with a full-text index, tokenized matching (all words must appear).\n            # Without a full-text index, exact substring match.\n            op = \"icontains\" if \"icontains\" in value else \"contains\"\n            text = value[op]\n            if op == \"icontains\":\n                logger.debug(","sourceCodeStart":289,"sourceCodeEnd":325,"githubUrl":"https://github.com/mem0ai/mem0/blob/001c235229be8795e3834520467bd0d661ed8f34/mem0/vector_stores/qdrant.py#L289-L325","documentation":"Raised when Qdrant's filter builder detects a datetime-shaped range (values that look like dates/timestamps) but the upstream DatetimeRange constructor rejects the values — typically strings that are not valid RFC 3339 / ISO 8601 timestamps or non-numeric types. The original ValueError/TypeError from Qdrant is chained (`from e`) so the underlying parse failure is preserved in the message.","triggerScenarios":"Filters such as `{\"created_at\": {\"gte\": \"yesterday\"}}`, `{\"created_at\": {\"lt\": \"2024-13-45\"}}`, `{\"created_at\": {\"gte\": \"01/02/2024\"}}` (slash format), or passing None/bool where a timestamp is expected — any value DatetimeRange cannot parse.","commonSituations":"Free-form user input used directly as a date filter; locale-specific date formats (MM/DD/YYYY vs ISO); LLM tool-calls emitting relative dates like 'last week' instead of ISO strings; storing timestamps with a 'Z' suffix or offset that the installed qdrant-client version rejects.","solutions":["Normalize date inputs to ISO 8601 / RFC 3339 before filtering: `datetime.isoformat()` or `dateutil.parser.parse(raw).isoformat()`.","If the field is actually numeric (unix epoch), pass ints/floats so `_is_datetime_range` routes it to the plain Range constructor instead of DatetimeRange.","Upgrade qdrant-client — DatetimeRange parsing strictness has changed between versions; align client and server versions."],"exampleFix":"# before\nfilters = {\"created_at\": {\"gte\": \"01/02/2024\"}}\n\n# after\nfrom datetime import datetime, timezone\nfrom dateutil import parser\n\ndef to_iso(raw: str) -> str:\n    return parser.parse(raw).astimezone(timezone.utc).isoformat()\n\nfilters = {\"created_at\": {\"gte\": to_iso(\"2024-01-02\")}}","handlingStrategy":"validation","validationCode":"from datetime import datetime\n\ndef iso_or_none(raw) -> str | None:\n    if raw is None:\n        return None\n    dt = datetime.fromisoformat(str(raw).replace(\"Z\", \"+00:00\"))  # raises on garbage\n    return dt.isoformat()\n\nfilters = {\"created_at\": {\"gte\": iso_or_none(user_input)}}","typeGuard":"def is_parseable_datetime(v) -> bool:\n    if isinstance(v, (int, float)) and not isinstance(v, bool):\n        return True  # epoch values go to plain Range\n    if not isinstance(v, str):\n        return False\n    try:\n        datetime.fromisoformat(v.replace(\"Z\", \"+00:00\"))\n        return True\n    except ValueError:\n        return False","tryCatchPattern":"try:\n    results = memory.search(\"q\", filters=filters)\nexcept ValueError as e:\n    if \"Invalid datetime value\" in str(e):\n        raise ValueError(f\"Bad date filter from caller: {filters}; expected ISO 8601\") from e\n    raise","preventionTips":["Always serialize datetimes with datetime.isoformat() at the boundary; never forward free-text dates.","Decide once per field whether it stores ISO strings or epoch numbers, and validate accordingly.","Add a request-level schema check that datetime filter values parse before they reach the vector layer."],"tags":["filters","qdrant","datetime","validation","vector-store"],"backgroundTag":null,"analyzedSha":"001c235229be8795e3834520467bd0d661ed8f34","analyzedAt":"2026-08-15T01:55:42.685Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}