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 DatetimeIndex.tz_convert when self.tz is None. tz_convert only re-interprets existing absolute (UTC) instants into another tz; it cannot invent a tz for naive data, so it points the user at tz_localize instead. TypeError.

Source

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

        ... )

        >>> dti
        DatetimeIndex(['2014-08-01 09:00:00+02:00',
                       '2014-08-01 10:00:00+02:00',
                       '2014-08-01 11:00:00+02:00'],
                        dtype='datetime64[us, Europe/Berlin]', freq='h')

        >>> dti.tz_convert(None)
        DatetimeIndex(['2014-08-01 07:00:00',
                       '2014-08-01 08:00:00',
                       '2014-08-01 09:00:00'],
                        dtype='datetime64[us]', freq='h')
        """  # noqa: E501
        tz = timezones.maybe_get_tz(tz)

        if self.tz is None:
            # tz naive, use tz_localize
            raise TypeError(
                "Cannot convert tz-naive timestamps, use tz_localize to localize"
            )

        # No conversion since timestamps are all UTC to begin with
        dtype = tz_to_dtype(tz, unit=self.unit)
        return self._simple_new(self._ndarray, dtype=dtype)

    @dtl.ravel_compat
    def tz_localize(
        self,
        tz,
        ambiguous: TimeAmbiguous = "raise",
        nonexistent: TimeNonexistent = "raise",
    ) -> Self:
        """
        Localize tz-naive Datetime Array/Index to tz-aware Datetime Array/Index.

        This method takes a time zone (tz) naive Datetime Array/Index object

View on GitHub (pinned to 71959b8cb9)

Solutions

  1. Use tz_localize to attach a tz first: idx.tz_localize('UTC').tz_convert('US/Eastern').
  2. Check idx.tz is not None before calling tz_convert; branch to tz_localize when it is None.
  3. At ingestion, parse with utc=True or tz_localize immediately so downstream code always sees aware data.

Example fix

# before
idx.tz_convert('US/Eastern')
# after
idx.tz_localize('UTC').tz_convert('US/Eastern')
Defensive patterns

Strategy: validation

Validate before calling

def to_tz(idx, target):
    if idx.tz is None:
        return idx.tz_localize(target)
    return idx.tz_convert(target)

Type guard

def is_aware(idx) -> bool:
    return getattr(idx, 'tz', None) is not None

Try / catch

try:
    out = idx.tz_convert('US/Eastern')
except TypeError as e:
    if 'tz-naive' in str(e):
        out = idx.tz_localize('UTC').tz_convert('US/Eastern')
    else:
        raise

Prevention

When it happens

Trigger: Calling .tz_convert('US/Eastern') on a DatetimeIndex/Series whose tz is None (created by pd.to_datetime without tz, pd.date_range without tz=, or read_csv without parse tz). Reached via Series.dt.tz_convert too.

Common situations: Confusing tz_localize (attach tz) with tz_convert (change tz); assuming pd.to_datetime already attaches UTC; calling tz_convert in a generic helper that receives mixed-awareness indexes.

Related errors


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