pd.to_timedelta() converts duration text or numeric measurements into elapsed time. Add or subtract durations from timestamps, compare response times with a target, or report total hours. Use calendar offsets when the requirement is a number of months or years.
| Input or requirement | Approach |
|---|---|
| Numeric minutes | pd.to_timedelta(values, unit="min") |
| Duration text | pd.to_timedelta("1 days 02:30:00") |
| Invalid input | errors="coerce" gives NaT; default raise stops |
| Full elapsed seconds | series.dt.total_seconds() |
| Calendar month or year | pd.DateOffset(months=1) or pd.DateOffset(years=1) |
Use pd.to_timedelta() for elapsed time, and to_datetime() for a point in time. A scalar input returns a Timedelta, a list returns a TimedeltaIndex, and a Series returns a Series. Duration resolution can differ by input and Pandas version.
import pandas as pd
duration = pd.to_timedelta("1 days 05:22:34.006")
print(duration)
series = pd.to_timedelta(pd.Series(["30 min", "2 h", "1 day"]))
print(series)
assert duration == pd.Timedelta(days=1, hours=5, minutes=22, seconds=34, milliseconds=6)
assert isinstance(series, pd.Series)Expected output
1 days 05:22:34.006000
0 0 days 00:30:00
1 0 days 02:00:00
2 1 days 00:00:00
dtype: timedelta64[us]Numeric values default to nanoseconds. For a source measured in minutes, specify unit="min". For strings, include units in the text and omit the unit argument. Use lowercase h, min, s, ms, us and ns for clock units. Months and years are not fixed durations.
print("Two without a unit:", pd.to_timedelta(2))
print("Two hours:", pd.to_timedelta(2, unit="h"))
print(pd.to_timedelta([15, 30, 90], unit="min"))
assert pd.to_timedelta(2) == pd.Timedelta(nanoseconds=2)Expected output
Two without a unit: 0 days 00:00:00.000000002
Two hours: 0 days 02:00:00
TimedeltaIndex(['0 days 00:15:00', '0 days 00:30:00', '0 days 01:30:00'], dtype='timedelta64[s]', freq=None)Add a fixed elapsed duration to a timestamp. The fixed base makes the output repeatable. Use pd.Timestamp.now(tz="UTC") as the base to calculate from the current UTC time.
base = pd.Timestamp("2020-04-13 13:35:44.877832")
change = pd.to_timedelta(10, unit="D")
future = base + change
print("Base:", base)
print("Duration:", change)
print("Future:", future)
assert future - base == changeExpected output
Base: 2020-04-13 13:35:44.877832
Duration: 10 days 00:00:00
Future: 2020-04-23 13:35:44.877832Add a fixed elapsed duration to a timestamp. The fixed base makes the output repeatable. Use pd.Timestamp.now(tz="UTC") as the base to calculate from the current UTC time.
base = pd.Timestamp("2020-04-13 13:35:44.877832")
change = pd.to_timedelta(2, unit="W")
future = base + change
print("Base:", base)
print("Duration:", change)
print("Future:", future)
assert future - base == changeExpected output
Base: 2020-04-13 13:35:44.877832
Duration: 14 days 00:00:00
Future: 2020-04-27 13:35:44.877832Add a fixed elapsed duration to a timestamp. The fixed base makes the output repeatable. Use pd.Timestamp.now(tz="UTC") as the base to calculate from the current UTC time.
base = pd.Timestamp("2020-04-13 13:35:44.877832")
change = pd.to_timedelta(2, unit="h")
future = base + change
print("Base:", base)
print("Duration:", change)
print("Future:", future)
assert future - base == changeExpected output
Base: 2020-04-13 13:35:44.877832
Duration: 0 days 02:00:00
Future: 2020-04-13 15:35:44.877832Add a fixed elapsed duration to a timestamp. The fixed base makes the output repeatable. Use pd.Timestamp.now(tz="UTC") as the base to calculate from the current UTC time.
base = pd.Timestamp("2020-04-13 13:35:44.877832")
change = pd.to_timedelta(10, unit="min")
future = base + change
print("Base:", base)
print("Duration:", change)
print("Future:", future)
assert future - base == changeExpected output
Base: 2020-04-13 13:35:44.877832
Duration: 0 days 00:10:00
Future: 2020-04-13 13:45:44.877832Add a fixed elapsed duration to a timestamp. The fixed base makes the output repeatable. Use pd.Timestamp.now(tz="UTC") as the base to calculate from the current UTC time.
base = pd.Timestamp("2020-04-13 13:35:44.877832")
change = pd.to_timedelta(10, unit="s")
future = base + change
print("Base:", base)
print("Duration:", change)
print("Future:", future)
assert future - base == changeExpected output
Base: 2020-04-13 13:35:44.877832
Duration: 0 days 00:00:10
Future: 2020-04-13 13:35:54.877832Add a fixed elapsed duration to a timestamp. The fixed base makes the output repeatable. Use pd.Timestamp.now(tz="UTC") as the base to calculate from the current UTC time.
base = pd.Timestamp("2020-04-13 13:35:44.877832")
change = pd.to_timedelta(100, unit="ms")
future = base + change
print("Base:", base)
print("Duration:", change)
print("Future:", future)
assert future - base == changeExpected output
Base: 2020-04-13 13:35:44.877832
Duration: 0 days 00:00:00.100000
Future: 2020-04-13 13:35:44.977832Add a fixed elapsed duration to a timestamp. The fixed base makes the output repeatable. Use pd.Timestamp.now(tz="UTC") as the base to calculate from the current UTC time.
base = pd.Timestamp("2020-04-13 13:35:44.877832")
change = pd.to_timedelta(100, unit="us")
future = base + change
print("Base:", base)
print("Duration:", change)
print("Future:", future)
assert future - base == changeExpected output
Base: 2020-04-13 13:35:44.877832
Duration: 0 days 00:00:00.000100
Future: 2020-04-13 13:35:44.877932A month or year does not have a fixed number of seconds. Use DateOffset rather than unit="M" or unit="Y". Month-end and leap-day results follow calendar rules; define the rule your schedule needs.
base = pd.Timestamp("2020-04-13 13:35:44")
print("Four months:", base + pd.DateOffset(months=4))
print("Four years:", base + pd.DateOffset(years=4))
print("Month end:", pd.Timestamp("2024-01-31") + pd.DateOffset(months=1))
print("Leap day plus one year:", pd.Timestamp("2024-02-29") + pd.DateOffset(years=1))
assert pd.Timestamp("2024-01-31") + pd.DateOffset(months=1) == pd.Timestamp("2024-02-29")Expected output
Four months: 2020-08-13 13:35:44
Four years: 2024-04-13 13:35:44
Month end: 2024-02-29 00:00:00
Leap day plus one year: 2025-02-28 00:00:00Parse a timestamp with an explicit format. Calendar years preserve the time of day here. A duration string can combine days, hours, minutes and fractional seconds.
base = pd.to_datetime("12252020:233445", format="%m%d%Y:%H%M%S")
print("Five calendar years later:", base + pd.DateOffset(years=5))
delta = pd.to_timedelta("1 days 5 hours 22 minute 34.006 second")
start = pd.to_datetime("2001-11-27 16:20:05", format="%Y-%m-%d %H:%M:%S")
print("Combined duration:", delta)
print("After addition:", start + delta)
assert base + pd.DateOffset(years=5) == pd.Timestamp("2025-12-25 23:34:45")
assert start + delta == pd.Timestamp("2001-11-28 21:42:39.006")Expected output
Five calendar years later: 2025-12-25 23:34:45
Combined duration: 1 days 05:22:34.006000
After addition: 2001-11-28 21:42:39.006000Store Timedelta values in a Series and add them to a timestamp. Arithmetic between two Series aligns by index labels; verify labels when combining independently collected columns.
durations = pd.Series([pd.Timedelta(days=day) for day in range(6)])
print(durations)
print("Dtype:", durations.dtype)
print(pd.Timestamp("2024-01-01") + durations)
assert durations.iloc[-1] == pd.Timedelta(days=5)Expected output
0 0 days
1 1 days
2 2 days
3 3 days
4 4 days
5 5 days
dtype: timedelta64[us]
Dtype: timedelta64[us]
0 2024-01-01
1 2024-01-02
2 2024-01-03
3 2024-01-04
4 2024-01-05
5 2024-01-06
dtype: datetime64[us]errors="raise" stops on invalid input. errors="coerce" gives NaT. Current Pandas supports raise and coerce; errors="ignore" is not supported. Preserve raw text and distinguish invalid nonblank values from missing input.
raw = pd.Series(["30 min", "bad duration", "", None], dtype="string")
try:
pd.to_timedelta(raw, errors="raise")
except ValueError:
print("Invalid duration found; inspect raw input.")
parsed = pd.to_timedelta(raw, errors="coerce")
missing = raw.isna() | raw.str.strip().eq("").fillna(False)
invalid = ~missing & parsed.isna()
print(pd.DataFrame({"raw": raw, "duration": parsed, "missing": missing, "invalid": invalid}))
assert missing.sum() == 2 and invalid.sum() == 1Expected output
Invalid duration found; inspect raw input.
raw duration missing invalid
0 30 min 0 days 00:30:00 False False
1 bad duration NaT False True
2 NaT True False
3 <NA> NaT True FalseFor a duration longer than one day, .seconds gives the remaining seconds component. Use total_seconds for the complete elapsed time. A Series uses the .dt accessor; dividing by a one-hour Timedelta gives total hours.
duration = pd.to_timedelta("1 days 02:30:00")
print("Seconds component:", duration.seconds)
print("Total seconds:", duration.total_seconds())
values = pd.to_timedelta(pd.Series(["1 days 02:30:00", "00:30:00"]))
print("Total hours:")
print(values / pd.Timedelta(hours=1))
print("Total seconds:")
print(values.dt.total_seconds())
assert duration.total_seconds() == 95400
assert duration.seconds == 9000Expected output
Seconds component: 9000
Total seconds: 95400.0
Total hours:
0 26.5
1 0.5
dtype: float64
Total seconds:
0 95400.0
1 1800.0
dtype: float64Subtracting a duration moves a timestamp earlier. Negative durations display normalized days and a positive clock component. Inspect total_seconds when the sign matters. A negative response time needs review rather than conversion to its absolute value.
base = pd.Timestamp("2024-01-01 10:00:00")
print("Thirty minutes earlier:", base - pd.to_timedelta(30, unit="min"))
negative = pd.to_timedelta(-30, unit="min")
print("Negative duration:", negative)
print("Total seconds:", negative.total_seconds())
assert negative.total_seconds() == -1800Expected output
Thirty minutes earlier: 2024-01-01 09:30:00
Negative duration: -1 days +23:30:00
Total seconds: -1800.0A Timedelta day is 24 elapsed hours. A DateOffset day follows the calendar and can span 23 or 25 elapsed hours across a daylight-saving change. This example uses a timezone with a spring clock change; select the rule that matches your deadline policy.
base = pd.Timestamp("2024-03-09 12:00:00", tz="America/New_York")
elapsed = base + pd.to_timedelta(1, unit="D")
calendar = base + pd.DateOffset(days=1)
print("24 elapsed hours:", elapsed)
print("Next calendar day:", calendar)
assert elapsed.hour == 13 and calendar.hour == 12
assert (calendar - base).total_seconds() == 23 * 3600Expected output
24 elapsed hours: 2024-03-10 13:00:00-04:00
Next calendar day: 2024-03-10 12:00:00-04:00This synthetic support dataset includes a slow response, a missing finish time and a reversed timestamp. Parse UTC timestamps, calculate elapsed time and create a 90-minute threshold with to_timedelta. Keep unknown or negative durations in a review group.
def ticket_data():
return pd.DataFrame({"ticket_id": [101, 102, 103, 104, 105],
"start": ["2024-01-01 09:00:00"] * 5,
"finish": ["2024-01-01 09:30:00", "2024-01-01 10:30:00", "2024-01-01 11:00:00", None, "2024-01-01 08:45:00"]})
tickets = ticket_data()
for column in ["start", "finish"]:
tickets[column] = pd.to_datetime(tickets[column], format="%Y-%m-%d %H:%M:%S", utc=True, errors="coerce")
tickets["elapsed"] = tickets["finish"] - tickets["start"]
threshold = pd.to_timedelta(90, unit="min")
tickets["needs_review"] = tickets["elapsed"].isna() | tickets["elapsed"].lt(pd.Timedelta(0))
tickets["status"] = "review"
valid = ~tickets["needs_review"]
tickets.loc[valid & tickets["elapsed"].le(threshold), "status"] = "within target"
tickets.loc[valid & tickets["elapsed"].gt(threshold), "status"] = "over target"
tickets["total_minutes"] = tickets["elapsed"].dt.total_seconds() / 60
print(tickets[["ticket_id", "elapsed", "total_minutes", "status"]].to_string(index=False))
assert tickets["status"].tolist() == ["within target", "within target", "over target", "review", "review"]Expected output
ticket_id elapsed total_minutes status
101 0 days 00:30:00 30.0 within target
102 0 days 01:30:00 90.0 within target
103 0 days 02:00:00 120.0 over target
104 NaT NaN review
105 -1 days +23:45:00 -15.0 reviewA response of exactly 90 minutes meets this inclusive target. Exclude review records from the target rate and disclose their count. Missing finish times are not zero. For portable CSV output, export total minutes with explicit review labels.
from pathlib import Path
valid_tickets = tickets.loc[~tickets["needs_review"]]
within = valid_tickets["elapsed"].le(threshold).sum()
print("Valid responses:", len(valid_tickets))
print("Within target:", within)
print("Review records:", tickets["needs_review"].sum())
print("Target rate:", round(100 * within / len(valid_tickets), 1))
assert len(valid_tickets) + tickets["needs_review"].sum() == len(tickets)
assert valid_tickets["total_minutes"].sum() == 240
folder = Path("pandas_timedelta_practice")
folder.mkdir(exist_ok=True)
path = folder / "response_minutes.csv"
tickets[["ticket_id", "total_minutes", "status"]].to_csv(path, index=False)
restored = pd.read_csv(path)
assert len(restored) == 5 and restored["total_minutes"].isna().sum() == 1
assert restored["status"].tolist() == tickets["status"].tolist()
print("Verified CSV:", path.as_posix())Expected output
Valid responses: 3
Within target: 2
Review records: 2
Target rate: 66.7
Verified CSV: pandas_timedelta_practice/response_minutes.csvUnexpected tiny value: specify the unit for numeric input. Months or years rejected: use DateOffset for calendar changes. NaT appeared: inspect raw values and missing-input flags. Total is too small: use total_seconds rather than the seconds component. Negative time: check timestamp order and source timezones. Different wall-clock deadline: choose elapsed versus calendar arithmetic. Label mismatch: Series arithmetic aligns indexes. For further practice, combine Excel imports, data cleaning and time-based summaries.
Convert 30, 90, a missing value and -5 into durations. Flag unknown or negative durations for review. With an inclusive 60-minute target, predict one within-target record, one over-target record and two review records.
Check validity before comparing durations with a threshold. Missing comparisons must not silently count as successful responses. A nullable integer column preserves whole-minute measurements and missing values.
minutes = pd.Series([30, 90, None, -5], dtype="Int64")
durations = pd.to_timedelta(minutes, unit="min")
review = durations.isna() | durations.lt(pd.Timedelta(0))
within = ~review & durations.le(pd.to_timedelta(60, unit="min"))
over = ~review & durations.gt(pd.to_timedelta(60, unit="min"))
print(pd.DataFrame({"minutes": minutes, "duration": durations, "review": review, "within": within, "over": over}))
assert within.sum() == over.sum() == 1
assert review.sum() == 2Expected output
minutes duration review within over
0 30 0 days 00:30:00 False True False
1 90 0 days 01:30:00 False False True
2 <NA> NaT True False False
3 -5 -1 days +23:55:00 True False FalseOpen in Google Colab View on GitHub
All synthetic data is included. Run the duration examples, inspect review records and try the exercise. Save a copy in Drive before editing.
Continue with Pandas date and time, date_range(), period_range() and Excel export. Reference: Pandas to_timedelta documentation.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.