{"record":{"id":"1483e41519e3be0a","repo":"pandas-dev/pandas","slug":"self-dtype-does-not-have-duration-components","errorCode":null,"errorMessage":"'{self.dtype}' does not have duration components","messagePattern":"'(.+?)' does not have duration components","errorType":"exception","errorClass":"AttributeError","httpStatus":null,"severity":"error","filePath":"pandas/core/arrays/arrow/array.py","lineNumber":3802,"sourceCode":"                pa.scalar(None, type=pa.int32()),\n                pa.scalar(0, type=pa.int32()),\n            )\n        )\n\n    @property\n    def _duration_unit(self) -> str:\n        \"\"\"\n        Return the time unit for a duration-typed array.\n\n        Raises ``AttributeError`` for non-duration dtypes so the duration\n        component accessors (``_dt_days``, ``_dt_seconds``, etc.) are rejected\n        for e.g. ``timestamp[pyarrow]`` instead of silently treating the\n        underlying int64 as a duration. The ``dt`` dispatcher turns this into\n        the usual \"dt.<name> is not supported for <dtype>\" error.\n        \"\"\"\n        pa_type = self._pa_array.type\n        if not pa.types.is_duration(pa_type):\n            raise AttributeError(f\"'{self.dtype}' does not have duration components\")\n        return pa_type.unit\n\n    @cache_readonly\n    def _dt_day_remainder(self) -> pa.ChunkedArray:\n        \"\"\"\n        Return the remainder after removing full days, always non-negative.\n\n        For negative durations like -22h 57m 57s (= -1 day + 1h 2m 3s),\n        this returns the positive offset from the day boundary.\n\n        This is cached because it's used by all sub-day component accessors.\n        \"\"\"\n        unit = self._duration_unit\n        divisor = _DURATION_DIVISORS[\"day\"][unit]\n        arr = self._pa_array.cast(pa.int64())\n        days = floor_div_int64(arr, divisor)\n        # remainder = arr - days * divisor (always non-negative)\n        return pc.subtract(arr, pc.multiply(days, divisor))","sourceCodeStart":3784,"sourceCodeEnd":3820,"githubUrl":"https://github.com/pandas-dev/pandas/blob/71959b8cb9b2459c16e14b34f28b178ccfe14735/pandas/core/arrays/arrow/array.py#L3784-L3820","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure the Series is a duration type: subtract two timestamps to get a duration, or cast: `s.astype(\"duration[s][pyarrow]\")` (if semantically valid).","Use the correct accessor for the dtype: timestamps use `s.dt.day`, `s.dt.hour`, not `s.dt.days`.","Convert to timedelta64[ns] then use timedelta accessors: `s.astype(\"timedelta64[ns]\").dt.days`.","Inspect dtype first: `print(s.dtype)` and switch the accessor accordingly."],"exampleFix":"# before\ns = pd.Series(pd.to_datetime([\"2024-01-01\",\"2024-01-02\"]), dtype=\"timestamp[us][pyarrow]\")\ns.dt.days  # AttributeError\n\n# after (compute a duration first)\ndur = s - s.iloc[0]\ndur.astype(\"duration[us][pyarrow]\").dt.days","handlingStrategy":"type-guard","validationCode":"import pyarrow as pa\n\ndef is_pyarrow_duration(s) -> bool:\n    try:\n        return pa.types.is_duration(s.dtype.pyarrow_dtype)\n    except AttributeError:\n        return False\n\ndef safe_duration_component(s, name):\n    if not is_pyarrow_duration(s):\n        raise AttributeError(f\"{s.dtype} has no duration components; use a duration[pyarrow] dtype\")\n    return getattr(s.dt, name)","typeGuard":"import pyarrow as pa\n\ndef is_duration_series(s) -> bool:\n    pa_dt = getattr(s.dtype, \"pyarrow_dtype\", None)\n    return pa_dt is not None and pa.types.is_duration(pa_dt)","tryCatchPattern":"try:\n    days = s.dt.days\nexcept AttributeError:\n    # not a duration; recompute as duration from timestamps if applicable\n    days = (s - s.iloc[0]).astype(\"duration[us][pyarrow]\").dt.days","preventionTips":["Subtract two timestamps to materialize a duration before using .dt.days/.seconds.","Check s.dtype and pa.types.is_duration before duration accessors.","Keep date vs timestamp vs duration dtypes explicit in schemas."],"tags":["pyarrow","datetime-accessor","duration","type-mismatch"],"analyzedSha":"71959b8cb9b2459c16e14b34f28b178ccfe14735","analyzedAt":"2026-08-07T01:30:20.476Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}