pandas-dev/pandas · error · AttributeError

Cannot directly set timezone. Use tz_localize() or tz_conver

Error message

Cannot directly set timezone. Use tz_localize() or tz_convert() as appropriate

What it means

Raised by the tz property setter of DatetimeArray/DatetimeIndex. The tz attribute is read-only because timezone changes are not in-place conversions — localizing (adding a tz) and converting (shifting across zones) are different operations with different semantics (and DST ambiguity handling), so they each get their own method.

Source

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

        dtype: datetime64[us, UTC]
        >>> s.dt.tz
        datetime.timezone.utc

        For DatetimeIndex:

        >>> idx = pd.DatetimeIndex(
        ...     ["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"]
        ... )
        >>> idx.tz
        datetime.timezone.utc
        """  # noqa: E501
        # GH 18595
        return getattr(self.dtype, "tz", None)

    @tz.setter
    def tz(self, value):
        # GH 3746: Prevent localizing or converting the index by setting tz
        raise AttributeError(
            "Cannot directly set timezone. Use tz_localize() "
            "or tz_convert() as appropriate"
        )

    @property
    def tzinfo(self) -> tzinfo | None:
        """
        Alias for tz attribute
        """
        return self.tz

    @property  # NB: override with cache_readonly in immutable subclasses
    def is_normalized(self) -> bool:
        """
        Returns True if all of the dates are at midnight ("no time")
        """
        return is_date_array_normalized(self.asi8, self.tz, reso=self._creso)

View on GitHub (pinned to 71959b8cb9)

Solutions

  1. To attach a tz to naive data: idx = idx.tz_localize('UTC').
  2. To switch zones on tz-aware data: idx = idx.tz_convert('US/Eastern').
  3. To strip a tz: idx = idx.tz_localize(None).

Example fix

# before
idx.tz = 'UTC'

# after (naive -> aware)
idx = idx.tz_localize('UTC')
# or aware -> different zone
idx = idx.tz_convert('UTC')
Defensive patterns

Strategy: validation

Validate before calling

# do not assign; route to the correct API
if want_attach_tz: idx = idx.tz_localize(tz)
elif want_change_tz: idx = idx.tz_convert(tz)
elif want_drop_tz: idx = idx.tz_localize(None)

Try / catch

try:
    idx.tz = tz
except AttributeError as e:
    if 'Cannot directly set timezone' in str(e):
        idx = idx.tz_localize(tz) if idx.tz is None else idx.tz_convert(tz)
    else: raise

Prevention

When it happens

Trigger: idx.tz = 'UTC' on a DatetimeIndex or DatetimeArray-backed Series.dt; series.index.tz = pytz.UTC; df.index.tz = None.

Common situations: Users from other libraries (or older pandas idioms) assuming index.tz is assignable. Quick interactive attempts to 'just add a zone'. Tutorial code copied from a non-pandas source.

Related errors


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