Learning Goals
3 minBy the end of this lesson you can:
- Get the current moment with
datetime.now()and pull it apart. - Format a datetime into any string with
strftime(and remember the codes). - Parse a string back into a datetime with
strptime. - Add and subtract time with
timedelta, and compare dates.
Warm-Up · Time Is Everywhere
5 minLook at how often automation touches time:
backup-2026-05-28.zip timestamped filenames "delete logs older than 30 days" date arithmetic "email the report every Monday" day-of-week logic "2026-05-28T14:30:00" parsing API timestamps
A datetime is a real object — not a string — that you can compare, subtract, and reformat. The two skills that matter most: strftime (datetime → string, "format") and strptime (string → datetime, "parse"). Get those two straight and dates stop being scary.
New Concept · The datetime Toolkit
14 minGetting "now"
from datetime import datetime, date, timedelta now = datetime.now() print(now) # 2026-05-28 14:30:12.345678 print(now.year, now.month, now.day) print(now.hour, now.minute) print(now.weekday()) # 0 = Monday … 6 = Sunday print(date.today()) # just the date: 2026-05-28
Formatting: datetime → string (strftime)
now = datetime.now() now.strftime("%Y-%m-%d") # 2026-05-28 (great for filenames/sorting) now.strftime("%Y%m%d-%H%M%S") # 20260528-143012 (backup stamps) now.strftime("%d/%m/%Y") # 28/05/2026 now.strftime("%A, %d %B %Y") # Thursday, 28 May 2026 now.strftime("%I:%M %p") # 02:30 PM
The format codes you'll actually use:
%Y 4-digit year 2026 %H hour 24h 14 %m month 01-12 05 %M minute 30 %d day 01-31 28 %S second 12 %B month name May %A weekday Thursday %b month abbrev May %a wkday abr Thu %p AM/PM PM %I hour 12h 02
Use %Y-%m-%d (or %Y%m%d) in filenames. Because it goes big-unit-to-small, the files sort chronologically when sorted alphabetically — 2026-05-28 comes before 2026-06-01 automatically. Never use %d/%m/%Y in a filename (slashes are path separators!).
Parsing: string → datetime (strptime)
# you give the EXACT format the string is in, and Python reads it d = datetime.strptime("2026-05-28", "%Y-%m-%d") d = datetime.strptime("28/05/2026 14:30", "%d/%m/%Y %H:%M") print(d.year, d.hour) # 2026 14
The trick: strptime's second argument is a recipe describing the input. If it doesn't match exactly, you get a ValueError — which is good, it catches malformed data. (For ISO strings like "2026-05-28T14:30:00", datetime.fromisoformat(s) is even easier.)
Arithmetic with timedelta
now = datetime.now() tomorrow = now + timedelta(days=1) last_week = now - timedelta(weeks=1) in_2_hours = now + timedelta(hours=2) deadline = now + timedelta(days=30, hours=12) # subtract two datetimes → a timedelta age = now - datetime(2026, 1, 1) print(age.days) # how many days into the year print(age.total_seconds()) # the whole gap in seconds
timedelta represents a duration. Add it to a datetime to move forward/back; subtract two datetimes to get the gap between them.
Comparing dates
cutoff = datetime.now() - timedelta(days=30) file_time = datetime.fromtimestamp(some_path.stat().st_mtime) if file_time < cutoff: print("older than 30 days — a cleanup candidate")
Datetimes compare with <, >, == like numbers. This is exactly how "delete files older than N days" works — and it ties straight back to st_mtime from Lesson 6.
Worked Example · A Log-Retention Cleaner
12 minGoal: find log files older than a cutoff and report each one's exact age — the classic "retention policy" task, combining datetime with Lesson 6's file metadata.
from pathlib import Path from datetime import datetime, timedelta def old_logs(folder: str, days: int = 30) -> list[Path]: cutoff = datetime.now() - timedelta(days=days) old = [] for log in Path(folder).rglob("*.log"): modified = datetime.fromtimestamp(log.stat().st_mtime) if modified < cutoff: age = datetime.now() - modified print(f"{log.name:30} {age.days:4} days old " f"(modified {modified:%Y-%m-%d})") old.append(log) print(f"\n{len(old)} log(s) older than {days} days.") return old # caller decides whether to delete — we only report old_logs("logs", days=30)
app-2026-03-01.log 88 days old (modified 2026-03-01) app-2026-04-12.log 46 days old (modified 2026-04-12) 2 log(s) older than 30 days.
Read the code
Three datetime tools work together: timedelta(days=days) builds the cutoff, fromtimestamp(st_mtime) turns the file's raw timestamp into a datetime, and the < comparison decides what's old. The {modified:%Y-%m-%d} inside the f-string is strftime in disguise — you can format dates right in the placeholder. Notice it returns the list rather than deleting: the script reports, a human (or Lesson 38's backup tool) decides.
Try It Yourself
13 minWrite stamp() that returns the current time as YYYYMMDD-HHMMSS, and pretty() that returns it as "Thursday, 28 May 2026 at 02:30 PM". Print both.
Write days_until(date_str) that parses a "YYYY-MM-DD" string and returns how many days from today until that date (negative if past). Test with a birthday or holiday.
Hint
from datetime import datetime, date def days_until(date_str: str) -> int: target = datetime.strptime(date_str, "%Y-%m-%d").date() return (target - date.today()).days print(days_until("2026-12-25"))
Write last_monday() that returns the date of the most recent Monday (today if today is Monday). Use weekday() and timedelta.
Hint
from datetime import date, timedelta def last_monday() -> date: today = date.today() return today - timedelta(days=today.weekday()) # weekday(): Mon=0 print(last_monday())
Mini-Challenge · The Uptime Reporter
8 minWrite human_age(seconds) that turns a duration in seconds into a friendly string like "2 days, 3 hours, 14 minutes", omitting any zero units. Build it from a timedelta. Test with 90, 3700, and 200000 seconds.
Show a sample solution
from datetime import timedelta def human_age(seconds: float) -> str: td = timedelta(seconds=int(seconds)) days = td.days hours, rem = divmod(td.seconds, 3600) minutes, secs = divmod(rem, 60) parts = [] if days: parts.append(f"{days} day{'s' if days != 1 else ''}") if hours: parts.append(f"{hours} hour{'s' if hours != 1 else ''}") if minutes: parts.append(f"{minutes} minute{'s' if minutes != 1 else ''}") if secs and not days: parts.append(f"{secs} second{'s' if secs != 1 else ''}") return ", ".join(parts) or "0 seconds" print(human_age(90)) # 1 minute, 30 seconds print(human_age(3700)) # 1 hour, 1 minute print(human_age(200000)) # 2 days, 7 hours, 33 minutes
Non-negotiables: build from timedelta, drop zero units, correct pluralisation.
Recap
3 mindatetime.now() gives the current moment as an object you can pull apart (.year, .weekday()). strftime formats a datetime into a string with codes like %Y-%m-%d %H:%M:%S — use big-to-small order in filenames so they sort chronologically. strptime parses a string back into a datetime given the exact format. timedelta represents durations: add it to move in time, or subtract two datetimes to measure the gap, and compare datetimes with </>. These power timestamped files, retention policies, and scheduling.
Vocabulary Card
- strftime
- "String FROM time" — format a datetime into a string.
- strptime
- "String PARSE time" — read a string into a datetime, given its format.
- timedelta
- A duration (days/hours/…); add or subtract it from a datetime.
- fromtimestamp
- Converts a Unix timestamp (like
st_mtime) into a datetime.
Homework
4 minBuild age.py <birthdate> (using argparse) that takes a date in YYYY-MM-DD form and prints: your exact age in years, the total days you've been alive, your next birthday's date and how many days until it, and which weekday you were born on. Handle a bad date format with a friendly error.
Sample · age.py
import argparse, sys from datetime import datetime, date p = argparse.ArgumentParser(description="Age calculator.") p.add_argument("birthdate", help="YYYY-MM-DD") a = p.parse_args() try: born = datetime.strptime(a.birthdate, "%Y-%m-%d").date() except ValueError: print("Use the format YYYY-MM-DD, e.g. 2010-03-14") sys.exit(1) today = date.today() years = today.year - born.year - ( (today.month, today.day) < (born.month, born.day)) total_days = (today - born).days next_bday = born.replace(year=today.year) if next_bday < today: next_bday = next_bday.replace(year=today.year + 1) print(f"Age: {years} years") print(f"Days alive: {total_days}") print(f"Born on a: {born:%A}") print(f"Next birthday: {next_bday:%Y-%m-%d} " f"(in {(next_bday - today).days} days)")
Non-negotiables: strptime with error handling, exact years, days alive, born weekday, next-birthday countdown.