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 format

Common format codes:

CodeMeaningExample
%YYear (4 digit)2026
%mMonth (zero-padded)03
%dDay (zero-padded)21
%HHour (24h)14
%IHour (12h)02
%MMinute30
%SSecond00
%pAM/PMPM
%BMonth nameMarch
%AWeekday nameFriday

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)  # UTC

Don'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).days

dateutil for Parsing

For flexible parsing, use python-dateutil:

pip install python-dateutil
from 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 parsing

Relative 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 arrow
import 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 handling

My 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: