Working with dates and times — datetime, timedelta, and timezones
datetime looks simple until timezones enter the picture — the single most important fact about it is that a naive datetime and an aware datetime are not the same type of thing, and comparing them is a silent trap, not a clean error.
3 min read
The building blocks: date, time, datetime, timedelta
from datetime import date, time, datetime, timedelta
today = date.today() # 2026-08-31 — just a calendar date, no time
now = datetime.now() # 2026-08-31 14:32:07.123456 — date + time
later = now + timedelta(hours=2, minutes=30) # arithmetic works directly on datetimes
duration = later - now # timedelta(seconds=9000) — a difference IS a timedeltadate is a calendar day with no time component; datetime combines a date and a time of day. timedelta represents a duration, not a point in time — adding a timedelta to a datetime shifts it forward or backward, and subtracting two datetimes produces a timedelta. This arithmetic is one of datetime's genuine strengths over manually tracking timestamps as raw numbers: "3 days from now" is just datetime.now() + timedelta(days=3), with datetime correctly handling month lengths and leap years underneath.
Parsing and formatting: strptime and strftime
datetime.strptime("2026-08-31", "%Y-%m-%d") # parse: string -> datetime
datetime(2026, 8, 31).strftime("%B %d, %Y") # format: datetime -> "August 31, 2026"The mnemonic that actually sticks: strptime parses a string (p for parse — string to datetime), strftime formats one (f for format — datetime to string). Both use the same set of %-codes (%Y four-digit year, %m month, %d day, %H/%M/%S for time) — learning the codes once covers both directions.
The trap: naive vs. aware datetimes are NOT the same type of thing
naive = datetime(2026, 8, 31, 14, 0) # no timezone info attached at all
naive2 = datetime(2026, 8, 31, 20, 0)
naive2 - naive # timedelta(hours=6) — looks fine...
# ...but naive datetimes carry NO timezone. If naive came from a server in UTC
# and naive2 came from a server in UTC-5, that "6 hour difference" is actually
# meaningless — the subtraction ran anyway, silently, with no error at all.A naive datetime has no attached timezone — it's just a bag of numbers (year, month, day, hour...) with no notion of which timezone those numbers are in. Python happily subtracts and compares naive datetimes against each other with zero warning, even when they secretly represent different timezones — this is the single most common real-world datetime bug, and it fails silently rather than raising an error.
Mixing an aware and a naive datetime in a comparison actually does raise TypeError — Python refuses to guess. The dangerous case is two naive datetimes that quietly represent different timezones, since that comparison runs without complaint and produces a wrong-but-plausible-looking answer.
Making a datetime aware: timezone and zoneinfo
from datetime import timezone
from zoneinfo import ZoneInfo # standard library since Python 3.9
utc_now = datetime.now(timezone.utc) # aware, in UTC
cairo_now = datetime.now(ZoneInfo("Africa/Cairo")) # aware, in a named IANA timezone
utc_now.astimezone(ZoneInfo("Africa/Cairo")) # convert an aware datetime between zonesdatetime.now(timezone.utc) produces an aware datetime by explicitly attaching a tzinfo at creation time. zoneinfo.ZoneInfo (standard library, no install needed since 3.9) gives access to full IANA timezone database entries — including daylight saving rules — by name, which timezone.utc alone can't express since UTC has no daylight saving. The practical rule: store and compare in UTC, convert to a local zone only for display — this sidesteps the naive/aware trap entirely, since every value in the system is aware and in the same zone until the very last moment.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What is a 'naive' datetime?
2. What happens when you compare an aware datetime to a naive one?
3. Which function PARSES a string into a datetime object?
4. What does subtracting two datetime objects produce?