datetime Handling — Core Concepts
The Four Core Types
The datetime module provides four main types:
| Type | What It Represents | Example |
|---|---|---|
date | Calendar date (year, month, day) | 2026-03-27 |
time | Time of day (hour, minute, second, microsecond) | 14:30:00 |
datetime | Date + time combined | 2026-03-27 14:30:00 |
timedelta | A duration (difference between two datetimes) | 5 days, 3:00:00 |
from datetime import date, time, datetime, timedelta
today = date.today() # 2026-03-27
now = datetime.now() # 2026-03-27 14:30:45.123456
t = time(14, 30) # 14:30:00
delta = timedelta(days=7) # 7 days, 0:00:00
Date Arithmetic
This is where datetime shines. You can add, subtract, and compare dates naturally:
from datetime import datetime, timedelta
now = datetime(2026, 3, 27, 14, 0)
# Add time
next_week = now + timedelta(weeks=1) # 2026-04-03 14:00:00
in_3_hours = now + timedelta(hours=3) # 2026-03-27 17:00:00
# Subtract dates to get a duration
deadline = datetime(2026, 12, 31)
remaining = deadline - now # 279 days, 10:00:00
remaining.days # 279
# Compare dates
now < deadline # True
Formatting: datetime → String
Use strftime() to convert a datetime to a formatted string:
dt = datetime(2026, 3, 27, 14, 30)
dt.strftime("%Y-%m-%d") # '2026-03-27'
dt.strftime("%B %d, %Y") # 'March 27, 2026'
dt.strftime("%I:%M %p") # '02:30 PM'
dt.strftime("%A, %b %d") # 'Friday, Mar 27'
Common format codes:
| Code | Meaning | Example |
|---|---|---|
%Y | 4-digit year | 2026 |
%m | Month (zero-padded) | 03 |
%d | Day (zero-padded) | 27 |
%H | Hour (24-hour) | 14 |
%I | Hour (12-hour) | 02 |
%M | Minute | 30 |
%S | Second | 45 |
%p | AM/PM | PM |
%A | Full weekday name | Friday |
%B | Full month name | March |
Parsing: String → datetime
Use strptime() (string-parse-time) to convert strings to datetime objects:
dt = datetime.strptime("2026-03-27 14:30", "%Y-%m-%d %H:%M")
dt = datetime.strptime("March 27, 2026", "%B %d, %Y")
For ISO format strings, use the faster built-in method:
dt = datetime.fromisoformat("2026-03-27T14:30:00+02:00")
fromisoformat() was expanded in Python 3.11 to handle most ISO 8601 formats.
Time Zones: The Hard Part
A datetime without timezone info is called naive. One with timezone info is aware. Mixing them causes errors.
from datetime import datetime, timezone, timedelta
# Naive — no timezone
naive = datetime(2026, 3, 27, 14, 0)
# Aware — UTC
utc_now = datetime.now(timezone.utc)
# Aware — custom offset
est = timezone(timedelta(hours=-5))
eastern = datetime(2026, 3, 27, 14, 0, tzinfo=est)
Using zoneinfo (Python 3.9+)
For real-world timezones (which handle daylight saving time), use zoneinfo:
from zoneinfo import ZoneInfo
eastern = ZoneInfo("America/New_York")
london = ZoneInfo("Europe/London")
meeting = datetime(2026, 3, 27, 14, 0, tzinfo=eastern)
meeting_london = meeting.astimezone(london) # 18:00 or 19:00 depending on DST
The Golden Rule
Always store and compare datetimes in UTC. Convert to local time only for display.
# Store in UTC
stored = datetime.now(timezone.utc)
# Display in user's timezone
user_tz = ZoneInfo("Asia/Tokyo")
displayed = stored.astimezone(user_tz)
print(displayed.strftime("%Y-%m-%d %H:%M %Z")) # 2026-03-28 00:30 JST
Common Misconception
“datetime.now() gives you the current UTC time.” No — datetime.now() returns a naive datetime in your system’s local timezone. For UTC, you must explicitly pass timezone.utc: datetime.now(timezone.utc). This is the single most common datetime bug in Python.
Practical Patterns
Check if a date falls on a weekday
def is_weekday(dt):
return dt.weekday() < 5 # Monday=0, Sunday=6
Calculate business days
def add_business_days(start, days):
current = start
added = 0
while added < days:
current += timedelta(days=1)
if current.weekday() < 5:
added += 1
return current
Get start and end of a month
import calendar
def month_range(year, month):
first = date(year, month, 1)
last_day = calendar.monthrange(year, month)[1]
last = date(year, month, last_day)
return first, last
One thing to remember: Use datetime.now(timezone.utc) for current time, timedelta for arithmetic, strftime/strptime for formatting/parsing, and zoneinfo for real-world time zones. Store everything in UTC; convert to local only when displaying to humans.
See Also
- Python Atexit How Python's atexit module lets your program clean up after itself right before it shuts down.
- Python Bisect Sorted Lists How Python's bisect module finds things in sorted lists the way you'd find a word in a dictionary — by jumping to the middle.
- Python Contextlib How Python's contextlib module makes the 'with' statement work for anything, not just files.
- Python Copy Module Why copying data in Python isn't as simple as it sounds, and how the copy module prevents sneaky bugs.
- Python Dataclass Field Metadata How Python dataclass fields can carry hidden notes — like sticky notes on a filing cabinet that tools read automatically.