date — calendar date with no time. time — clock time with no date. datetime — both. timedelta — a duration (positive or negative). Most of your code uses datetime and timedelta; the others come up less often.
Naive vs aware — the timezone distinction
A "naive" datetime has no timezone info. An "aware" datetime has a tzinfo attached. Mixing them in arithmetic raises. The 2020s answer: always use aware datetimes. Construct them with datetime.now(timezone.utc) or datetime.fromisoformat with a timezone in the string. The zoneinfo module (3.9+) gives you ZoneInfo("Asia/Seoul") for IANA-named zones.
The ISO 8601 lifeline
datetime.fromisoformat("2026-05-02T15:30:00+09:00") parses, dt.isoformat() emits. Round-trippable, sortable as strings, unambiguous. Modern code defaults to ISO 8601 for serialization. strftime/strptime handle other formats; only reach for them when you must.
timedelta — durations and arithmetic
timedelta(days=7, hours=3) is a duration. Datetimes minus datetimes give a timedelta. Datetimes plus timedelta gives a datetime. Comparisons work. Common idiom: now + timedelta(days=30) for "30 days from now."
Principle: Store and transmit datetimes as ISO 8601 strings with a UTC offset. Convert to local time only at display. Mixing naive and aware datetimes is the most common datetime bug; using aware everywhere makes it impossible.
“Twenty-four hours later” and “the same local time tomorrow” are different operations across a DST boundary. Store and compare aware UTC values; convert to a named local zone for display and calendar intent. Keeping those jobs separate removes an entire class of timestamp bugs.
Code
Construction and formatting·python
from datetime import datetime, date, time, timedelta, timezone
from zoneinfo import ZoneInfo
# Today, now
print(date.today()) # 2026-05-02
print(datetime.now()) # naive — bad! avoid in modern code
print(datetime.now(timezone.utc)) # aware — good
print(datetime.now(ZoneInfo("Asia/Seoul"))) # aware in Seoul time
# Construction
dt = datetime(2026, 5, 2, 15, 30, tzinfo=ZoneInfo("Asia/Seoul"))
print(dt) # 2026-05-02 15:30:00+09:00
# ISO format — the modern standard
print(dt.isoformat()) # 2026-05-02T15:30:00+09:00
print(datetime.fromisoformat("2026-05-02T15:30:00+09:00")) # round-trip
Naive vs aware — and why the distinction matters·python
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
naive = datetime(2026, 5, 2, 15, 30)
aware = datetime(2026, 5, 2, 15, 30, tzinfo=timezone.utc)
# Mixing them in arithmetic raises
try:
aware - naive
except TypeError as e:
print("failed:", e)
# Convert naive to aware (assume it's UTC)
aware_from_naive = naive.replace(tzinfo=timezone.utc)
print(aware - aware_from_naive) # 0 — same instant
# Convert aware to a different zone
seoul = aware.astimezone(ZoneInfo("Asia/Seoul"))
print(seoul) # 2026-05-03 00:30:00+09:00
timedelta — arithmetic on dates·python
from datetime import datetime, timedelta, timezone
now = datetime.now(timezone.utc)
in_a_week = now + timedelta(days=7)
yesterday = now - timedelta(days=1)
print(in_a_week - now) # 7 days, 0:00:00
print(now - yesterday) # 1 day, 0:00:00
# Total seconds
delta = in_a_week - now
print(delta.total_seconds()) # 604800
# Components
print(delta.days) # 7
print(timedelta(hours=25).days) # 1 — hours overflow into days
strftime / strptime — non-ISO formats·python
from datetime import datetime
dt = datetime(2026, 5, 2, 15, 30)
# strftime — datetime to string
print(dt.strftime("%Y-%m-%d")) # 2026-05-02
print(dt.strftime("%B %d, %Y")) # May 02, 2026
print(dt.strftime("%I:%M %p")) # 03:30 PM
# strptime — string to datetime
parsed = datetime.strptime("05/02/2026 15:30", "%m/%d/%Y %H:%M")
print(parsed)
# When you can, prefer fromisoformat. Only use strptime
# for non-ISO formats you can't change.
Common operations — diff in days, day name, weekday·python
from datetime import date, timedelta
birthday = date(2025, 6, 15)
today = date.today()
diff = (today - birthday).days
print(f"{diff} days since birthday")
# Day of week — 0=Monday, 6=Sunday
print(today.weekday()) # int
print(["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"][today.weekday()])
# Day name via strftime
print(today.strftime("%A")) # 'Saturday' (or whatever)
# Find next Monday
days_until_monday = (7 - today.weekday()) % 7 or 7
print(today + timedelta(days=days_until_monday))
(a) Get the current time in both UTC and Asia/Seoul, and print the difference (should be 9 hours). Use zoneinfo.ZoneInfo. (b) Compute the date that is exactly 100 days from today. Print it as ISO format. (c) Parse the string "2026-12-25T10:00:00+09:00" with fromisoformat and print how many days from now until that datetime.
Progress
Progress is local-only — sign in to sync across devices.