Three datetime problems come up again and again in data scripts: comparisons that fail because one value has a timezone and the other doesn’t, turning YYYYMMDD integers into folder paths or the next date, and measuring the gap between two timestamps in seconds. All of them are solved with the standard datetime module.
Fix “can’t compare offset-naive and offset-aware datetimes”
TypeError: can't compare offset-naive and offset-aware datetimes
This error appears when one datetime carries timezone information (offset-aware) and the other doesn’t (offset-naive). Make both the same kind before comparing.
Make a naive datetime aware (UTC)
With replace:
from datetime import datetime, timezone
# Create a naive datetime
cutoff_date = datetime(2024, 10, 17)
# Make it UTC offset-aware
cutoff_date_utc = cutoff_date.replace(tzinfo=timezone.utc)
print(cutoff_date_utc) # Output: 2024-10-17 00:00:00+00:00
Only do this if you know the naive value already represents UTC. replace attaches a timezone; it doesn’t convert the time.
With pytz, which is useful when you work with several timezones or with libraries that expect pytz objects:
from datetime import datetime
import pytz
# Create a naive datetime
cutoff_date = datetime(2024, 10, 17)
# Make it UTC offset-aware using pytz
utc_timezone = pytz.UTC
cutoff_date_utc = utc_timezone.localize(cutoff_date)
print(cutoff_date_utc) # Output: 2024-10-17 00:00:00+00:00
Make an aware datetime naive
# Remove timezone information to make it naive
cutoff_date_naive = cutoff_date_utc.replace(tzinfo=None)
print(cutoff_date_naive) # Output: 2024-10-17 00:00:00
Compare safely
Decide whether your code works with aware or naive datetimes, and convert everything to that kind before comparing:
from datetime import datetime, timezone
# Offset-aware datetime
current_time = datetime.now(timezone.utc)
# Offset-naive datetime
cutoff_date = datetime(2024, 10, 17)
# Convert cutoff_date to offset-aware
cutoff_date_utc = cutoff_date.replace(tzinfo=timezone.utc)
if current_time > cutoff_date_utc:
print("The current time is past the cutoff date.")
else:
print("The cutoff date is in the future.")
Timestamps from AWS APIs (S3 LastModified, for example) come back timezone-aware in UTC, which is a common source of this error when a script compares them with datetime(2024, 10, 17).
Extract year, month and day from YYYYMMDD
A typical case is building year/month/day folder paths from an integer date:
from datetime import datetime
def extract_year_month_day(date):
date_obj = datetime.strptime(str(date), "%Y%m%d")
year = str(date_obj.year)
month = str(date_obj.month).zfill(2) # Ensure leading zero for single-digit months
day = str(date_obj.day).zfill(2) # Ensure leading zero for single-digit days
return year, month, day
today = 20230906
year_folder, month_folder, day_folder = extract_year_month_day(today)
print(f"Year: {year_folder}, Month: {month_folder}, Day: {day_folder}")
Year: 2023, Month: 09, Day: 06
datetime.strptimeparses the string into adatetimeobject. It also validates it:20230231raisesValueError.zfill(2)pads single-digit months and days with a leading zero.
Calculate the next day
from datetime import datetime, timedelta
def next_day(date):
date_obj = datetime.strptime(str(date), "%Y%m%d")
next_date = date_obj + timedelta(days=1)
return next_date.strftime("%Y%m%d")
next_day_date = next_day(today)
print(f"Next day: {next_day_date}")
Next day: 20230907
timedelta(days=1) handles month and year boundaries (20231231 becomes 20240101), and strftime("%Y%m%d") formats the result back to YYYYMMDD.
Seconds between two datetimes: total_seconds(), not .seconds
Subtracting two datetimes gives a timedelta. Its .seconds attribute is tempting but wrong for this: it is only the seconds part of the duration (0 to 86399), with the days stored separately in .days. To get the whole duration in seconds, use total_seconds(), which the Python docs describe as “the total number of seconds contained in the duration”:
from datetime import datetime
start = datetime(2024, 10, 17, 8, 0, 0)
end = datetime(2024, 10, 18, 8, 0, 30)
delta = end - start
print(delta) # 1 day, 0:00:30
print(delta.seconds) # 30
print(delta.total_seconds()) # 86430.0
The same question with more examples: How do I check the difference, in seconds, between two dates?
Gotchas
==doesn’t raise. Ordering comparisons (<,>) between naive and aware datetimes raiseTypeError, but==returnsFalse. A naive/aware mismatch in an equality check fails silently.replace(tzinfo=...)is not a conversion. It labels the wall-clock time with a timezone. To convert between zones, useastimezone()on an aware datetime.timedelta.secondsdrops days. Usetotal_seconds()for durations.strptimeneeds a string. Integer dates like20230906must go throughstr()first.
Related
- Handling Python datetime objects in Amazon DynamoDB covers storing these values, since DynamoDB has no native datetime type.
- Efficiently listing and filtering S3 objects by date builds
year/month/dayprefixes like the ones above and returns awareLastModifiedtimestamps. - How to aggregate values by date and sum them in Python sums values per date key with a plain dictionary.
- Extracting year, month and day from dates in Azure Data Factory does the same split in ADF expressions.