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.0Parsing 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
| Code | Meaning | Example |
|---|---|---|
%Y | 4-digit year | 2024 |
%m | Month (01-12) | 03 |
%d | Day (01-31) | 15 |
%H | Hour 24h (00-23) | 14 |
%I | Hour 12h (01-12) | 02 |
%M | Minute (00-59) | 30 |
%S | Second (00-59) | 45 |
%p | AM/PM | PM |
%A | Weekday name | Friday |
%B | Month name | March |
%z | UTC offset | +0000 |
%Z | Timezone name | UTC |
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 timezoneTimestamps
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 correctlyDates and times are complex. Always use timezone-aware datetimes, store in UTC, and convert to local only for display.
React to this post: