pandas-dev/pandas · error · AttributeError

'{self.dtype}' does not have duration components

Error message

'{self.dtype}' does not have duration components

What it means

Raised by the `_duration_unit` property of ArrowExtensionArray when the underlying pyarrow type is not a `pyarrow.duration(...)` type. The duration-component accessors (_dt_days, _dt_hours, _dt_seconds, etc.) call _duration_unit to obtain the unit, so calling them on a non-duration array (e.g. timestamp[pyarrow]) raises AttributeError. The dt dispatcher converts this into the standard 'dt.<name> is not supported for <dtype>' message.

Source

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

                pa.scalar(None, type=pa.int32()),
                pa.scalar(0, type=pa.int32()),
            )
        )

    @property
    def _duration_unit(self) -> str:
        """
        Return the time unit for a duration-typed array.

        Raises ``AttributeError`` for non-duration dtypes so the duration
        component accessors (``_dt_days``, ``_dt_seconds``, etc.) are rejected
        for e.g. ``timestamp[pyarrow]`` instead of silently treating the
        underlying int64 as a duration. The ``dt`` dispatcher turns this into
        the usual "dt.<name> is not supported for <dtype>" error.
        """
        pa_type = self._pa_array.type
        if not pa.types.is_duration(pa_type):
            raise AttributeError(f"'{self.dtype}' does not have duration components")
        return pa_type.unit

    @cache_readonly
    def _dt_day_remainder(self) -> pa.ChunkedArray:
        """
        Return the remainder after removing full days, always non-negative.

        For negative durations like -22h 57m 57s (= -1 day + 1h 2m 3s),
        this returns the positive offset from the day boundary.

        This is cached because it's used by all sub-day component accessors.
        """
        unit = self._duration_unit
        divisor = _DURATION_DIVISORS["day"][unit]
        arr = self._pa_array.cast(pa.int64())
        days = floor_div_int64(arr, divisor)
        # remainder = arr - days * divisor (always non-negative)
        return pc.subtract(arr, pc.multiply(days, divisor))

View on GitHub (pinned to 71959b8cb9)

Solutions

  1. Ensure the Series is a duration type: subtract two timestamps to get a duration, or cast: `s.astype("duration[s][pyarrow]")` (if semantically valid).
  2. Use the correct accessor for the dtype: timestamps use `s.dt.day`, `s.dt.hour`, not `s.dt.days`.
  3. Convert to timedelta64[ns] then use timedelta accessors: `s.astype("timedelta64[ns]").dt.days`.
  4. Inspect dtype first: `print(s.dtype)` and switch the accessor accordingly.

Example fix

# before
s = pd.Series(pd.to_datetime(["2024-01-01","2024-01-02"]), dtype="timestamp[us][pyarrow]")
s.dt.days  # AttributeError

# after (compute a duration first)
dur = s - s.iloc[0]
dur.astype("duration[us][pyarrow]").dt.days
Defensive patterns

Strategy: type-guard

Validate before calling

import pyarrow as pa

def is_pyarrow_duration(s) -> bool:
    try:
        return pa.types.is_duration(s.dtype.pyarrow_dtype)
    except AttributeError:
        return False

def safe_duration_component(s, name):
    if not is_pyarrow_duration(s):
        raise AttributeError(f"{s.dtype} has no duration components; use a duration[pyarrow] dtype")
    return getattr(s.dt, name)

Type guard

import pyarrow as pa

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

Try / catch

try:
    days = s.dt.days
except AttributeError:
    # not a duration; recompute as duration from timestamps if applicable
    days = (s - s.iloc[0]).astype("duration[us][pyarrow]").dt.days

Prevention

When it happens

Trigger: Calling `s.dt.days`, `s.dt.seconds`, `s.dt.microseconds`, `s.dt.components`, etc. on a Series whose dtype is `timestamp[pyarrow]`, `date32[pyarrow]`, or any non-duration pyarrow type. Confusing timedelta semantics (which support .dt.days) with timestamp semantics.

Common situations: Subtracting two timestamp Series and forgetting to keep the result as a duration; pandas may materialize it differently. Applying generic timedelta-style accessors to a column that was loaded from Parquet/Arrow as a timestamp.

Related errors


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