Dates are tricky. Here's how to handle them without losing your mind.
datetime Basics
from datetime import datetime, date, time, timedelta
# Current date and time
now = datetime.now()
today = date.today()
# Create specific dates
d = date(2026, 3, 21)
dt = datetime(2026, 3, 21, 14, 30, 0)
t = time(14, 30, 0)
# Access components
print(now.year, now.month, now.day)
print(now.hour, now.minute, now.second)timedelta for Arithmetic
from datetime import timedelta
now = datetime.now()
# Add/subtract time
tomorrow = now + timedelta(days=1)
last_week = now - timedelta(weeks=1)
two_hours_ago = now - timedelta(hours=2)
# Difference between dates
delta = datetime(2026, 12, 31) - now
print(f"{delta.days} days until end of year")Formatting Dates
now = datetime.now()
# strftime - datetime to string
now.strftime("%Y-%m-%d") # 2026-03-21
now.strftime("%B %d, %Y") # March 21, 2026
now.strftime("%I:%M %p") # 02:30 PM
now.strftime("%Y-%m-%dT%H:%M:%S") # ISO formatCommon format codes:
| Code | Meaning | Example |
|---|---|---|
| %Y | Year (4 digit) | 2026 |
| %m | Month (zero-padded) | 03 |
| %d | Day (zero-padded) | 21 |
| %H | Hour (24h) | 14 |
| %I | Hour (12h) | 02 |
| %M | Minute | 30 |
| %S | Second | 00 |
| %p | AM/PM | PM |
| %B | Month name | March |
| %A | Weekday name | Friday |
Parsing Dates
# strptime - string to datetime
dt = datetime.strptime("2026-03-21", "%Y-%m-%d")
dt = datetime.strptime("March 21, 2026", "%B %d, %Y")
dt = datetime.strptime("21/03/2026 14:30", "%d/%m/%Y %H:%M")ISO Format
now = datetime.now()
# To ISO string
iso_str = now.isoformat() # 2026-03-21T14:30:00.123456
# From ISO string
dt = datetime.fromisoformat("2026-03-21T14:30:00")Timezones
Always use timezone-aware datetimes in production.
from datetime import timezone
from zoneinfo import ZoneInfo # Python 3.9+
# UTC
utc_now = datetime.now(timezone.utc)
# Specific timezone
eastern = ZoneInfo("America/New_York")
local_time = datetime.now(eastern)
# Convert between timezones
utc_time = datetime.now(timezone.utc)
eastern_time = utc_time.astimezone(eastern)Naive vs Aware Datetimes
# Naive - no timezone info (avoid in production)
naive = datetime.now()
print(naive.tzinfo) # None
# Aware - has timezone
aware = datetime.now(timezone.utc)
print(aware.tzinfo) # UTCDon't compare naive and aware datetimes:
naive = datetime.now()
aware = datetime.now(timezone.utc)
naive < aware # TypeError!Common Operations
from datetime import datetime, timedelta
now = datetime.now()
# Start of day
start_of_day = now.replace(hour=0, minute=0, second=0, microsecond=0)
# End of day
end_of_day = now.replace(hour=23, minute=59, second=59)
# Start of month
start_of_month = now.replace(day=1)
# Check if weekend
is_weekend = now.weekday() >= 5 # 5=Sat, 6=Sun
# Days until date
days_until = (datetime(2026, 12, 31) - now).daysdateutil for Parsing
For flexible parsing, use python-dateutil:
pip install python-dateutilfrom dateutil import parser
# Parses many formats automatically
parser.parse("March 21, 2026")
parser.parse("21/03/2026")
parser.parse("2026-03-21T14:30:00Z")
parser.parse("next friday") # Relative parsingRelative Deltas
from dateutil.relativedelta import relativedelta
now = datetime.now()
# Add months (timedelta can't do this)
next_month = now + relativedelta(months=1)
next_year = now + relativedelta(years=1)
# Last day of month
end_of_month = now + relativedelta(day=31)
# Complex offsets
result = now + relativedelta(months=2, days=15, hours=3)Arrow Library
Cleaner API:
pip install arrowimport arrow
now = arrow.now()
utc = arrow.utcnow()
# Formatting
now.format("YYYY-MM-DD HH:mm")
# Parsing
arrow.get("2026-03-21", "YYYY-MM-DD")
# Human-friendly
now.humanize() # "just now"
now.shift(days=-1).humanize() # "a day ago"
# Timezone handling
now.to("US/Pacific")Storing Dates
In databases, store as UTC:
# Save
utc_time = datetime.now(timezone.utc)
db.save(utc_time)
# Load and convert to local
stored_time = db.load()
local_time = stored_time.astimezone(ZoneInfo("America/New_York"))In JSON:
import json
# Serialize
json.dumps({"created": now.isoformat()})
# Deserialize
data = json.loads(json_str)
dt = datetime.fromisoformat(data["created"])Common Pitfalls
Comparing dates across timezones
# Wrong - ignores timezone
if user_time < deadline: # Both should be aware
# Right
if user_time.astimezone(timezone.utc) < deadline.astimezone(timezone.utc):Assuming local timezone
# Wrong
now = datetime.now() # Local, but which timezone?
# Right
now = datetime.now(timezone.utc)Daylight saving time
# Some days have 23 or 25 hours!
# Use zoneinfo for proper DST handlingMy Patterns
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
def utc_now() -> datetime:
return datetime.now(timezone.utc)
def to_local(dt: datetime, tz: str = "America/New_York") -> datetime:
return dt.astimezone(ZoneInfo(tz))
def format_date(dt: datetime) -> str:
return dt.strftime("%Y-%m-%d %H:%M:%S %Z")Always store UTC. Convert to local only for display.
React to this post: