Skip to content

description: "Dates & Times — timedelta objects support arithmetic (+, -) and comparisons directly with datetime/date objects."---

07 · Dates & Times

🎥 Video walkthrough

Nearly every real application deals with dates and times somewhere: timestamps in logs, expiration dates, scheduling. Python's datetime module is the standard tool for representing, comparing, and formatting them.

date, time, and datetime

from datetime import date, time, datetime

d = date(2026, 7, 18)
t = time(14, 30, 0)
dt = datetime(2026, 7, 18, 14, 30, 0)

print(d)     # 2026-07-18
print(t)     # 14:30:00
print(dt)    # 2026-07-18 14:30:00

now = datetime.now()          # current local date & time
today = date.today()          # current local date
print(now.year, now.month, now.day, now.hour, now.minute)

timedelta — durations and date arithmetic

from datetime import datetime, timedelta

start = datetime(2026, 1, 1)
one_week_later = start + timedelta(weeks=1)
ninety_days_later = start + timedelta(days=90)

print(one_week_later)      # 2026-01-08 00:00:00
print(ninety_days_later)   # 2026-04-01 00:00:00

deadline = datetime(2026, 12, 31)
remaining = deadline - datetime.now()
print(remaining.days, "days left")

timedelta objects support arithmetic (+, -) and comparisons directly with datetime/date objects.

Comparing dates and times

from datetime import date

release = date(2026, 7, 1)
today = date.today()

if today >= release:
    print("released")
else:
    print(f"{ (release - today).days } days until release")

Formatting dates: strftime

strftime ("string format time") converts a datetime into a string using format codes.

from datetime import datetime

dt = datetime(2026, 7, 18, 9, 5, 0)

print(dt.strftime("%Y-%m-%d"))            # 2026-07-18
print(dt.strftime("%B %d, %Y"))           # July 18, 2026
print(dt.strftime("%A, %I:%M %p"))        # Saturday, 09:05 AM
print(dt.isoformat())                      # 2026-07-18T09:05:00 — standard ISO 8601
Code Meaning Example
%Y 4-digit year 2026
%m zero-padded month 07
%d zero-padded day 18
%H / %I hour (24h / 12h) 09
%M minute 05
%B full month name July
%A full weekday name Saturday
%p AM/PM AM

Parsing dates: strptime

strptime ("string parse time") is the reverse: it turns a string into a datetime, given the format it's written in.

from datetime import datetime

parsed = datetime.strptime("2026-07-18 09:05", "%Y-%m-%d %H:%M")
print(parsed.year, parsed.hour)   # 2026 9

# ISO 8601 strings can be parsed directly, without a format string:
parsed_iso = datetime.fromisoformat("2026-07-18T09:05:00")

If the format string doesn't match the actual text, strptime raises ValueError — worth catching when parsing user- or file-supplied dates.

def parse_date_safe(text, fmt="%Y-%m-%d"):
    try:
        return datetime.strptime(text, fmt)
    except ValueError:
        return None

Time zones

Naive datetime objects (the ones we've made so far) carry no timezone information. Python 3.9+ ships zoneinfo in the standard library for real IANA timezone support.

from datetime import datetime
from zoneinfo import ZoneInfo

utc_now = datetime.now(ZoneInfo("UTC"))
ny_now = utc_now.astimezone(ZoneInfo("America/New_York"))
tokyo_now = utc_now.astimezone(ZoneInfo("Asia/Tokyo"))

print(utc_now)
print(ny_now)
print(tokyo_now)

# attaching a timezone to a naive datetime (assumes it already represents that zone):
meeting = datetime(2026, 7, 18, 15, 0, tzinfo=ZoneInfo("Europe/London"))
meeting_in_sf = meeting.astimezone(ZoneInfo("America/Los_Angeles"))
print(meeting_in_sf)

Comparing or subtracting a naive datetime and an aware one raises TypeError — always keep track of which kind you're working with, and prefer timezone-aware datetimes for anything involving more than one location.

Cheat sheet

Task Tool
Current local time datetime.now()
Current UTC time datetime.now(ZoneInfo("UTC"))
Add/subtract time timedelta(days=..., hours=...)
datetime -> string .strftime(fmt) or .isoformat()
string -> datetime datetime.strptime(text, fmt) or .fromisoformat()
Convert timezone .astimezone(ZoneInfo("..."))

How It Actually Works

datetime stores broken-out fields, not a single timestamp. Internally a datetime is a small struct of year, month, day, hour, minute, second, microsecond, and an optional tzinfo pointer — packed into a few bytes. It is not seconds-since-epoch. Comparisons and subtraction convert to a common scale (an internal ordinal day count plus seconds) on the fly.

datetime.now() is a system call. It asks the OS for the current wall time (clock_gettime(CLOCK_REALTIME) on Linux, similar elsewhere), which returns seconds + nanoseconds since the Unix epoch, then converts that into the broken-out fields using the local timezone rules. A naive datetime throws the timezone away after that conversion; an aware one keeps it.

timedelta normalizes on construction. Whatever units you pass (weeks, hours, milliseconds, …) are immediately reduced to a canonical (days, seconds, microseconds) triple with 0 ≤ seconds < 86400 and 0 ≤ microseconds < 10⁶. Date arithmetic is then just integer math on those fields, with month/day rollover handled by the calendar conversion.

strftime/strptime are format interpreters. strftime walks the format string and, for each %code, substitutes the corresponding field (often delegating to the C library's strftime, so locale settings matter for %B/%A). strptime builds a regular expression from the format string, matches it against the input, and pulls the captured pieces into a datetime — raising ValueError if the regex doesn't match.

zoneinfo reads the IANA tzdata database files (compiled binary tables of historical UTC-offset transitions and DST rules), caches the parsed ZoneInfo object, and computes an offset for a given instant by binary- searching the transition list. That's how it knows, say, that New York was UTC−5 in January but UTC−4 in July.

Exercise

Write a function days_until_birthday(birth_month, birth_day) that returns how many days remain until the next occurrence of that month/day (handling the case where it has already passed this year, so it should count toward next year instead). Then write format_countdown(target: datetime) that returns a human string like "3 days, 4 hours remaining" using a timedelta.