The Python datetime module provides classes for representing and manipulating dates and times — date, time, datetime, timedelta — with formatting (strftime), parsing (strptime), and timezone-aware arithmetic.
Working with dates and times
What you'll learn: The four datetime objects — date, time, datetime, timedelta — and how to format and parse dates from strings.
How to read this tab: Try datetime.datetime.now() in the REPL first. Then try strftime to format it. Then try timedelta to add days.
The datetime module provides objects for representing and manipulating dates, times, and time intervals — the tools for "what time is it now", "how many days until the deadline", and "format this timestamp for display".
Four objects to know: date (year, month, day only), time (hour, minute, second only), datetime (date + time combined — the one you use most), timedelta (a duration — 3 days, 2 hours, 30 minutes). Most of the time you want datetime, not date or time.
datetime objects
from datetime import datetime, date, time, timedelta
# date — year, month, day only
today = date.today()
print(today) # 2026-06-01
print(today.year) # 2026
print(today.month) # 6
print(today.weekday()) # 0=Mon, 6=Sun
# time — hours, minutes, seconds, microseconds
t = time(14, 30, 45)
print(t) # 14:30:45
# datetime — date + time combined
now = datetime.now() # local time
utc = datetime.utcnow() # UTC (naive — no timezone info)
print(now) # 2026-06-01 14:30:45.123456
# Create specific datetime
deadline = datetime(2026, 12, 31, 23, 59, 59)
# timedelta — represent a duration
one_week = timedelta(weeks=1)
ten_days = timedelta(days=10, hours=3)
print(today + ten_days) # date 10 days from now
# Arithmetic
diff = deadline - now
print(diff.days) # days until deadline
print(diff.total_seconds()) # total secondsstrftime formats, strptime parses. A memory trick: strftime has an "f" for "format" (datetime → string). strptime has a "p" for "parse" (string → datetime). The format codes are the same for both: %Y (4-digit year), %m (month), %d (day), %H (hour 24h), %M (minute), %S (second).
Formatting and parsing
from datetime import datetime
now = datetime.now()
# strftime — format datetime as string
print(now.strftime("%Y-%m-%d")) # 2026-06-01
print(now.strftime("%d/%m/%Y %H:%M")) # 01/06/2026 14:30
print(now.strftime("%A, %B %d, %Y")) # Monday, June 01, 2026
print(now.strftime("%I:%M %p")) # 02:30 PM
# strptime — parse string to datetime
date_str = "01/06/2026 14:30"
parsed = datetime.strptime(date_str, "%d/%m/%Y %H:%M")
print(parsed) # 2026-06-01 14:30:00
# ISO 8601 format
iso = now.isoformat() # "2026-06-01T14:30:45.123456"
back = datetime.fromisoformat(iso) # parse back (Python 3.7+)
# Common format codes
# %Y = 4-digit year %m = month (01-12) %d = day (01-31)
# %H = hour (00-23) %M = minute %S = second
# %A = weekday name %B = month name %p = AM/PMAlways use timezone-aware datetimes in production. Naive datetimes have no timezone information — they look like local time but Python doesn't know which timezone. This causes subtle bugs when code runs on servers in different timezones, or during daylight saving transitions. Use datetime.now(tz=timezone.utc) for the current UTC time, then convert to local time for display.
Timezones
from datetime import datetime, timezone, timedelta
# Timezone-aware datetime
utc_now = datetime.now(timezone.utc)
print(utc_now) # 2026-06-01 09:00:45.123456+00:00
# Fixed offset timezone
ist = timezone(timedelta(hours=5, minutes=30)) # India Standard Time
ist_now = datetime.now(ist)
print(ist_now) # 2026-06-01 14:30:45.123456+05:30
# Convert between timezones
utc_time = datetime(2026, 6, 1, 9, 0, tzinfo=timezone.utc)
ist_time = utc_time.astimezone(ist)
print(ist_time) # 2026-06-01 14:30:00+05:30
# For full timezone database (all cities, DST), use zoneinfo (3.9+)
from zoneinfo import ZoneInfo
mumbai = ZoneInfo("Asia/Kolkata")
mumbai_now = datetime.now(mumbai)✅ Beginner tab complete — check your understanding
- I can get the current date/time with datetime.datetime.now()
- I can format a datetime as a string with strftime()
- I can parse a date string with strptime()
- I can add days or hours to a date with timedelta
Timezones and timezone-aware datetimes
What you'll learn: The critical difference between naive datetimes (no timezone) and aware datetimes (with timezone). Using ZoneInfo (Python 3.9+) for proper timezone handling.
How to read this tab: Try creating datetime.datetime.now() (naive) vs datetime.datetime.now(tz=datetime.timezone.utc) (aware) and see the difference in output.
The one rule that prevents most datetime bugs: decide at the start of your project whether you'll store times as UTC (aware) or local time (naive). Mixing them produces TypeError when you try to compare them. The standard answer: store everything as UTC, convert to local time only for display.
Naive vs. aware datetimes
A naive datetime has no timezone information — Python doesn't know if it's UTC, IST, or PST. An aware datetime has a tzinfo object attached. You cannot compare or subtract naive and aware datetimes — Python raises TypeError. The rule for production code: always store and transmit timestamps in UTC (aware), convert to local time only for display. Never use datetime.now() without a timezone for anything that will be stored or compared across systems.
from datetime import datetime, timezone
from zoneinfo import ZoneInfo # Python 3.9+
# CORRECT: always use UTC for storage and computation
def record_event():
return datetime.now(timezone.utc) # aware UTC datetime
# CORRECT: convert to local only for display
def display_time(utc_dt, tz_name="Asia/Kolkata"):
local_tz = ZoneInfo(tz_name)
local = utc_dt.astimezone(local_tz)
return local.strftime("%d %B %Y, %I:%M %p %Z")
event_time = record_event()
print(display_time(event_time)) # 01 June 2026, 02:30 PM IST
# Compare datetimes
t1 = datetime(2026, 6, 1, 9, 0, tzinfo=timezone.utc)
t2 = datetime(2026, 6, 1, 14, 30, tzinfo=ZoneInfo("Asia/Kolkata"))
print(t1 == t2) # True — same moment in time
# Epoch timestamp (Unix time)
now = datetime.now(timezone.utc)
timestamp = now.timestamp() # float seconds since 1970-01-01 UTC
back = datetime.fromtimestamp(timestamp, tz=timezone.utc)datetime.utcnow() is deprecated (Python 3.12+) and returns a naive datetime. It looks like UTC but carries no timezone info — it can be mistakenly compared with local times. Use datetime.now(timezone.utc) instead, which returns a timezone-aware UTC datetime.datetime.now() (without timezone) returns the local time as a naive datetime. datetime.now(timezone.utc) returns UTC as an aware datetime. Mixing them causes bugs.timedelta does not handle months or years. Months vary in length (28–31 days); years vary (365 or 366). timedelta only works with days, seconds, and microseconds. For month/year arithmetic, use dateutil.relativedelta or calendar.✅ Intermediate tab complete — check your understanding
- I know the difference between a naive datetime (no timezone) and an aware datetime
- I can create a timezone-aware datetime using datetime.timezone.utc or ZoneInfo
- I know never to compare naive and aware datetimes (it raises TypeError)
Epoch time, TAI, and the complexity of time
What you'll learn: Unix epoch time and its limitations. The difference between UTC and TAI (International Atomic Time). Why time is genuinely hard in software.
How to read this tab: Read after you've used timezones in a real project and been bitten by daylight saving time.
Time is genuinely hard. Unix epoch counts seconds since 1970-01-01 00:00:00 UTC, but it doesn't account for leap seconds. TAI (International Atomic Time) does. This is why timekeeping in distributed systems is a specialised field. For most applications, datetime with proper UTC timezone handling is sufficient.
Epoch, TAI, and the complexity of time
The Unix epoch is 1970-01-01 00:00:00 UTC. datetime.timestamp() returns seconds since the epoch as a float (IEEE 754 double, accurate to microseconds until ~2255). UTC is not perfectly uniform — it is occasionally adjusted by leap seconds to stay within 0.9 seconds of UT1 (based on Earth's rotation). Python's datetime ignores leap seconds (follows POSIX time). The zoneinfo module (Python 3.9, PEP 615) uses the IANA time zone database — the authoritative source for all historical and current time zone rules including DST transitions.
✅ Expert tab complete
- I know what Unix epoch time is and its 2038 problem (for 32-bit systems)
- I understand why daylight saving time makes time arithmetic non-trivial