datetime Handling — Core Concepts

The Four Core Types

The datetime module provides four main types:

TypeWhat It RepresentsExample
dateCalendar date (year, month, day)2026-03-27
timeTime of day (hour, minute, second, microsecond)14:30:00
datetimeDate + time combined2026-03-27 14:30:00
timedeltaA 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:

CodeMeaningExample
%Y4-digit year2026
%mMonth (zero-padded)03
%dDay (zero-padded)27
%HHour (24-hour)14
%IHour (12-hour)02
%MMinute30
%SSecond45
%pAM/PMPM
%AFull weekday nameFriday
%BFull month nameMarch

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.

pythonstandard-librarydates

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.