diff --git a/redisvl/query/filter.py b/redisvl/query/filter.py index 04d3e77d..e92e2e8b 100644 --- a/redisvl/query/filter.py +++ b/redisvl/query/filter.py @@ -803,13 +803,22 @@ def __ne__( self: The filter object for method chaining """ if self._is_date(other): - # For date objects, exclude the entire day + # For date objects, exclude the entire day by negating exactly the + # range that __eq__ matches. if isinstance(other, str): other = datetime.datetime.strptime(other, "%Y-%m-%d").date() assert isinstance(other, datetime.date) # validate for mypy - start = datetime.datetime.combine(other, datetime.time.min) - end = datetime.datetime.combine(other, datetime.time.max) - return self.between(start, end) + start = datetime.datetime.combine(other, datetime.time.min).astimezone( + datetime.timezone.utc + ) + end = datetime.datetime.combine(other, datetime.time.max).astimezone( + datetime.timezone.utc + ) + start_ts = self._convert_to_timestamp(start) + end_ts = self._convert_to_timestamp(end, end_date=True) + return FilterExpression( + self.OPERATOR_MAP[FilterOperator.NE] % (self._field, start_ts, end_ts) + ) timestamp = self._convert_to_timestamp(other) self._set_value(timestamp, self.SUPPORTED_TYPES, FilterOperator.NE) diff --git a/tests/unit/test_filter.py b/tests/unit/test_filter.py index fa66a0f1..a7be2651 100644 --- a/tests/unit/test_filter.py +++ b/tests/unit/test_filter.py @@ -412,6 +412,26 @@ def test_timestamp_date(): assert str(ts) == f"@created_at:[{expected_ts_start} {expected_ts_end}]" +def test_timestamp_not_equal_date(): + """Test Timestamp != with date objects (should exclude the full day).""" + d = date(2023, 3, 17) + ts = Timestamp("created_at") != d + + expected_ts_start = ( + datetime.combine(d, time.min).astimezone(timezone.utc).timestamp() + ) + expected_ts_end = datetime.combine(d, time.max).astimezone(timezone.utc).timestamp() + + assert str(ts) == f"(-@created_at:[{expected_ts_start} {expected_ts_end}])" + + # != must be the exact negation of == for the same date + eq = Timestamp("created_at") == d + assert str(ts) == f"(-{eq!s})" + + # A date-only ISO string takes the same path as a date object + assert str(Timestamp("created_at") != "2023-03-17") == str(ts) + + def test_timestamp_iso_string(): """Test Timestamp filter with ISO format strings.""" # Date-only ISO string