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
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))
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
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")
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
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}")
2026-10-06 14:05 Tuesday, 06 October 2026 Tue 06 Oct, 02:05 PM 2026-10-06T14:05:00 06/10/2026
| Code | Meaning | Code | Meaning |
|---|---|---|---|
%Y | Year (2026) | %H | Hour, 24h (14) |
%m | Month (10) | %I / %p | Hour, 12h / AM-PM |
%d | Day (06) | %M / %S | Minute / second |
%A / %a | Weekday name / short | %B / %b | Month name / short |
Parsing strings
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)
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:
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())
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
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))
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
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__)
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).