Dates and Deltas

Date and Time in Practice ๐Ÿ†

Chapter 1 ยท Dates and Deltas

Dates and times look simple until a program has to compare, add, store or display them. Months have different lengths, years sometimes have an extra day, and the same moment is written differently in different countries. This course is practical: each chapter takes one idea, runs it, and ends with a rule you can reuse. All examples use Python 3 and its built-in datetime module, so there is nothing to install.

What was run
Every snippet in this chapter was executed, and the output shown in the comments is what Python printed. Run them yourself in a file or the interactive prompt.

Four Types, Four Jobs

The module gives you separate types for separate ideas. Choosing the right one up front avoids a lot of later conversion.

TypeHoldsExample
datea calendar day, no time of daydate(2026, 11, 2)
timea time of day, no calendar daytime(14, 30, 5)
datetimea day and a time togetherdatetime(2026, 11, 2, 14, 30, 5)
timedeltaa length of time, not a point in timetimedelta(hours=1, minutes=30)
from datetime import date, time, datetime, timedelta d = date(2026, 11, 2) print(d) # 2026-11-02 print(d.year, d.month, d.day) # 2026 11 2 print(d.weekday(), d.isoweekday()) # 0 1 (Monday is 0 here, 1 in the ISO numbering) print(d.strftime("%A")) # Monday t = time(14, 30, 5) print(t) # 14:30:05 dt = datetime(2026, 11, 2, 14, 30, 5) print(dt) # 2026-11-02 14:30:05 print(repr(dt)) # datetime.datetime(2026, 11, 2, 14, 30, 5) print(datetime.combine(d, t)) # 2026-11-02 14:30:05

Invalid values are rejected immediately rather than silently rolling over, which is a useful safety net:

try: date(2026, 2, 30) except ValueError as e: print("ValueError:", e) # ValueError: day 30 must be in range 1..28 for month 2 in year 2026

Subtracting Gives a timedelta

Two points in time subtract to give a length of time. That length is a timedelta, and you can add it to or subtract it from a date or datetime to move forward or back.

a = date(2026, 11, 2) b = date(2027, 1, 1) gap = b - a print(gap) # 60 days, 0:00:00 print(gap.days) # 60 print(type(gap)) # <class 'datetime.timedelta'> print(a + timedelta(days=30)) # 2026-12-02 print(a - timedelta(weeks=2)) # 2026-10-19 print(a < b, a == date(2026, 11, 2)) # True True td = timedelta(hours=1, minutes=30) print(td, td.total_seconds()) # 1:30:00 5400.0 print(timedelta(seconds=-90)) # -1 day, 23:58:30 print(datetime(2026, 11, 2, 14, 30, 5) + timedelta(hours=10)) # 2026-11-03 00:30:05

Two details catch people out. First, a timedelta stores only days, seconds and microseconds, so there is no "months" or "years" argument: a month is not a fixed length. Second, a negative delta prints oddly (-1 day, 23:58:30 is minus 90 seconds), because the days part is negative and the rest is positive. Use total_seconds() when you want one plain number.

.seconds is not the total
The .seconds attribute of a timedelta is only the seconds left over after whole days are removed. For a span of a day or more it is the wrong thing to read. Use .total_seconds().

Turning Dates into Text and Back

strftime ("string from time") formats a date as text using percent codes. strptime ("string parsed to time") does the reverse: you give it the text and the format that describes it. The two use the same codes.

CodeMeaningExample
%Y %m %d4-digit year; month 01-12; day 01-312026 11 02
%H %M %Shour (24-hour); minute; second14 30 05
%A %Bfull weekday name; full month nameMonday November
dt = datetime(2026, 11, 2, 14, 30, 5) print(dt.strftime("%d/%m/%Y %H:%M")) # 02/11/2026 14:30 print(dt.strftime("%A %d %B %Y")) # Monday 02 November 2026 print(dt.isoformat()) # 2026-11-02T14:30:05 p = datetime.strptime("2026-11-02 14:30", "%Y-%m-%d %H:%M") print(p, p == datetime(2026, 11, 2, 14, 30)) # 2026-11-02 14:30:00 True print(date.fromisoformat("2026-11-02")) # 2026-11-02 print(datetime.fromisoformat("2026-11-02T14:30:05")) # 2026-11-02 14:30:05

The format must describe the text exactly, and the same text means different dates under different formats. Both lines below parse successfully and give a different day:

print(datetime.strptime("02/11/2026", "%d/%m/%Y").date()) # 2026-11-02 (2 November) print(datetime.strptime("11/02/2026", "%m/%d/%Y").date()) # 2026-11-02 (also 2 November, from a different text) try: datetime.strptime("2026-13-02", "%Y-%m-%d") except ValueError as e: print("ValueError:", e) # ValueError: time data '2026-13-02' does not match format '%Y-%m-%d'

So the text 02/11/2026 and the text 11/02/2026 both mean 2 November, but only because each was given the matching format. Handed the wrong format, the first would be read as 11 February. This is why you should store and exchange dates in the unambiguous ISO form (2026-11-02) and keep the day-first or month-first forms for display only.

A habit worth forming
Keep the right type for the job: date when there is no time of day, timedelta for lengths. Subtract to measure, add a timedelta to move. Prefer ISO text when storing, and catch ValueError when parsing anything a person typed.

Challenges

Exercise 1

Given born = date(1990, 5, 17) and today = date(2026, 11, 2), work out the person's age in whole years and the total number of days they have lived. Take care that the age is right when the birthday has not yet happened this year. Hint: tuples compare element by element, so (today.month, today.day) < (born.month, born.day) is a neat test.

๐Ÿ“„ View solution
Exercise 2

A shift starts at 09:15 and ends at 17:50 on the same day (use datetime objects for 2026-11-02). Print the length of the shift as a timedelta, as a decimal number of hours, and as whole hours plus remaining minutes. Predict each output before you run it.

๐Ÿ“„ View solution
Exercise 3

Write next_weekday(d, target) that returns the next date strictly after d that falls on weekday number target (Monday is 0, Sunday is 6). If d is already that weekday, return the date a week later. Test it with date(2026, 11, 2), which is a Monday, for a Monday, a Friday and a Sunday, and print the Friday's name with strftime.

๐Ÿ“„ View solution

Quick Reference

  • Types: date, time, datetime are points; timedelta is a length
  • Subtract two points to get a timedelta; add a timedelta to move a point
  • Read a delta with .days for whole days, .total_seconds() for everything
  • Format: strftime(fmt) to text, strptime(text, fmt) from text
  • ISO: isoformat() and fromisoformat() for unambiguous storage
  • Bad input raises ValueError; catch it when parsing user text
Coming next
Date and Time in Practice 2 looks at formatting and parsing in more depth: the full set of format codes, ambiguous inputs, and how to handle messy dates safely.