Beginner · Lesson 42 of 94

Dates and times

Watch this lesson

View full playlist ↗

Read the lesson

What you'll learn

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.

Dates and times: date(2026, 9, 8) → year 2026 · month 9 · day 8 · weekday 1 → datetime(2026, 9, 8, 14, 30) → + hour 14 · minute 30 → timedelta(days=7, seconds=52200)
Dates and times · Figure 1 · Open for full size

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.

Dates and times: input: local time + zone → convert to UTC, store → compute, compare, sort in UTC → display: astimezone(user's zone)
Dates and times · Figure 2 · Open for full size

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))
Output
2026-09-08 | 2026-09-08 14:30:15
2026 9 8 weekday 1 Tuesday
14 30 15
True date
True True

Formatting 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)
Output
08 September 2026 at 14:30
Tuesday, 2026-09-08
2026-09-08T14:30:00
2026-09-08 09:05:00
True

Arithmetic 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))
Output
2026-10-08 Thursday
107 days, 18:30:00
107 days, 2586.5 hours
True 2026-10-08

Aware 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))
Output
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 datetimes

Try 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

What's next

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.

Course overview