pandas-dev/pandas · error · TypeError

Cannot compare tz-naive and tz-aware datetime-like objects

Error message

Cannot compare tz-naive and tz-aware datetime-like objects

What it means

The mirror case of error 280 from the same _assert_tzawareness_compat method: here self (the DatetimeIndex/array) is tz-aware (self.tz is not None) but the other operand is tz-naive (other_tz is None, and not NaT). Pandas raises because a tz-aware value cannot be meaningfully compared against a wall-time-only value. TypeError.

Source

Thrown at pandas/core/arrays/datetimes.py:786

    def _assert_tzawareness_compat(self, other) -> None:
        # adapted from _Timestamp._assert_tzawareness_compat
        other_tz = getattr(other, "tzinfo", None)
        other_dtype = getattr(other, "dtype", None)

        if isinstance(other_dtype, DatetimeTZDtype):
            # Get tzinfo from Series dtype
            other_tz = other.dtype.tz
        if other is NaT:
            # pd.NaT quacks both aware and naive
            pass
        elif self.tz is None:
            if other_tz is not None:
                raise TypeError(
                    "Cannot compare tz-naive and tz-aware datetime-like objects."
                )
        elif other_tz is None:
            raise TypeError(
                "Cannot compare tz-naive and tz-aware datetime-like objects"
            )

    # -----------------------------------------------------------------
    # Arithmetic Methods

    def _add_offset(self, offset: BaseOffset) -> Self:
        assert not isinstance(offset, Tick)

        # For pure-timedelta DateOffset with tz-aware data, add to UTC values
        # directly to avoid nonexistent/ambiguous time errors from
        # re-localizing wall-time results near DST (GH#28610).
        if (
            self.tz is not None
            and isinstance(offset, RelativeDeltaOffset)
            and not offset._use_relativedelta
        ):
            res_values = self._ndarray + offset._pd_timedelta

View on GitHub (pinned to 71959b8cb9)

Solutions

  1. Localize the naive operand to the aware side's tz, e.g. pd.Timestamp('2020-01-01').tz_localize(aware_dti.tz).
  2. Or tz_convert the aware side to the naive side's implied tz after localizing the naive side.
  3. If wall-time comparison is intended, tz_localize(None) on the aware side (document the trade-off).
  4. Normalize tz across the pipeline at ingestion time so all datetime columns share one awareness.

Example fix

# before
aware_dti > pd.Timestamp('2020-01-01')
# after
aware_dti > pd.Timestamp('2020-01-01').tz_localize(aware_dti.tz)
Defensive patterns

Strategy: validation

Validate before calling

def localize_operand(op, target_tz):
    op_tz = getattr(getattr(op, 'dtype', None), 'tz', None) or getattr(op, 'tzinfo', None)
    if op_tz is None and target_tz is not None:
        return op.tz_localize(target_tz) if hasattr(op, 'tz_localize') else pd.Timestamp(op).tz_localize(target_tz)
    return op

Type guard

def is_tz_aware(x) -> bool:
    dt = getattr(x, 'dtype', None)
    return getattr(dt, 'tz', None) is not None or getattr(x, 'tzinfo', None) is not None

Try / catch

try:
    out = aware_dti > scalar
except TypeError as e:
    if 'tz-naive and tz-aware' in str(e):
        scalar = pd.Timestamp(scalar).tz_localize(aware_dti.tz)
        out = aware_dti > scalar
    else:
        raise

Prevention

When it happens

Trigger: A tz-aware DatetimeIndex/Series compared with a naive Timestamp, datetime.datetime, or naive index, e.g. aware_dti > pd.Timestamp('2020-01-01'), or filtering an aware Series with a naive scalar. Reached through ==, <, >, merge keys, isin, .between.

Common situations: Column localized to UTC for storage, then filtered with naive timestamps from user input; joining an aware index against a naive one produced by pd.date_range without tz; mixing datetime.datetime.now() (naive) with tz-aware data.

Related errors


AI-assisted analysis of pandas-dev/pandas@71959b8cb9 (2026-08-07). Data as JSON: /api/errors/6e3805f65b7e107c. Report an issue: GitHub.