Working with Durations in Python
Durations (the difference between two points in time) are fundamental in data science — calculating customer lifetime, session length, time since last purchase, delivery delays, or rolling windows. Python’s timedelta and pandas Timedelta make working with durations clean, accurate, and highly performant.
TL;DR — Core Tools for Durations
timedelta→ basic arithmetic(dt2 - dt1)→ automatic duration.total_seconds(),.days, etc. → convert to numbers- Pandas
Timedeltafor vectorized operations on DataFrames
1. Basic Duration Creation and Arithmetic
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
start = datetime(2026, 3, 19, 9, 0, 0, tzinfo=ZoneInfo("UTC"))
end = datetime(2026, 3, 19, 14, 30, 0, tzinfo=ZoneInfo("UTC"))
duration = end - start
print(duration) # 5:30:00
print(duration.total_seconds()) # 19800.0
print(duration.days) # 0
2. Real-World Data Science Examples
import pandas as pd
# Create a small demo file so this example runs as-is
# (in your project, sales_data.csv is your real export)
import numpy as np
rng = np.random.default_rng(42)
n = 60
pd.DataFrame({
"order_id": range(1001, 1001 + n),
"order_date": pd.date_range("2026-01-01", periods=n, freq="3D"),
"customer_id": rng.integers(101, 121, n),
"customer_name": rng.choice([" ana", "ben ", "chen", "dana"], n),
"product_id": rng.integers(1, 9, n),
"region": rng.choice(["North", "South", "East", "West"], n),
"category": rng.choice(["Electronics", "Computers", "Books", "Home"], n),
"payment_method": rng.choice(["card", "cash", "wallet"], n),
"segment": rng.choice(["consumer", "business"], n),
"quantity": rng.integers(1, 6, n),
"amount": rng.uniform(50, 2500, n).round(2),
"discount": rng.choice([0.0, 0.05, 0.1, 0.2], n),
}).assign(
price=lambda d: (d["amount"] / d["quantity"]).round(2),
profit=lambda d: (d["amount"] * rng.uniform(0.05, 0.3, n)).round(2),
).assign(
ship_date=lambda d: d["order_date"] + pd.Timedelta(days=2),
status=lambda d: np.where(d["amount"] > 2000, "priority", "standard"),
).to_csv("sales_data.csv", index=False)
pd.read_csv("sales_data.csv").rename(columns={"ship_date": "delivery_date"}).to_csv("sales_data.csv", index=False) # demo: delivery_date column
df = pd.read_csv("sales_data.csv", parse_dates=["order_date", "delivery_date"])
# Calculate duration between two columns
df["delivery_duration"] = df["delivery_date"] - df["order_date"]
# Convert to useful units
df["days_to_deliver"] = df["delivery_duration"].dt.days
df["hours_to_deliver"] = df["delivery_duration"].dt.total_seconds() / 3600
# Example: Session duration (demo start/end times)
df["start_time"] = df["order_date"] + pd.Timedelta(hours=9)
df["end_time"] = df["start_time"] + pd.Timedelta(minutes=45)
df["session_duration"] = df["end_time"] - df["start_time"]
df["session_minutes"] = df["session_duration"].dt.total_seconds() / 60
3. Creating Durations Manually
# Create specific durations
one_week = timedelta(weeks=1)
three_days = timedelta(days=3)
two_hours = timedelta(hours=2, minutes=30)
# Add to existing dates
future_date = datetime.now(ZoneInfo("UTC")) + timedelta(days=14)
past_date = datetime.now(ZoneInfo("UTC")) - timedelta(days=30)
4. Best Practices in 2026
- Use
Timedeltafor all pandas-based duration calculations - Always work with timezone-aware datetimes before calculating differences
- Convert durations to numeric units (
.days,.total_seconds()) for modeling - Use
pendulumfor human-readable duration strings when needed - Keep raw datetime columns and derived duration columns separate
Durations as timedelta
Subtracting two datetimes gives a timedelta, which stores days, seconds and microseconds. Its days attribute counts only whole days, while total_seconds() returns the complete duration, so use total_seconds for hours or minutes.
In pandas, subtracting datetime columns gives a timedelta column; .dt.days, .dt.total_seconds() and division by pd.Timedelta(hours=1) convert it to numbers.
Negative durations mean the end is before the start, which often signals swapped columns or bad data.
Worked Example: Hours, Days and Bad Rows
Delivery durations are converted to hours and checked for negative values.
import pandas as pd
df = pd.DataFrame({
"ordered": pd.to_datetime(["2026-03-01 09:00", "2026-03-02 18:30", "2026-03-05 12:00"]),
"delivered": pd.to_datetime(["2026-03-03 15:00", "2026-03-03 10:30", "2026-03-04 12:00"]),
})
df["duration"] = df["delivered"] - df["ordered"]
df["hours"] = df["duration"] / pd.Timedelta(hours=1)
df["days_attr"] = df["duration"].dt.days
print(df[["duration", "hours", "days_attr"]])
print(df[df["duration"] < pd.Timedelta(0)].index.tolist())
The first delivery took 54 hours, which .dt.days reports as 2 whole days. The third row has a negative duration of minus 24 hours and is flagged as bad data at index 2.
Practical Tips
Store durations in a single unit, such as minutes or hours, when exporting to tools that do not understand timedelta.
Converting with total_seconds() and dividing keeps the value precise.
Common Mistakes
- Using .days when hours matter.
- Ignoring negative durations.
- Mixing naive and aware timestamps.
- Converting durations to strings for calculations.
Quick Summary
- Subtracting datetimes gives a timedelta.
- total_seconds gives the full duration.
- Divide by Timedelta to get units.
- Negative values often signal bad data.
Conclusion
Working with durations is a core skill in data science. In 2026, Python’s timedelta and pandas Timedelta make time difference calculations fast, accurate, and vectorized. Use these tools to build features like delivery time, session length, customer recency, and rolling windows with clean and professional code.
Next steps:
- Add duration-based features (days since last order, session length, etc.) to one of your current datasets