Working with Durations in Python

Dates And Time Mar 19, 2026

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 Timedelta for 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 Timedelta for 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 pendulum for 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

Related Tutorials

Share:
Last updated: March 2026
Built-in Functions in Python 2026 str() in Python 2026: String Conversion + Modern Formatting & Best Practices memoryview with JAX in Python 2026: Zero-Copy NumPy → JAX Array Interop + Efficient ML Examples memoryview with TensorFlow in Python 2026: Zero-Copy NumPy → Tensor Interop + GPU Pinning & ML Examples memoryview with TensorFlow in Python 2026: Zero-Copy NumPy → Tensor Interop + ML Examples memoryview with NumPy & PyTorch in Python 2026: Zero-Copy Views, Efficient Slicing & ML Interop Examples memoryview with NumPy in Python 2026: Zero-Copy Views, Efficient Slicing & Real ML Examples memoryview() in Python 2026: Zero-Copy Magic for Large Binary Data + Real Examples import() in Python 2026: How It Works, Security Risks & Modern Alternatives (importlib) zip() in Python 2026: How to Use It, strict=True Behavior & Real-World Examples vars() in Python 2026: Accessing Object Namespace + Modern Introspection Patterns type() in Python 2026: Dynamic Type Inspection & Object Creation + Modern Patterns tuple() in Python 2026: Immutable Sequences + Modern Patterns & Best Practices super() in Python 2026: Method Resolution & Modern Inheritance Patterns sum() in Python 2026: Summing Iterables + Modern Numeric Patterns & Best Practices staticmethod() in Python 2026: Static Methods, Utility Functions & Modern Best Practices sorted() in Python 2026: Sorting Iterables + Modern Patterns & Best Practices slice() in Python 2026: Creating Slice Objects + Modern Patterns & Best Practices setattr() in Python 2026: Dynamic Attribute Setting + Modern Patterns & Safety set() in Python 2026: Mutable Sets Creation + Modern Patterns & Best Practices