Python datetime Module — Complete Guide
A complete, professional guide to Python's datetime module — date, time, datetime, and timedelta classes, strftime/strptime formatting, timezone-aware vs naive datetimes, and real date-arithmetic patterns.
Last Updated
March 2026
Read Time
28 min
Level
Intermediate
What is the datetime Module in Python?
The datetime module is Python's built-in standard library toolkit for working with dates, times, and the intervals between them. It provides a small set of specialized classes — date, time, datetime, timedelta, and tzinfo — that together cover nearly everything a program needs to represent, compare, format, parse, and calculate with calendar dates and clock times.
Working with dates correctly is deceptively hard: months have different numbers of days, years are sometimes leap years, time zones shift by non-whole-hour amounts in some regions, daylight saving time creates hours that repeat or skip entirely, and string date formats vary wildly by country and convention. The datetime module exists precisely to handle this complexity correctly and consistently, so application code doesn't have to reinvent calendar arithmetic — and getting even a small operation wrong (like naively adding 86400 seconds to represent "one day later") can silently produce incorrect results across a daylight-saving boundary.
Every value produced by the datetime module is immutable — once a date or datetime object is created, it cannot be changed in place. Operations that appear to "modify" a date, like adding a number of days, always return a brand-new object rather than altering the original, which makes datetime objects safe to pass around and reuse without fear of unexpected side effects.
The Five Core Classes of the datetime Module
The datetime module is built around five distinct classes, each representing a different kind of temporal value. Understanding which one to reach for is the first step to using the module correctly.
from datetime import date, time, datetime, timedelta, timezone
d = date(2026, 3, 16)
t = time(14, 30, 0)
dt = datetime(2026, 3, 16, 14, 30, 0)
delta = timedelta(days=7, hours=3)
print(d) # 2026-03-16
print(t) # 14:30:00
print(dt) # 2026-03-16 14:30:00
print(delta) # 7 days, 3:00:00
# datetime combines date and time — you can extract either half:
print(dt.date()) # 2026-03-16
print(dt.time()) # 14:30:00Creating Date and Time Objects
There are several ways to construct date and datetime objects, depending on whether you want the current moment, a specific known date, or a date derived from a Unix timestamp.
Getting the Current Date and Time
from datetime import date, datetime
today = date.today()
print(today) # e.g. 2026-07-24
now = datetime.now()
print(now) # e.g. 2026-07-24 15:42:10.123456 (local time, naive)
utc_now = datetime.now(datetime_timezone_utc := __import__('datetime').timezone.utc)
print(utc_now) # e.g. 2026-07-24 10:12:10.123456+00:00 (timezone-aware)Constructing a Specific Date
from datetime import date, datetime
independence_day = date(1947, 8, 15)
print(independence_day) # 1947-08-15
print(independence_day.year) # 1947
print(independence_day.month) # 8
print(independence_day.day) # 15
print(independence_day.weekday()) # 4 (Monday=0 ... Sunday=6)
print(independence_day.strftime('%A')) # FridayCreating a Date from a Unix Timestamp
from datetime import datetime
unix_timestamp = 1750000000
dt = datetime.fromtimestamp(unix_timestamp)
print(dt) # local time equivalent of that Unix timestamp
# Converting a datetime back to a Unix timestamp:
print(dt.timestamp()) # 1750000000.0Formatting and Parsing Dates — strftime and strptime
Converting between datetime objects and human-readable strings is one of the most common tasks in real applications. Python uses two complementary methods for this: strftime() ("string format time") converts a datetime object into a formatted string, and strptime() ("string parse time") converts a string into a datetime object, given a matching format pattern.
from datetime import datetime
dt = datetime(2026, 3, 16, 14, 30, 0)
# Formatting: datetime object -> string
print(dt.strftime('%Y-%m-%d')) # 2026-03-16
print(dt.strftime('%d/%m/%Y')) # 16/03/2026
print(dt.strftime('%A, %B %d, %Y')) # Monday, March 16, 2026
print(dt.strftime('%I:%M %p')) # 02:30 PM
# Parsing: string -> datetime object
parsed = datetime.strptime('16-03-2026', '%d-%m-%Y')
print(parsed) # 2026-03-16 00:00:00
print(type(parsed)) # <class 'datetime.datetime'>The single most important rule when using strptime(): the format string passed as the second argument must exactly match the structure of the input string, character for character, including all separators (dashes, slashes, spaces). A mismatch — even something as small as expecting a dash where the string uses a slash — raises a ValueError with the message "time data ... does not match format".
Naive vs Timezone-Aware Datetime — Decision Flowchart
One of the most important — and most commonly mishandled — decisions when working with datetimes is whether to use a naive datetime (no time zone information at all) or a timezone-aware one (carries explicit UTC offset information). This flowchart shows how to decide, and what happens when the two are mixed.
Code Execution Flow — from source to output
The general industry guideline: store and compute in UTC using timezone-aware datetimes everywhere internally (databases, APIs, logs), and only convert to a specific local time zone at the very last moment — when actually displaying a value to a human user.
Date Arithmetic with timedelta
The timedelta class represents a duration — the span between two points in time — and is the correct tool for adding or subtracting time from a date or datetime, rather than manually calculating seconds or days yourself.
from datetime import date, datetime, timedelta
today = date.today()
one_week_later = today + timedelta(weeks=1)
print(one_week_later)
deadline = datetime(2026, 12, 31, 23, 59, 59)
reminder = deadline - timedelta(days=3)
print(reminder) # 2026-12-28 23:59:59
# Subtracting two datetimes gives you a timedelta:
start = datetime(2026, 1, 1)
end = datetime(2026, 3, 16)
difference = end - start
print(difference) # 74 days, 0:00:00
print(difference.days) # 74
print(difference.total_seconds()) # 6393600.0- ▶
timedelta(days=..., seconds=..., microseconds=..., weeks=...)— Accepts multiple unit keywords at once; internally, everything is normalized down to days, seconds, and microseconds. - ▶
date/datetime + timedelta— Returns a new date/datetime shifted forward by that duration. - ▶
date/datetime - timedelta— Returns a new date/datetime shifted backward by that duration. - ▶
datetime - datetime— Returns a timedelta representing the exact difference between the two points in time. - ▶
timedelta.days,timedelta.seconds,timedelta.total_seconds()— Ways to extract the duration's magnitude in different units for further calculation.
Notably, timedelta has no concept of months or years, because those units don't have a fixed, consistent length (a month can be 28-31 days, a year can be 365 or 366). Adding "one month" correctly (e.g. handling February and month-end edge cases) requires either manual calendar-aware logic or a third-party library like python-dateutil's relativedelta.
Comparing and Sorting Dates
date and datetime objects support all standard comparison operators directly — <, <=, >, >=, ==, and != — making it straightforward to check ordering, sort collections of dates, or filter for a specific range.
from datetime import date
start = date(2026, 1, 1)
end = date(2026, 12, 31)
checkpoint = date(2026, 6, 15)
print(start < checkpoint < end) # True — chained comparisons work too
events = [date(2026, 5, 1), date(2026, 1, 15), date(2026, 9, 20)]
events.sort()
print(events) # sorted chronologically: [2026-01-15, 2026-05-01, 2026-09-20]
print(max(events)) # 2026-09-20 — the latest date
print(min(events)) # 2026-01-15 — the earliest dateA critical caveat: Python cannot compare a naive datetime with a timezone-aware one — attempting to do so raises TypeError: can't compare offset-naive and offset-aware datetimes. Both datetimes being compared must be consistently either naive or aware; mixing the two is one of the most common real-world datetime bugs.
Timezone Handling with zoneinfo
Since Python 3.9, the standard library includes the zoneinfo module, providing access to the IANA time zone database (the same authoritative source used across most modern operating systems) without requiring any third-party package. This replaced the earlier common practice of relying on the external pytz library for anything beyond simple UTC handling.
from datetime import datetime
from zoneinfo import ZoneInfo
# Create a timezone-aware datetime directly in a specific zone:
ny_time = datetime(2026, 3, 16, 9, 0, tzinfo=ZoneInfo('America/New_York'))
print(ny_time) # 2026-03-16 09:00:00-04:00
# Convert that same moment into a different time zone:
kolkata_time = ny_time.astimezone(ZoneInfo('Asia/Kolkata'))
print(kolkata_time) # 2026-03-16 18:30:00+05:30
# Both represent the exact SAME instant in time, just displayed differently:
print(ny_time == kolkata_time) # TrueFor any new project targeting Python 3.9 or later, zoneinfo is the recommended approach for full timezone-aware handling — it's built into the standard library, correctly accounts for daylight saving transitions using the same authoritative data your operating system uses, and avoids the historically confusing, easy-to-misuse localize()/normalize() API that pytz required.
now() vs utcnow() vs today() — Choosing the Right Function
The datetime module offers several similar-looking functions for getting the current moment, each with an important distinction worth understanding clearly.
As of Python 3.12, datetime.utcnow() is officially deprecated precisely because of this naive-vs-aware ambiguity — it returns a naive datetime whose values happen to represent UTC, but with nothing in the object itself indicating that; that missing context has historically caused numerous production bugs when such a value was later compared or combined with a different timezone-aware or local-time value. The recommended modern replacement is always datetime.now(timezone.utc), which returns the equivalent value but explicitly, correctly marked as timezone-aware UTC.
How the datetime Module's Pieces Fit Together
The datetime module's classes build on each other in layers — from raw calendar/clock values, through formatting and arithmetic, up to full timezone awareness.
Every layer builds on the one beneath it: you cannot format or parse a value that doesn't exist yet, you cannot meaningfully compute a duration without two raw values, and a value only becomes fully timezone-aware once a tzinfo subclass (like timezone or ZoneInfo) is explicitly attached to it.
Practice: A Countdown and Age Calculator
This example combines date arithmetic, formatting, and comparisons into two small, realistic utility functions.
from datetime import date
def calculate_age(birth_date):
today = date.today()
age = today.year - birth_date.year
had_birthday_this_year = (today.month, today.day) >= (birth_date.month, birth_date.day)
if not had_birthday_this_year:
age -= 1
return age
def days_until(target_date):
today = date.today()
if target_date < today:
return f"That date was {(today - target_date).days} days ago."
remaining = (target_date - today).days
return f"{remaining} days remaining until {target_date.strftime('%B %d, %Y')}."
print(calculate_age(date(2000, 6, 15)))
print(days_until(date(2026, 12, 25)))Output
26 154 days remaining until December 25, 2026.Practice This Code — Live Editor
datetime Module Best Practices
These practices help avoid the most common, and most subtle, bugs when working with dates and times in production code.
- ▶
Store and compute in UTC, display in local time — Use timezone-aware UTC datetimes internally (databases, APIs, logs), converting to a specific local zone only at the final point of display to a user.
- ▶
Prefer datetime.now(timezone.utc) over the deprecated utcnow() — utcnow() returns a naive datetime with no timezone marker at all, a common source of comparison bugs; the aware version is unambiguous.
- ▶
Use zoneinfo instead of pytz for new Python 3.9+ projects — zoneinfo is built into the standard library, uses the same IANA database, and has a simpler, less error-prone API than pytz's localize()/normalize() pattern.
- ▶
Never mix naive and timezone-aware datetimes — Comparing or subtracting one of each raises a TypeError; keep all datetimes in a given code path consistently one or the other.
- ▶
Use timedelta for day/week/hour arithmetic, not manual second math — Manually computing seconds (like adding 86400 for 'one day') is error-prone around DST transitions; timedelta handles this correctly.
- ▶
Remember timedelta has no month or year units — For calendar-aware month/year arithmetic (handling variable month lengths correctly), use the third-party dateutil.relativedelta instead.
- ▶
Match strptime() format strings exactly to the input — Every separator character and field order must match precisely, or Python raises a ValueError describing the mismatch.
- ▶
Use isoformat()/fromisoformat() for machine-to-machine date exchange — ISO 8601 is the unambiguous, internationally standardized format ideal for APIs, logs, and file storage — avoid locale-dependent formats there.
Advantages and Disadvantages of the datetime Module
The datetime module handles the vast majority of real-world date and time needs directly, but it has some genuine rough edges worth understanding.
Python datetime Module — Interview Questions
Frequently asked interview questions about the datetime module, timezone handling, and date arithmetic.
Practice Questions — Test Your Understanding
Work through these practice questions on the datetime module. Try to reason through each one before checking the answer.
1. What does date(2026, 3, 16) - date(2026, 3, 1) return, and what is its value?
Easy2. Why might adding timedelta(days=1) to a datetime near a daylight-saving transition NOT change the clock time by exactly 24 hours in wall-clock terms for a timezone-aware datetime?
Hard3. What is the output of datetime(2026, 3, 16).strftime('%A')?
Easy4. Why does comparing datetime.now() with datetime.now(timezone.utc) raise a TypeError?
Medium5. If today is date(2026, 2, 27), what does today + timedelta(days=3) return, and why is this significant for February?
Medium6. What is wrong with using datetime.strptime('2026-13-01', '%Y-%m-%d')?
Medium7. How would you get the exact same moment in time expressed in two different time zones using zoneinfo?
Hard8. Why is a manual formula like 'age = this_year - birth_year' potentially incorrect for calculating someone's current age?
HardConclusion — Handling Dates and Times Correctly
The datetime module packs an enormous amount of real-world calendar and clock complexity behind a compact, well-designed set of classes. Its true value shows up not in the simple cases — printing today's date is trivial — but in the harder ones it quietly gets right: leap years, variable month lengths, daylight saving transitions, and the crucial distinction between a naive value and one that's genuinely, unambiguously anchored to a specific point in absolute time.
The habits worth carrying forward: work in UTC internally and convert to local time only for display, always use timezone-aware datetimes for anything that might be compared, stored, or shared across systems, reach for zoneinfo rather than manual offsets or the older pytz library on modern Python, and use timedelta for date arithmetic rather than manually computing seconds or days yourself.
From here, natural next topics include the third-party dateutil library for calendar-aware relative date arithmetic, the time module for lower-level, performance-focused timing needs, and working with dates in pandas for anyone handling time-series data at scale.