Dates & Times

The datetime module represents dates, times and durations; zoneinfo adds real-world time zones. Together they handle formatting, parsing, date arithmetic and conversions between zones — tasks that are full of traps if done by hand.

date, time and datetime

PY basics.py
from datetime import date, time, datetime

d = date(2026, 10, 6)
t = time(14, 30)
dt = datetime(2026, 10, 6, 14, 30, 15)

print(d, t, dt)
print(d.year, d.month, d.day, d.weekday(), d.isoweekday())
print(dt.hour, dt.minute, dt.second)
print(datetime.combine(d, t))
Output
2026-10-06 14:30:00 2026-10-06 14:30:15
2026 10 6 1 2
14 30 15
2026-10-06 14:30:00

Months start at 1 (unlike JavaScript). weekday() counts Monday as 0; isoweekday() counts Monday as 1. For the current moment use date.today() and datetime.now().

Durations: timedelta

PY timedelta.py
from datetime import date, datetime, timedelta

start = date(2026, 1, 28)
print(start + timedelta(days=7))
print(start - timedelta(weeks=4))

deadline = datetime(2026, 12, 31, 23, 59)
now = datetime(2026, 10, 6, 9, 0)
remaining = deadline - now
print(remaining)
print(remaining.days, "days,", remaining.seconds // 3600, "hours")
print(round(remaining.total_seconds() / 3600), "hours in total")
Output
2026-02-04
2025-12-31
86 days, 14:59:00
86 days, 14 hours
2079 hours in total

Adding a timedelta handles month and year boundaries correctly. Subtracting two dates or datetimes gives a timedelta. (For "add one month", which isn't a fixed number of days, the third-party dateutil package offers relativedelta.)

Formatting with strftime

PY strftime.py
from datetime import datetime

dt = datetime(2026, 10, 6, 14, 5)

print(dt.strftime("%Y-%m-%d %H:%M"))
print(dt.strftime("%A, %d %B %Y"))
print(dt.strftime("%a %d %b, %I:%M %p"))
print(dt.isoformat())
print(f"{dt:%d/%m/%Y}")
Output
2026-10-06 14:05
Tuesday, 06 October 2026
Tue 06 Oct, 02:05 PM
2026-10-06T14:05:00
06/10/2026
CodeMeaningCodeMeaning
%YYear (2026)%HHour, 24h (14)
%mMonth (10)%I / %pHour, 12h / AM-PM
%dDay (06)%M / %SMinute / second
%A / %aWeekday name / short%B / %bMonth name / short

Parsing strings

PY parsing.py
from datetime import datetime, date

print(date.fromisoformat("2026-10-06"))
print(datetime.fromisoformat("2026-10-06T14:05:00+01:00"))
print(datetime.strptime("06/10/2026 2:05 PM", "%d/%m/%Y %I:%M %p"))

try:
    datetime.strptime("31/02/2026", "%d/%m/%Y")
except ValueError as err:
    print("Invalid:", err)
Output
2026-10-06
2026-10-06 14:05:00+01:00
2026-10-06 14:05:00
Invalid: day 31 must be in range 1..28 for month 2 in year 2026

Prefer the ISO 8601 format (2026-10-06) for storing and exchanging dates — it's unambiguous and sorts correctly as text. strptime ("string parse time") reads any other format, given the matching codes.

Time zones

A datetime without time zone information is naive; one with it is aware. For anything involving users in different places, servers, or daylight saving time, use aware datetimes:

PY zones.py
from datetime import datetime, timezone
from zoneinfo import ZoneInfo

meeting = datetime(2026, 10, 6, 9, 0, tzinfo=ZoneInfo("America/New_York"))

print(meeting)
print(meeting.astimezone(ZoneInfo("Europe/Lisbon")))
print(meeting.astimezone(ZoneInfo("Asia/Tokyo")))
print(meeting.astimezone(timezone.utc).isoformat())
Output
2026-10-06 09:00:00-04:00
2026-10-06 14:00:00+01:00
2026-10-06 22:00:00+09:00
2026-10-06T13:00:00+00:00

zoneinfo uses the IANA time zone database (names like "Europe/Lisbon"), which knows every country's daylight saving rules. A good rule: store and compute in UTC, and convert to local time only for display.

Daylight saving in action

PY dst.py
from datetime import datetime, timezone
from zoneinfo import ZoneInfo

lisbon = ZoneInfo("Europe/Lisbon")
before = datetime(2026, 10, 24, 12, 0, tzinfo=lisbon)
after = datetime(2026, 10, 26, 12, 0, tzinfo=lisbon)

print(before.utcoffset(), after.utcoffset())
print(after - before)
print(after.astimezone(timezone.utc) - before.astimezone(timezone.utc))
Output
1:00:00 0:00:00
2 days, 0:00:00
2 days, 1:00:00

Clocks went back on 25 October, so those two "noons" are really 49 hours apart. But notice the second line: when both datetimes share the same time zone, Python subtracts the wall-clock times and ignores the offset change. Converting both to UTC first gives the true elapsed time — one more reason to do arithmetic in UTC.

Measuring elapsed time

PY timing.py
import time

start = time.perf_counter()
total = sum(i * i for i in range(200_000))
elapsed = time.perf_counter() - start

print(total, "computed", "quickly" if elapsed < 1 else "slowly")
print(type(time.time()).__name__)
Output
2666646666700000 computed quickly
float

Use time.perf_counter() to measure durations — it's precise and unaffected by clock changes. time.time() gives the current Unix timestamp (seconds since 1 January 1970 UTC).

Try it yourself

Try it yourself
Console output