Date and time handling is tricky. Here's how to do it right in Python.

Basic Types

from datetime import date, time, datetime, timedelta
 
# Date only
d = date(2024, 3, 15)
print(d.year, d.month, d.day)  # 2024 3 15
 
# Time only
t = time(14, 30, 45)
print(t.hour, t.minute, t.second)  # 14 30 45
 
# Date and time
dt = datetime(2024, 3, 15, 14, 30, 45)
print(dt)  # 2024-03-15 14:30:45
 
# Current date/time
today = date.today()
now = datetime.now()

Timedelta (Durations)

from datetime import datetime, timedelta
 
now = datetime.now()
 
# Add/subtract time
tomorrow = now + timedelta(days=1)
last_week = now - timedelta(weeks=1)
in_two_hours = now + timedelta(hours=2)
 
# Complex duration
delta = timedelta(days=5, hours=3, minutes=30)
future = now + delta
 
# Difference between datetimes
start = datetime(2024, 1, 1)
end = datetime(2024, 3, 15)
diff = end - start
print(diff.days)  # 74
print(diff.total_seconds())  # 6393600.0

Parsing and Formatting

from datetime import datetime
 
# Parse string to datetime
dt = datetime.strptime("2024-03-15 14:30:00", "%Y-%m-%d %H:%M:%S")
dt = datetime.strptime("March 15, 2024", "%B %d, %Y")
 
# Format datetime to string
formatted = dt.strftime("%Y-%m-%d")  # 2024-03-15
formatted = dt.strftime("%B %d, %Y")  # March 15, 2024
formatted = dt.strftime("%I:%M %p")  # 02:30 PM
 
# ISO format
iso = dt.isoformat()  # 2024-03-15T14:30:00
dt = datetime.fromisoformat("2024-03-15T14:30:00")

Common Format Codes

CodeMeaningExample
%Y4-digit year2024
%mMonth (01-12)03
%dDay (01-31)15
%HHour 24h (00-23)14
%IHour 12h (01-12)02
%MMinute (00-59)30
%SSecond (00-59)45
%pAM/PMPM
%AWeekday nameFriday
%BMonth nameMarch
%zUTC offset+0000
%ZTimezone nameUTC

Timezones

from datetime import datetime, timezone, timedelta
 
# UTC
utc_now = datetime.now(timezone.utc)
print(utc_now)  # 2024-03-15 14:30:00+00:00
 
# Create timezone
est = timezone(timedelta(hours=-5))
est_time = datetime.now(est)
 
# Convert between timezones
utc_dt = datetime.now(timezone.utc)
est_dt = utc_dt.astimezone(est)
 
# Using zoneinfo (Python 3.9+)
from zoneinfo import ZoneInfo
 
eastern = ZoneInfo("America/New_York")
pacific = ZoneInfo("America/Los_Angeles")
 
now_eastern = datetime.now(eastern)
now_pacific = now_eastern.astimezone(pacific)

Naive vs Aware Datetimes

from datetime import datetime, timezone
 
# Naive (no timezone info)
naive = datetime.now()
print(naive.tzinfo)  # None
 
# Aware (has timezone info)
aware = datetime.now(timezone.utc)
print(aware.tzinfo)  # UTC
 
# Make naive datetime aware
from zoneinfo import ZoneInfo
tz = ZoneInfo("America/New_York")
aware = naive.replace(tzinfo=tz)
 
# WARNING: replace() doesn't convert, just labels
# Use this only when you know the naive datetime's timezone

Timestamps

from datetime import datetime, timezone
 
# Datetime to Unix timestamp
dt = datetime.now(timezone.utc)
timestamp = dt.timestamp()
print(timestamp)  # 1710513600.0
 
# Unix timestamp to datetime
dt = datetime.fromtimestamp(timestamp, tz=timezone.utc)
 
# Current timestamp
import time
timestamp = time.time()

Date Arithmetic

from datetime import date, timedelta
import calendar
 
today = date.today()
 
