Introduction
To calculate the difference between two dates in Python, subtract one datetime.date or datetime.datetime object from another. The result is a timedelta object that stores the difference in days, seconds, and microseconds. Access the total difference in days with .days or in seconds with .total_seconds(). For more human-readable differences (years, months), use the dateutil.relativedelta module. The built-in datetime module handles most use cases without external dependencies.
Basic Date Difference
1from datetime import date
2
3date1 = date(2025, 1, 1)
4date2 = date(2025, 3, 15)
5
6diff = date2 - date1
7print(diff) # 73 days, 0:00:00
8print(diff.days) # 73
9print(type(diff)) # <class 'datetime.timedelta'>
10
11# Absolute difference (order doesn't matter)
12diff2 = date1 - date2
13print(diff2.days) # -73
14print(abs(diff2).days) # 73
Subtracting two date objects returns a timedelta. The .days attribute gives the difference as an integer number of days.
DateTime Difference (With Time)
1from datetime import datetime
2
3dt1 = datetime(2025, 1, 1, 8, 0, 0)
4dt2 = datetime(2025, 1, 3, 14, 30, 45)
5
6diff = dt2 - dt1
7print(diff) # 2 days, 6:30:45
8print(diff.days) # 2
9print(diff.seconds) # 23445 (6*3600 + 30*60 + 45)
10print(diff.total_seconds()) # 196245.0 (total including days)
11
12# Convert to hours, minutes
13total_hours = diff.total_seconds() / 3600
14print(f"{total_hours:.1f} hours") # 54.5 hours
15
16total_minutes = diff.total_seconds() / 60
17print(f"{total_minutes:.0f} minutes") # 3271 minutes
For datetime objects, the timedelta includes both days and time components. Use .total_seconds() to get the complete difference as a single number, including the days portion.
Parsing Date Strings
1from datetime import datetime
2
3# Parse date strings with strptime
4date_str1 = "2025-01-15"
5date_str2 = "2025-03-20"
6
7dt1 = datetime.strptime(date_str1, "%Y-%m-%d")
8dt2 = datetime.strptime(date_str2, "%Y-%m-%d")
9
10diff = dt2 - dt1
11print(f"Difference: {diff.days} days") # 64 days
12
13# Other common formats
14datetime.strptime("15/01/2025", "%d/%m/%Y")
15datetime.strptime("Jan 15, 2025", "%b %d, %Y")
16datetime.strptime("2025-01-15 14:30:00", "%Y-%m-%d %H:%M:%S")
strptime converts a date string to a datetime object using format codes (%Y = year, %m = month, %d = day, %H = hour, %M = minute, %S = second).
Difference in Years and Months (dateutil)
1from datetime import date
2from dateutil.relativedelta import relativedelta
3
4date1 = date(2020, 3, 15)
5date2 = date(2025, 7, 22)
6
7diff = relativedelta(date2, date1)
8print(f"{diff.years} years, {diff.months} months, {diff.days} days")
9# 5 years, 4 months, 7 days
10
11# Calculate age
12birthday = date(1990, 5, 20)
13today = date.today()
14age = relativedelta(today, birthday)
15print(f"Age: {age.years} years")
The built-in timedelta only stores days and seconds — it cannot express differences in months or years (because months have variable lengths). dateutil.relativedelta handles calendar-aware differences.
Working with Timezones
1from datetime import datetime, timezone, timedelta
2
3# UTC datetime
4utc_time = datetime(2025, 1, 15, 12, 0, 0, tzinfo=timezone.utc)
5
6# Eastern time (UTC-5)
7eastern = timezone(timedelta(hours=-5))
8eastern_time = datetime(2025, 1, 15, 7, 0, 0, tzinfo=eastern)
9
10# Difference accounts for timezone
11diff = utc_time - eastern_time
12print(diff) # 0:00:00 (same moment in time)
13
14# Using zoneinfo (Python 3.9+)
15from zoneinfo import ZoneInfo
16
17tokyo = datetime(2025, 1, 15, 21, 0, 0, tzinfo=ZoneInfo("Asia/Tokyo"))
18london = datetime(2025, 1, 15, 12, 0, 0, tzinfo=ZoneInfo("Europe/London"))
19
20diff = tokyo - london
21print(diff) # 0:00:00 (same moment: Tokyo is UTC+9, London is UTC+0)
When both datetimes are timezone-aware, subtraction correctly accounts for the UTC offset. Mixing timezone-aware and naive datetimes raises TypeError.
Business Days Difference
1import numpy as np
2from datetime import date
3
4date1 = date(2025, 1, 6) # Monday
5date2 = date(2025, 1, 17) # Friday
6
7# numpy.busday_count counts weekdays between two dates
8business_days = np.busday_count(date1, date2)
9print(f"Business days: {business_days}") # 9
10
11# Excluding holidays
12holidays = ['2025-01-13'] # MLK Day
13business_days = np.busday_count(date1, date2, holidays=holidays)
14print(f"Business days (excl holidays): {business_days}") # 8
Common Date Calculations
1from datetime import date, timedelta
2
3today = date.today()
4
5# Days until a future date
6target = date(2025, 12, 31)
7days_left = (target - today).days
8print(f"Days until end of year: {days_left}")
9
10# Days since a past date
11start = date(2025, 1, 1)
12days_elapsed = (today - start).days
13print(f"Days since Jan 1: {days_elapsed}")
14
15# Add/subtract days
16tomorrow = today + timedelta(days=1)
17last_week = today - timedelta(weeks=1)
18in_90_days = today + timedelta(days=90)
19
20# Check if a date is within a range
21deadline = date(2025, 6, 30)
22is_overdue = today > deadline
23days_until = max(0, (deadline - today).days)
1from datetime import datetime
2
3dt1 = datetime(2025, 1, 1)
4dt2 = datetime(2025, 3, 15, 14, 30)
5
6diff = dt2 - dt1
7
8# Custom formatting
9total_seconds = int(diff.total_seconds())
10days = diff.days
11hours = (total_seconds % 86400) // 3600
12minutes = (total_seconds % 3600) // 60
13seconds = total_seconds % 60
14
15print(f"{days}d {hours}h {minutes}m {seconds}s") # 73d 14h 30m 0s
16
17# Human-readable function
18def format_timedelta(td):
19 days = td.days
20 hours, remainder = divmod(td.seconds, 3600)
21 minutes, seconds = divmod(remainder, 60)
22
23 parts = []
24 if days: parts.append(f"{days} day{'s' if days != 1 else ''}")
25 if hours: parts.append(f"{hours} hour{'s' if hours != 1 else ''}")
26 if minutes: parts.append(f"{minutes} minute{'s' if minutes != 1 else ''}")
27 return ", ".join(parts) or "0 minutes"
28
29print(format_timedelta(diff)) # 73 days, 14 hours, 30 minutes
Common Pitfalls
Using .seconds instead of .total_seconds(): timedelta.seconds only returns the seconds component (0-86399), ignoring the days. For a 3-day difference, .seconds is 0 while .total_seconds() is 259200. Always use .total_seconds() for the complete difference.
Mixing timezone-aware and naive datetimes: Subtracting a timezone-aware datetime from a naive one raises TypeError: can't subtract offset-naive and offset-aware datetimes. Either make both aware or both naive before comparing.
Assuming months have fixed length: timedelta does not support months because months have 28-31 days. timedelta(months=1) is not valid. Use dateutil.relativedelta(months=1) for calendar-aware month arithmetic.
Negative timedelta confusion: date(2025, 1, 1) - date(2025, 3, 1) returns a negative timedelta with .days = -59. Use abs() to get the absolute difference regardless of order.
Forgetting strptime format mismatches: datetime.strptime("15/01/2025", "%Y-%m-%d") raises ValueError because the format does not match the string. The format string must exactly match the input pattern.
Summary
Subtract two date or datetime objects to get a timedelta with the difference
Use .days for whole days and .total_seconds() for the complete difference in seconds
Use dateutil.relativedelta for differences in years, months, and days
Parse date strings with datetime.strptime() before performing arithmetic
Use abs() to get a positive difference regardless of date order
Make both datetimes timezone-aware or both naive to avoid TypeError