pandas-dev/pandas · error · TypeError

Cannot convert tz-naive timestamps, use tz_localize to local

Error message

Cannot convert tz-naive timestamps, use tz_localize to localize

What it means

Raised by ArrowExtensionArray._dt_tz_convert when the Series timestamps are tz-naive (pyarrow timestamp type has tz=None). tz_convert requires an existing timezone to convert from; you must first localize naive timestamps. Raised as TypeError and reached through Series.dt.tz_convert() on a tz-naive timestamp[pyarrow] Series.

Source

Thrown at pandas/core/arrays/arrow/array.py:4279

            "shift_backward": "earliest",
            "shift_forward": "latest",
        }.get(
            nonexistent,  # type: ignore[arg-type]
            None,
        )
        if nonexistent_pa is None:
            raise NotImplementedError(f"{nonexistent=} is not supported")
        if tz is None:
            result = pc.local_timestamp(self._pa_array)
        else:
            result = pc.assume_timezone(
                self._pa_array, str(tz), ambiguous=ambiguous, nonexistent=nonexistent_pa
            )
        return self._from_pyarrow_array(result)

    def _dt_tz_convert(self, tz) -> Self:
        if self.dtype.pyarrow_dtype.tz is None:
            raise TypeError(
                "Cannot convert tz-naive timestamps, use tz_localize to localize"
            )
        current_unit = self.dtype.pyarrow_dtype.unit
        result = self._pa_array.cast(pa.timestamp(current_unit, tz))
        return self._from_pyarrow_array(result)


def transpose_homogeneous_pyarrow(
    arrays: Sequence[ArrowExtensionArray],
) -> list[ArrowExtensionArray]:
    """Transpose arrow extension arrays in a list, but faster.

    Input should be a list of arrays of equal length and all have the same
    dtype. The caller is responsible for ensuring validity of input data.
    """
    arrays = list(arrays)
    nrows, ncols = len(arrays[0]), len(arrays)
    indices = np.arange(nrows * ncols).reshape(ncols, nrows).T.reshape(-1)

View on GitHub (pinned to 71959b8cb9)

Solutions

  1. Localize first: `s.dt.tz_localize("UTC").dt.tz_convert("US/Eastern")`.
  2. Check `s.dtype.pyarrow_dtype.tz` before calling tz_convert; if None, call tz_localize instead.
  3. Load the data with explicit tz: parse with `pd.to_datetime(s, utc=True)` if source is UTC.
  4. Use `s.dt.tz` (None for naive) as a guard in pipeline code.

Example fix

# before
s = pd.Series(..., dtype="timestamp[us][pyarrow]")  # tz-naive
s.dt.tz_convert("UTC")  # TypeError

# after
s.dt.tz_localize("UTC").dt.tz_convert("US/Eastern")
Defensive patterns

Strategy: type-guard

Validate before calling

def is_tz_aware_pyarrow(s) -> bool:
    pa_dt = getattr(s.dtype, "pyarrow_dtype", None)
    return pa_dt is not None and getattr(pa_dt, "tz", None) is not None

def safe_tz_convert(s, tz):
    if not is_tz_aware_pyarrow(s):
        raise TypeError("Series is tz-naive; call tz_localize(tz) first")
    return s.dt.tz_convert(tz)

Type guard

import pyarrow as pa

def is_tz_aware_pyarrow(s) -> bool:
    pa_dt = getattr(s.dtype, "pyarrow_dtype", None)
    return pa_dt is not None and pa.types.is_timestamp(pa_dt) and pa_dt.tz is not None

Try / catch

try:
    out = s.dt.tz_convert(tz)
except TypeError:
    out = s.dt.tz_localize("UTC").dt.tz_convert(tz)

Prevention

When it happens

Trigger: Calling `s.dt.tz_convert("UTC")` on a Series whose dtype is `timestamp[us][pyarrow]` with no tz. Common after loading Parquet/Arrow data that stored timezone-naive timestamps.

Common situations: Assuming a column is tz-aware when it is actually naive; chaining tz_convert where tz_localize was needed; data ingestion stripping tz metadata.

Related errors


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