Dates and times appear in nearly every application: reports, reminders, subscriptions, logs, appointments, file names, and API payloads. They seem simple until time zones, daylight-saving changes, formatting, and calendar arithmetic enter the picture. Python’s standard datetime and zoneinfo modules provide the core tools needed to handle them clearly.
This Python datetime guide covers date, time, datetime, timedelta, parsing, formatting, time zones, comparisons, and practical patterns. It focuses on reliable application code rather than clever shortcuts.
The main datetime types
The module defines several related classes. date stores a calendar date, time stores a time of day, datetime combines both, and timedelta represents a duration. The official datetime documentation also distinguishes naive objects from timezone-aware objects.
from datetime import date, datetime, time, timedelta
today = date.today()
meeting_time = time(14, 30)
meeting = datetime(2026, 7, 10, 14, 30)
duration = timedelta(hours=1, minutes=15)
print(today)
print(meeting_time)
print(meeting + duration)Get the current date and time
Use date.today() for a local calendar date. For an instant that may be exchanged between systems, prefer a timezone-aware datetime.
from datetime import datetime, timezone
now_utc = datetime.now(timezone.utc)
print(now_utc)A naive datetime.now() has no timezone information. It may be acceptable for a local-only script, but it is risky for distributed systems because the value does not identify an unambiguous instant.
Create dates and datetimes
from datetime import date, datetime
release_date = date(2026, 9, 15)
deadline = datetime(2026, 9, 15, 17, 0)
print(release_date.year)
print(deadline.hour)Constructors validate calendar rules. An impossible date, such as February 30, raises ValueError. Validate user input and provide a friendly message rather than storing malformed strings.
Format dates for people
Use strftime() to turn a date or datetime into a formatted string. Format codes represent components such as year, month, day, hour, and minute.
from datetime import datetime
moment = datetime(2026, 7, 10, 16, 45)
print(moment.strftime("%Y-%m-%d"))
print(moment.strftime("%B %d, %Y at %H:%M"))Formatting is presentation. Keep dates as date or datetime objects during calculations and convert to text only at system boundaries or display time. The f-string formatting guide covers general output formatting.
Parse strings into datetime objects
Use strptime() when the input follows a known custom format. The format string must match the input exactly.
from datetime import datetime
text = "10/07/2026 16:45"
moment = datetime.strptime(text, "%d/%m/%Y %H:%M")
print(moment)For ISO 8601-compatible strings, fromisoformat() is often clearer and faster to read.
from datetime import datetime
moment = datetime.fromisoformat("2026-07-10T16:45:00+00:00")
print(moment)When parsing CSV or JSON, document the accepted format. The guides to CSV files and JSON in Python show where these conversions fit in a data pipeline.
Date arithmetic with timedelta
Add or subtract a timedelta for durations measured in days, seconds, microseconds, milliseconds, minutes, hours, or weeks.
from datetime import date, timedelta
today = date.today()
next_week = today + timedelta(weeks=1)
yesterday = today - timedelta(days=1)
print(next_week)
print(yesterday)Subtracting two date or datetime objects returns a timedelta.
from datetime import date
start = date(2026, 7, 10)
end = date(2026, 8, 1)
difference = end - start
print(difference.days)A timedelta is a fixed duration. “One month later” is not fixed because months have different lengths. Define the business rule explicitly or use a specialized calendar library when month arithmetic is required.
Compare dates and sort events
from datetime import datetime
events = [
datetime(2026, 8, 1, 9, 0),
datetime(2026, 7, 15, 14, 0),
datetime(2026, 7, 20, 10, 30),
]
for event in sorted(events):
print(event.isoformat())Comparisons work naturally when objects are compatible. Do not compare a naive datetime with an aware datetime. Normalize values first. For custom record ordering, see Python sort versus sorted.
Work with time zones using zoneinfo
The standard zoneinfo module provides IANA time zone data. A named zone knows historical and daylight-saving transitions, unlike a permanently fixed numeric offset.
from datetime import datetime
from zoneinfo import ZoneInfo
sao_paulo = ZoneInfo("America/Sao_Paulo")
new_york = ZoneInfo("America/New_York")
local_meeting = datetime(2026, 7, 10, 10, 0, tzinfo=sao_paulo)
new_york_time = local_meeting.astimezone(new_york)
print(local_meeting)
print(new_york_time)A common strategy is to store instants in UTC and convert to a user’s zone for display. Also preserve the intended named zone for recurring local events, because a fixed UTC time may not match the same wall-clock time after daylight-saving transitions.
Naive versus aware datetimes
A naive datetime has tzinfo=None. It can represent a local wall time, but the object alone does not say which location or offset applies. An aware datetime includes timezone information and represents an identifiable point on the global timeline.
from datetime import datetime, timezone
naive = datetime(2026, 7, 10, 12, 0)
aware = datetime(2026, 7, 10, 12, 0, tzinfo=timezone.utc)
print(naive.tzinfo)
print(aware.tzinfo)Do not attach a timezone to an arbitrary naive value unless you know what zone the original wall time meant. Converting an existing instant and labeling an unzoned wall time are different operations.
Build a deadline helper
from datetime import datetime, timezone
def seconds_until(deadline: datetime) -> float:
if deadline.tzinfo is None:
raise ValueError("deadline must be timezone-aware")
now = datetime.now(timezone.utc)
normalized = deadline.astimezone(timezone.utc)
return max(0.0, (normalized - now).total_seconds())The function rejects ambiguous input, normalizes both values to UTC, and never returns a negative wait. Type hints make the contract visible; see Python type hints for more examples.
Use dates in file names and logs
from datetime import datetime, timezone
from pathlib import Path
timestamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
path = Path("exports") / f"report-{timestamp}.txt"
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("Report created.", encoding="utf-8")
print(path)A sortable year-month-day order keeps generated files chronological. For application events, the logging module provides configurable timestamps and structured output.
Common mistakes
- Storing dates as inconsistent human-formatted strings.
- Using naive datetimes for events exchanged between systems.
- Assuming every day has exactly 24 local hours across daylight-saving transitions.
- Treating a month as a fixed number of days.
- Parsing user input without handling
ValueError. - Comparing naive and timezone-aware values.
- Using local server time as a universal reference.
Frequently asked questions
Should I store UTC in a database?
UTC is a strong default for instants such as creation times and completed transactions. For future appointments and recurring events, also store the relevant named time zone and business rules so the intended local time can be reconstructed.
What is the best date string format?
ISO 8601 is readable, sortable in many common forms, and widely supported. Include an offset or Z for instants. A bare date such as 2026-07-10 is appropriate when no time of day exists.
How do I calculate age?
Age is calendar-based, not simply elapsed days divided by 365. Compare month and day relative to the current year and define rules for leap-day birthdays.
Final thoughts
Reliable datetime code begins by deciding what a value represents: a calendar date, local wall time, duration, or global instant. Keep structured objects during calculations, use aware datetimes for exchanged instants, convert to a named zone for display, and treat calendar arithmetic as a business rule rather than a fixed-duration shortcut.






