Use lowercase access or math: get hours via .components['hours'], .dt.components['hours'], or divide by an hour; .Hours doesn’t exist.
You ran into AttributeError: ‘Timedelta’ Object Has No Attribute ‘Hours’ while working with a pandas duration. The message points to one thing: pandas exposes hour values in lowercase or through calculated totals, not with a capitalized attribute. This guide shows why the message appears, how to fix it for a single value or a whole column, and how to avoid it on future projects. You’ll also see clean, copy-ready snippets with tests that keep code safe as data changes.
AttributeError: ‘Timedelta’ Object Has No Attribute ‘Hours’ — Why It Happens
In pandas, attribute names are lowercase. A Timedelta exposes fields such as days, seconds, and microseconds, plus a full breakdown through components. There is no .Hours on a single Timedelta or on a Series.dt accessor. Any capitalized call triggers the exact message you saw.
Hours are available, just not as a top-level capitalized attribute. For one value, you can read td.components.hours. For a series or index, call .dt.components and select the 'hours' column. When you need total hours across day spans, use .total_seconds() divided by 3600, or divide by an hourly unit. Those patterns return clean, readable results and avoid fragile string parsing.
Case sensitivity causes most occurrences. A codebase that mixes Python and SQL might lean on capitalized names in SQL, then carry that habit into pandas by reflex. Another common source is auto-completion that guessed .Hours from a mental model of datetime tutorials. Pandas keeps attribute names simple and lowercase; stick to that pattern and the message disappears.
Quick Reproductions And What They Tell You
These short snippets show the same failure across common objects. Each one raises the same message and helps you locate a wrong attribute call fast.
- Trigger On A Single Value —
pd.Timedelta('2 days 05:00:00').Hours - Trigger On A Series —
s = pd.to_timedelta(pd.Series(['1:30:00','2:45:00'])); s.dt.Hours - Trigger On An Index —
idx = pd.to_timedelta(['03:00:00','19:15:00']); idx.Hours - Trigger After A Groupby —
df['gap'] = df['ts'].diff(); df['gap'].dt.Hours
Each snippet fails for the same reason: pandas exposes lowercase attributes and uses either components for clock parts or arithmetic for totals. Once you switch to those, the error is gone across the board.
Fixes That Work Right Away
Pick the pattern that matches your object and whether you need clock-style hours (0–23) or a continuous number across days. The code below reads cleanly and performs well on large frames.
- Read Hours From Components —
td = pd.Timedelta('2 days 05:06:07'); td.components.hours # 5 - Read Hours For A Series —
s = pd.to_timedelta(pd.Series(['1:30:00','26:45:00'])); s.dt.components['hours'] - Get Total Hours As Float —
td.total_seconds() / 3600 # 53.101944... - Vectorized Total Hours —
s / pd.Timedelta(hours=1) # or: s / np.timedelta64(1, 'h') - Round Or Floor Hours —
np.floor(s / np.timedelta64(1,'h')) # 1., 26.
One more pattern helps with tidy columns. If you need both clock hours and totals, compute and label them up front. Readers of your code will spot intent at a glance and won’t repeat fragile access calls down the file.
df['gap'] = df['ts'].diff()
comp = df['gap'].dt.components
df['hours_clock'] = comp['hours'] # 0–23, within the day
df['hours_total'] = df['gap'] / pd.Timedelta(hours=1) # float across days
Pandas Timedelta Hours Access — Correct Patterns
Here’s a compact view of the right access methods for common containers. Use components when you want the clock part and arithmetic when you need totals. The table keeps columns to three for mobile-friendly reading.
| Object | Clock Hours | Total Hours |
|---|---|---|
| Single Timedelta | td.components.hours |
td.total_seconds()/3600 |
| Series/Index | .dt.components['hours'] |
obj / np.timedelta64(1,'h') |
| DataFrame Column | df['col'].dt.components['hours'] |
df['col']/pd.Timedelta(hours=1) |
Pandas gives you two flavors of hour values. The breakdown from components reports the hour within a day, so a span of 26:45:00 shows days=1 and hours=2. The arithmetic route returns a continuous float across days, which fits rate math, averages, and thresholds.
You can also cast through NumPy when that suits your pipeline. With many rows, division by a time unit is concise and speedy.
# Three equivalent total-hour expressions for a Series s
s / pd.Timedelta(hours=1)
s / np.timedelta64(1, 'h')
s.dt.total_seconds() / 3600
If you prefer integer hours for bins, round, floor, or ceil. This avoids off-by-one surprises that pop up when string parsing trims seconds.
bins = np.floor(s / np.timedelta64(1, 'h')).astype('int64') # 0, 1, 2, ...
labels = pd.cut(bins, bins=[0,1,4,8,24,1000], right=False)
Edge Cases, Tests, And Safer Code
Watch these conditions. A small tweak keeps results stable, prevents hidden drift, and helps new teammates read the intention behind your math.
- Negative Durations — Components keep the sign on each part; totals from division return one signed float.
- NaT Values — Drop or fill before math.
componentsreturns NaNs per column; division returns NaN for the row. - Day Spans —
26:45:00yields componentshours=2withdays=1; total-hour math returns26.75. - Time Zones —
Timedeltahas no zone. Only differences between tz-aware timestamps carry zones earlier in the pipeline. - Overflow On Cast —
astype('timedelta64[h]')on massive ranges can clip in older stacks; prefer division for safety.
Small tests pay off. Drop these into a unit file or a notebook cell and you’ll catch regressions the moment a dataset shifts shape.
- Series Hours Match —
(s.dt.components['hours'] == (s % pd.Timedelta(days=1))/pd.Timedelta(hours=1)).all() - Totals Match Known Values —
np.isclose(pd.Timedelta('2 days 5:06:07').total_seconds()/3600, 53.101944, atol=1e-6) - Handles NaT —
pd.isna(pd.to_timedelta(['NaT','1h'])).sum() == 1
Parameter names help linters and readers. Prefer explicit constructor calls for clarity on intent.
# Clear intent with keyword args
one_hour = pd.Timedelta(hours=1)
two_days_five_hours = pd.Timedelta(days=2, hours=5)
When you feed text values to to_timedelta, normalize formats up front. A small cleanup avoids mixed parse paths and odd outcomes in late joins.
# Normalize inputs before to_timedelta
raw = pd.Series(['1:2:3', '01:02:03', '26:45:00', None])
norm = raw.fillna('0:00:00').str.replace(r'^\d:\d\d:', lambda m: '0' + m.group(0), regex=True)
td = pd.to_timedelta(norm)
Big frames respond well to vectorized division. It reads better than loops and stays inside the fast path of pandas and NumPy.
# Millions of rows: vectorized and clear
df['hours_total'] = df['gap'] / pd.Timedelta(hours=1)
slow = [x.total_seconds()/3600 for x in df['gap']] # avoid this on large data
Prevention Checklist You Can Reuse
A little hygiene prevents the error and keeps code readable on busy teams. These habits also help new hires match the code style on day one.
- Stick To Lowercase Attributes — Write
.days,.seconds, and.components; skip capitalized names. - Prefer Components For Clock Parts — It reads well and avoids manual math for hours, minutes, and seconds.
- Use Division For Totals — Divide by an hourly
Timedeltaornp.timedelta64; it’s concise and fast. - Name Columns Clearly —
hours_clockfor clock parts,hours_totalfor floats. Reviewers parse intent in a glance. - Pin Pandas And NumPy — Lock versions in
requirements.txt; run tests on a branch before bumping.
When you ship teaching docs or an internal wiki page, repeat the exact string AttributeError: ‘Timedelta’ Object Has No Attribute ‘Hours’ in plain text near the fix. That makes log searches land on the right page. Add short inline comments near the first use of .dt.components['hours'] and the first divide-by-hour expression. Less guesswork, fewer pings.
Two final inserts of the exact message help debugging threads and search inside your repo: AttributeError: ‘Timedelta’ Object Has No Attribute ‘Hours’ shows up when code calls a capitalized attribute; fix the name or switch to math. When articles and code reviews repeat AttributeError: ‘Timedelta’ Object Has No Attribute ‘Hours’ in plain text, teammates find answers faster and apply the same patterns across projects.