# First/last day of month
first_day = today.replace(day=1)
_, last_day_num = calendar.monthrange(today.year, today.month)
last_day = today.replace(day=last_day_num)
 
# Next Monday
days_ahead = 0 - today.weekday()  # 0 = Monday
if days_ahead <= 0:
    days_ahead += 7
next_monday = today + timedelta(days=days_ahead)
 
# Start of week
start_of_week = today - timedelta(days=today.weekday())

Common Patterns

from datetime import datetime, date, timedelta, timezone
 
def start_of_day(dt: datetime) -> datetime:
    """Get midnight of the given datetime."""
    return dt.replace(hour=0, minute=0, second=0, microsecond=0)
 
def end_of_day(dt: datetime) -> datetime:
    """Get last moment of the given datetime."""
    return dt.replace(hour=23, minute=59, second=59, microsecond=999999)
 
def is_business_day(d: date) -> bool:
    """Check if date is a weekday."""
    return d.weekday() < 5
 
def add_business_days(d: date, days: int) -> date:
    """Add business days to a date."""
    while days > 0:
        d += timedelta(days=1)
        if is_business_day(d):
            days -= 1
    return d
 
def age_in_years(birthdate: date) -> int:
    """Calculate age in complete years."""
    today = date.today()
    age = today.year - birthdate.year
    if (today.month, today.day) < (birthdate.month, birthdate.day):
        age -= 1
    return age
 
def format_relative(dt: datetime) -> str:
    """Format as relative time (e.g., '2 hours ago')."""
    now = datetime.now(dt.tzinfo)
    diff = now - dt
    
    if diff.days > 365:
        return f"{diff.days // 365} years ago"
    if diff.days > 30:
        return f"{diff.days // 30} months ago"
    if diff.days > 0:
        return f"{diff.days} days ago"
    if diff.seconds > 3600:
        return f"{diff.seconds // 3600} hours ago"
    if diff.seconds > 60:
        return f"{diff.seconds // 60} minutes ago"
    return "just now"

ISO 8601

from datetime import datetime, timezone
 
# Parse ISO format
dt = datetime.fromisoformat("2024-03-15T14:30:00+00:00")
dt = datetime.fromisoformat("2024-03-15T14:30:00Z".replace("Z", "+00:00"))
 
# Output ISO format
iso = dt.isoformat()  # 2024-03-15T14:30:00+00:00
 
# Date only
d = date.fromisoformat("2024-03-15")

Calendar Module

import calendar
 
# Month calendar
cal = calendar.month(2024, 3)
print(cal)
 
# Check leap year
is_leap = calendar.isleap(2024)  # True
 
# Days in month
days = calendar.monthrange(2024, 2)[1]  # 29
 
# Weekday of date
weekday = calendar.weekday(2024, 3, 15)  # 4 (Friday)

Best Practices

from datetime import datetime, timezone
from zoneinfo import ZoneInfo
 
# Always use timezone-aware datetimes
now = datetime.now(timezone.utc)
 
# Store times in UTC
stored = datetime.now(timezone.utc)
 
# Convert to local only for display
local_tz = ZoneInfo("America/New_York")
displayed = stored.astimezone(local_tz)
 
# Use ISO format for storage/APIs
iso_string = dt.isoformat()
 
# Compare only aware or only naive datetimes
# Never mix them
 
# Use timedelta for durations
duration = timedelta(hours=2, minutes=30)

Gotchas

from datetime import datetime, timezone
 
# datetime.now() is naive (no timezone)
naive = datetime.now()  # No timezone info!
 
# Use this instead
aware = datetime.now(timezone.utc)
 
# strptime() returns naive datetime
parsed = datetime.strptime("2024-03-15", "%Y-%m-%d")
# Add timezone manually if needed
 
# Daylight saving time gaps
# Some times don't exist or exist twice!
# Use zoneinfo to handle correctly

Dates and times are complex. Always use timezone-aware datetimes, store in UTC, and convert to local only for display.

React to this post: