Pandas - Add/Subtract Days from Date

The most straightforward approach to adding or subtracting days uses `pd.Timedelta`. This method works with both individual datetime objects and entire Series.

Key Insights

  • Pandas provides pd.Timedelta and pd.DateOffset for date arithmetic, with Timedelta being more precise for exact durations and DateOffset handling calendar-aware operations like month-end adjustments
  • The + and - operators work directly on datetime columns, enabling vectorized operations across entire DataFrames without explicit loops
  • Business day calculations require pd.offsets.BDay() to automatically skip weekends and optionally handle custom holiday calendars

Basic Date Arithmetic with Timedelta

The most straightforward approach to adding or subtracting days uses pd.Timedelta. This method works with both individual datetime objects and entire Series.

import pandas as pd
from datetime import datetime

# Single date manipulation
date = pd.to_datetime('2024-01-15')
future_date = date + pd.Timedelta(days=10)
past_date = date - pd.Timedelta(days=5)

print(f"Original: {date}")
print(f"Plus 10 days: {future_date}")
print(f"Minus 5 days: {past_date}")

Output:

Original: 2024-01-15 00:00:00
Plus 10 days: 2024-01-25 00:00:00
Minus 5 days: 2024-01-10 00:00:00

For DataFrame operations, the same syntax applies to entire columns:

df = pd.DataFrame({
    'order_date': pd.to_datetime(['2024-01-15', '2024-02-20', '2024-03-10']),
    'delivery_days': [3, 5, 2]
})

# Add fixed number of days
df['estimated_delivery'] = df['order_date'] + pd.Timedelta(days=7)

# Add variable days based on column values
df['actual_delivery'] = df['order_date'] + pd.to_timedelta(df['delivery_days'], unit='D')

print(df)

Output:

  order_date  delivery_days estimated_delivery actual_delivery
0 2024-01-15              3         2024-01-22      2024-01-18
1 2024-02-20              5         2024-02-27      2024-02-25
2 2024-03-10              2         2024-03-17      2024-03-12

Working with DateOffset for Calendar-Aware Operations

While Timedelta adds exact time periods, pd.DateOffset handles calendar-specific logic like month boundaries and year transitions.

from pandas.tseries.offsets import DateOffset

df = pd.DataFrame({
    'start_date': pd.to_datetime(['2024-01-31', '2024-02-29', '2024-03-15'])
})

# Add 1 month using DateOffset (calendar-aware)
df['plus_1_month'] = df['start_date'] + DateOffset(months=1)

# Add 30 days using Timedelta (exact duration)
df['plus_30_days'] = df['start_date'] + pd.Timedelta(days=30)

# Combine days and months
df['complex_offset'] = df['start_date'] + DateOffset(months=2, days=15)

print(df)

Output:

  start_date plus_1_month plus_30_days complex_offset
0 2024-01-31   2024-02-29   2024-03-01     2024-04-15
1 2024-02-29   2024-03-29   2024-03-30     2024-05-13
2 2024-03-15   2024-04-15   2024-04-14     2024-05-30

Notice how DateOffset(months=1) intelligently handles month-end dates, while Timedelta(days=30) simply adds 30 days.

Business Day Calculations

Real-world applications often need to exclude weekends and holidays. Pandas provides BDay (business day) offset for this purpose.

from pandas.tseries.offsets import BDay

df = pd.DataFrame({
    'task_start': pd.to_datetime(['2024-01-12', '2024-01-19', '2024-01-26'])  # All Fridays
})

# Add 3 business days
df['task_end'] = df['task_start'] + BDay(3)

# Subtract 5 business days
df['review_start'] = df['task_start'] - BDay(5)

print(df)

Output:

  task_start   task_end review_start
0 2024-01-12 2024-01-17   2024-01-05
1 2024-01-19 2024-01-24   2024-01-12
2 2024-01-26 2024-01-31   2024-01-19

Starting from Friday, adding 3 business days lands on Wednesday (skipping Saturday and Sunday).

Custom Holiday Calendars

For organization-specific holidays, create custom business day calendars:

from pandas.tseries.holiday import USFederalHolidayCalendar, Holiday
from pandas.tseries.offsets import CustomBusinessDay

# Use US Federal holidays
us_bd = CustomBusinessDay(calendar=USFederalHolidayCalendar())

df = pd.DataFrame({
    'project_start': pd.to_datetime(['2024-07-03', '2024-12-23'])
})

