What you'll learn
- Create dates and datetimes, and pull out their parts
- Format a datetime for people with
strftimeand parse text into one withstrptime - Do arithmetic with
timedelta: add days, find differences, compare moments - Handle time zones correctly with
zoneinfo, and know why naive datetimes cause bugs
Concept
The datetime module gives you three types you will use constantly. A date is a calendar day with no time. A datetime is a day plus a time, down to microseconds. A timedelta is a duration, the difference between two moments, and it is what you add to or subtract from the others. datetime.now() gives the current moment; date.today() gives the day. Every part is an attribute, .year, .month, .day, .hour, and the .weekday() method gives the day of the week.

Formatting turns a datetime into text for a person; parsing does the reverse. Both use the same codes: %Y four-digit year, %m month, %d day, %H hour, %M minute, %A weekday name, %B month name. dt.strftime("%d %B %Y") gives 08 September 2026; datetime.strptime("2026-09-08", "%Y-%m-%d") reads it back. For machine-readable text, use ISO 8601: dt.isoformat() and datetime.fromisoformat(text) round-trip without any format string and sort correctly as plain strings.
| Code | Meaning | Example |
|---|---|---|
%Y |
year, four digits | 2026 |
%m |
month, two digits | 09 |
%d |
day, two digits | 08 |
%H |
hour, 24-hour | 14 |
%M |
minute | 30 |
%A |
weekday name | Tuesday |
%B |
month name | September |
Arithmetic goes through timedelta. Adding timedelta(days=30) to a date gives the date 30 days later, with month and year boundaries handled. Subtracting two datetimes gives a timedelta, whose .days and .total_seconds() tell you the gap. Comparisons with < and > work directly, so "is the deadline past" is deadline < datetime.now(). What timedelta does not do is months or years, because their length varies; for "same day next month" you adjust the fields yourself or use replace.
Time zones are where most date bugs live. A datetime without a zone is naive: it is a wall-clock reading with no idea where the clock is. Two naive datetimes from different places cannot be compared meaningfully, and daylight saving shifts break arithmetic on them. An aware datetime carries a tzinfo. zoneinfo.ZoneInfo("Asia/Dhaka") gives you a real zone with its rules; datetime.now(tz=ZoneInfo("UTC")) gives an aware current moment, and .astimezone(other_zone) converts. The rule for production code: store and compute in UTC, convert to a local zone only to display.

Code
Creating dates and datetimes and reading their parts. Fixed values are used so the output is stable; now() and today() work the same way.
# create.py
from datetime import date, datetime
d = date(2026, 9, 8)
dt = datetime(2026, 9, 8, 14, 30, 15)
print(d, "|", dt)
print(d.year, d.month, d.day, "weekday", d.weekday(), d.strftime("%A"))
print(dt.hour, dt.minute, dt.second)
print(dt.date() == d, type(dt.date()).__name__)
print(isinstance(datetime.now(), datetime), isinstance(date.today(), date))
2026-09-08 | 2026-09-08 14:30:15
2026 9 8 weekday 1 Tuesday
14 30 15
True date
True TrueFormatting for people, parsing from text, and the ISO round trip that needs no format string.
# format_parse.py
from datetime import datetime
dt = datetime(2026, 9, 8, 14, 30)
print(dt.strftime("%d %B %Y at %H:%M"))
print(dt.strftime("%A, %Y-%m-%d"))
print(dt.isoformat())
parsed = datetime.strptime("08/09/2026 09:05", "%d/%m/%Y %H:%M")
print(parsed)
print(datetime.fromisoformat("2026-09-08T14:30:00") == dt)
08 September 2026 at 14:30
Tuesday, 2026-09-08
2026-09-08T14:30:00
2026-09-08 09:05:00
TrueArithmetic with timedelta: deadlines, gaps, and comparisons, with a month boundary crossed correctly.
# arithmetic.py
from datetime import date, datetime, timedelta
start = date(2026, 9, 8)
deadline = start + timedelta(days=30)
print(deadline, deadline.strftime("%A"))
gap = datetime(2026, 12, 25, 9, 0) - datetime(2026, 9, 8, 14, 30)
print(gap)
print(gap.days, "days,", round(gap.total_seconds() / 3600, 1), "hours")
print(deadline < date(2026, 10, 10), start.replace(month=10))
2026-10-08 Thursday
107 days, 18:30:00
107 days, 2586.5 hours
True 2026-10-08Aware datetimes with zoneinfo. The same moment reads differently in three zones, and comparing an aware value with a naive one is refused.
# zones.py
from datetime import datetime
from zoneinfo import ZoneInfo
moment = datetime(2026, 9, 8, 12, 0, tzinfo=ZoneInfo("UTC"))
for zone in ("Asia/Dhaka", "Europe/London", "America/New_York"):
local = moment.astimezone(ZoneInfo(zone))
print(f"{zone:<18}{local.strftime('%H:%M %Z')}")
dhaka = datetime(2026, 9, 8, 18, 0, tzinfo=ZoneInfo("Asia/Dhaka"))
print(dhaka == moment)
print(dhaka < datetime(2026, 9, 8, 13, 0))
Asia/Dhaka 18:00 +06
Europe/London 13:00 BST
America/New_York 08:00 EDT
True
Traceback (most recent call last):
File "zones.py", line 11, in <module>
print(dhaka < datetime(2026, 9, 8, 13, 0))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: can't compare offset-naive and offset-aware datetimesTry it yourself
Create countdown.py. Store your next important date as a date, print how many days remain from a fixed "today" you define, and print the weekday it falls on. Then create an aware datetime for a 09:00 meeting in Asia/Dhaka, convert it to Europe/London and America/Los_Angeles, and print all three in a table. Finally parse the string "31/12/2026 23:59" with strptime and add one minute.
Common mistakes
- Naive datetimes for anything that crosses zones or daylight saving. Attach a
ZoneInfo, compute in UTC, convert to display. - Adding months with
timedelta(days=30). Months vary; usereplace(month=...)with care, or a helper that handles year rollover. - Parsing with the wrong format string.
%m/%dversus%d/%msilently swaps day and month when both are 12 or less; prefer ISO 8601 for anything machine-written.
What's next
date,datetime, andtimedeltamodel days, moments, and durations; parts are attributes.strftimeformats,strptimeparses, and ISO 8601 viaisoformat/fromisoformatround-trips safely.- Use aware datetimes with
zoneinfo: store and compute in UTC, convert to a local zone only to display.
You can handle time without the usual bugs. Next you will read and write the two data formats you will meet everywhere: JSON and CSV.