df['milestone_5bd'] = df['project_start'] + 5 * us_bd

print(df)

This accounts for Independence Day (July 4th) and Christmas (December 25th) as non-business days.

For custom holidays:

from pandas.tseries.holiday import AbstractHolidayCalendar

class CompanyCalendar(AbstractHolidayCalendar):
    rules = [
        Holiday('Company Foundation Day', month=3, day=15),
        Holiday('Summer Shutdown Start', month=7, day=15),
    ]

company_bd = CustomBusinessDay(calendar=CompanyCalendar())

df = pd.DataFrame({
    'deadline': pd.to_datetime(['2024-03-13', '2024-07-12'])
})

df['extended_deadline'] = df['deadline'] + 3 * company_bd

print(df)

Vectorized Operations for Performance

When working with large datasets, avoid loops. Pandas operations are vectorized and significantly faster:

import numpy as np

# Generate large dataset
n = 100000
df = pd.DataFrame({
    'transaction_date': pd.date_range('2023-01-01', periods=n, freq='h'),
    'days_to_add': np.random.randint(1, 30, n)
})

# Efficient vectorized approach
df['future_date'] = df['transaction_date'] + pd.to_timedelta(df['days_to_add'], unit='D')

# Avoid this loop-based approach
# for idx, row in df.iterrows():  # SLOW!
#     df.at[idx, 'future_date'] = row['transaction_date'] + pd.Timedelta(days=row['days_to_add'])

The vectorized approach runs 100x+ faster on large datasets.

Conditional Date Arithmetic

Apply different offsets based on conditions using np.where or apply:

df = pd.DataFrame({
    'order_date': pd.to_datetime(['2024-01-15', '2024-02-20', '2024-03-10']),
    'priority': ['standard', 'express', 'standard']
})

# Express orders: +2 days, Standard: +5 days
df['delivery_date'] = df['order_date'] + pd.to_timedelta(
    np.where(df['priority'] == 'express', 2, 5), unit='D'
)

print(df)

Output:

  order_date  priority delivery_date
0 2024-01-15  standard    2024-01-20
1 2024-02-20   express    2024-02-22
2 2024-03-10  standard    2024-03-15

For complex conditions:

def calculate_delivery(row):
    if row['priority'] == 'express':
        return row['order_date'] + BDay(1)
    elif row['priority'] == 'standard':
        return row['order_date'] + BDay(3)
    else:
        return row['order_date'] + BDay(7)

df['delivery_date'] = df.apply(calculate_delivery, axis=1)

Handling Edge Cases

Always validate datetime conversions and handle potential errors:

df = pd.DataFrame({
    'date_string': ['2024-01-15', '2024-02-30', 'invalid', None]
})

# Convert with error handling
df['parsed_date'] = pd.to_datetime(df['date_string'], errors='coerce')

# Only add days to valid dates
df['future_date'] = df['parsed_date'] + pd.Timedelta(days=10)

print(df)

Output:

  date_string parsed_date future_date
0  2024-01-15  2024-01-15  2024-01-25
1  2024-02-30         NaT         NaT
2     invalid         NaT         NaT
3        None         NaT         NaT

The errors='coerce' parameter converts invalid dates to NaT (Not a Time), preventing exceptions.

Time Components in Date Arithmetic

When adding days to datetime objects with time components, the time is preserved:

df = pd.DataFrame({
    'timestamp': pd.to_datetime(['2024-01-15 14:30:00', '2024-02-20 09:15:00'])
})

df['plus_3_days'] = df['timestamp'] + pd.Timedelta(days=3)
df['plus_72_hours'] = df['timestamp'] + pd.Timedelta(hours=72)

print(df)

Output:

            timestamp       plus_3_days     plus_72_hours
0 2024-01-15 14:30:00 2024-01-18 14:30:00 2024-01-18 14:30:00
1 2024-02-20 09:15:00 2024-02-23 09:15:00 2024-02-23 09:15:00

Both operations yield identical results since 3 days equals 72 hours, and both preserve the time component.

Performance Comparison

For critical applications, choose the right tool:

  • Timedelta: Fastest for fixed-day operations, exact time calculations
  • DateOffset: Required for month/year arithmetic, slightly slower
  • BDay/CustomBusinessDay: Necessary for business logic, slowest due to calendar lookups

Use Timedelta when possible, upgrade to DateOffset or business day offsets only when calendar awareness is required.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.