Dates and Times
Birthdays, deadlines, timestamps, opening hours: dates and times are everywhere. Python's built-in datetime module handles them, including the tricky parts like month lengths and leap years.
The main types
The datetime module has a few classes you'll use all the time:
| Type | Holds | Example |
|---|---|---|
date |
year, month, day | 2026-10-05 |
time |
hour, minute, second | 14:30:00 |
datetime |
date and time | 2026-10-05 14:30:00 |
timedelta |
a duration | 3 days, 4 hours |
One naming trap: the module is called datetime, and so is one of the types inside it. That's why you'll see from datetime import datetime, which means "from the datetime module, import the datetime type". If you write import datetime instead, you need the longer datetime.datetime.now() and datetime.date.today(). This lesson uses the from ... import style throughout.
Today and now
from datetime import date, datetime today = date.today() now = datetime.now() print(today) print(now) print(today.year, today.month, today.day) print(now.hour, now.minute)
Your output depends on when, and in which time zone, you run it. Notice that today() belongs to date and now() belongs to datetime. .year, .month, .day, .hour and .minute are plain numbers (no parentheses), so you can use them in calculations.
Going deeper: Time zones optional
datetime.now() gives a naive datetime: it has no idea which time zone it's in (tzinfo is None). That's fine for a program that only runs in one place. Once you deal with users or servers in different places, use aware datetimes, which carry their time zone with them:
from datetime import datetime, timezone, timedelta # None: naive local = datetime.now() print(local.tzinfo) # ends in +00:00 utc_now = datetime.now(timezone.utc) print(utc_now) # the same moment, Indian time india = timezone(timedelta(hours=5, minutes=30)) print(utc_now.astimezone(india))
A common rule is to store times in UTC and convert to the user's time zone only when you display them.
Fixed offsets like +05:30 don't know about daylight saving time. For real place names such as "Europe/London", use ZoneInfo from the zoneinfo module on your own computer: datetime.now(ZoneInfo("Europe/London")).
Creating a specific date
from datetime import date, datetime moon_landing = date(1969, 7, 20) meeting = datetime(2026, 3, 14, 15, 30) print(moon_landing) print(meeting) # Monday is 0, Sunday is 6 print(moon_landing.weekday()) # Monday is 1, Sunday is 7 print(moon_landing.isoweekday())
The arguments always go from biggest to smallest: year, month, day, then (for a datetime) hour and minute. datetime(2026, 3, 14, 15, 30) is 14 March 2026 at 3:30 pm. Write the numbers without leading zeros: date(2026, 03, 14) is a SyntaxError. The moon landing was on a Sunday, so weekday() gives 6.
Python checks that dates are real:
from datetime import date date(2026, 2, 30)
There's no 30 February, so this is a ValueError: "day is out of range for month".
Formatting dates with strftime
strftime() turns a date into text using format codes:
from datetime import datetime
moment = datetime(2026, 3, 14, 15, 9, 26)
print(moment.strftime("%d/%m/%Y"))
print(moment.strftime("%A, %d %B %Y"))
print(moment.strftime("%I:%M %p"))
print(moment.strftime("%Y-%m-%d %H:%M:%S"))
# format codes work in f-strings too
print(f"{moment:%b %d, %Y}")strftime() gives back a string. The format is a template: each % code is replaced by part of the date, and everything else (/, ,, spaces, words) is copied as it is. So "%d/%m/%Y" gives 14/03/2026.
The most useful codes:
| Code | Meaning | Example |
|---|---|---|
%Y |
4-digit year | 2026 |
%m |
month number | 03 |
%d |
day of month | 14 |
%B / %b |
month name / short | March / Mar |
%A / %a |
weekday name / short | Saturday / Sat |
%H |
hour (24-hour) | 15 |
%I |
hour (12-hour) | 03 |
%M |
minute | 09 |
%S |
second | 26 |
%p |
AM or PM | PM |
Watch the capitals: %m is the month and %M is the minute; %d is the day and %H is the hour. Mixing up %m and %M is the most common formatting bug.
Parsing text into dates with strptime
strptime() does the opposite: it reads a date from text, using the same codes to describe the layout:
from datetime import datetime
text = "25/12/2026 18:30"
christmas_dinner = datetime.strptime(text, "%d/%m/%Y %H:%M")
print(christmas_dinner)
print(christmas_dinner.strftime("%A"))The format must describe the text exactly, separators included: / where the text has /, a space where it has a space. A handy way to remember which is which:
| Method | Think | Direction |
|---|---|---|
strftime() |
f for format | date → text |
strptime() |
p for parse | text → date |
If the text doesn't match the format, you get a ValueError. The ISO format YYYY-MM-DD has a shortcut:
from datetime import date
print(date.fromisoformat("2026-07-04"))Date arithmetic with timedelta
A timedelta is a length of time. Add it to or subtract it from dates:
from datetime import date, timedelta start = date(2026, 1, 28) print(start + timedelta(days=7)) print(start + timedelta(weeks=2)) print(start - timedelta(days=30))
Month lengths and leap years are handled for you. Notice how adding a week to January 28 lands on February 4.
timedelta accepts weeks, days, hours, minutes and seconds, but not months or years, because they don't have a fixed length (a month can be 28 to 31 days). To move by months, build a new date, for example start.replace(month=3).
Subtracting two dates gives a timedelta:
from datetime import date start = date(2026, 1, 1) end = date(2026, 12, 25) gap = end - start print(gap) print(gap.days, "days")
Printing a timedelta shows days plus hours:minutes:seconds, here 358 days, 0:00:00. .days gives just the number of days, 358, as a plain number you can calculate with.
With datetime values, durations can include hours and minutes:
from datetime import datetime check_in = datetime(2026, 5, 1, 14, 0) check_out = datetime(2026, 5, 4, 11, 30) stay = check_out - check_in print(stay) print(stay.total_seconds() / 3600, "hours")
The stay prints as 2 days, 21:30:00. To get it in hours, total_seconds() converts the whole duration to seconds, and dividing by 3600 (seconds in an hour) gives 69.5. Don't use stay.seconds for this: it holds only the leftover part after the whole days (the 21:30:00), not the total.
Comparing dates
Dates compare naturally with <, > and ==, and they sort correctly:
from datetime import date
deadline = date(2026, 6, 30)
submitted = date(2026, 7, 2)
if submitted > deadline:
late = (submitted - deadline).days
print(f"Late by {late} days")
dates = [date(2026, 5, 1), date(2025, 12, 31), date(2026, 1, 15)]
print(sorted(dates))Timing your code
To measure how long something takes, subtract two datetime.now() values, or use time.perf_counter():
import time
start = time.perf_counter()
total = sum(i * i for i in range(1_000_000))
elapsed = time.perf_counter() - start
print(f"Result {total:,} in {elapsed:.3f} seconds")Exercises
Exercise 1: Day of the week
Print the day of the week you were born, or of any date you like, as a full name such as Thursday.
from datetime import date birthday = date(2000, 1, 1)
strftime() turns a date into text using format codes.
The code for the full weekday name is %A.
Exercise 2: Due date
A library book is borrowed on 2026-03-25 and is due 21 days later. Print the due date as Due: Wednesday 15 April 2026.
from datetime import date, timedelta borrowed = date(2026, 3, 25)
Add timedelta(days=21) to the borrowed date.
Format it with "Due: %A %d %B %Y".
Exercise 3: Days until an event
Parse the event date from the string and print how many days there are between today and the event: Days to go: 81.
from datetime import date, datetime today = date(2026, 10, 5) event_text = "December 25, 2026"
Parse with datetime.strptime(event_text, "%B %d, %Y") and add .date() to get a date.
Subtract today from the event date and print the .days of the result.
Exercise 4: Age calculator
Write age_on(birth, on_date) that returns a person's age in whole years on a given date. Remember: if their birthday hasn't happened yet that year, subtract one.
from datetime import date
def age_on(birth, on_date):
pass
print(age_on(date(1990, 8, 15), date(2026, 8, 14))) # 35
print(age_on(date(1990, 8, 15), date(2026, 8, 15))) # 36Start with the difference in years: on_date.year - birth.year.
If the birthday hasn't come yet that year, subtract 1. Comparing (month, day) tuples does this neatly.
Date and time tools at a glance
A quick reference for later. The examples use d = date(2026, 3, 15) and from datetime import date, datetime, timedelta. Results marked "e.g." depend on when you run them. The strftime codes, such as %d and %B, are in the table earlier in this lesson.
| Tool | What it does | Example | Result |
|---|---|---|---|
date.today() |
Today's date | date.today() |
e.g. 2026-10-06 |
datetime.now() |
The current date and time | datetime.now() |
e.g. 2026-10-06 14:30:05.123456 |
date(y, m, d) |
A specific date | date(2026, 3, 15) |
2026-03-15 |
datetime(y, m, d, h, min) |
A specific date and time | datetime(2026, 3, 15, 9, 30) |
2026-03-15 09:30:00 |
.year, .month, .day |
The parts of a date | d.month |
3 |
.weekday() |
Day of the week: Monday is 0, Sunday is 6 |
d.weekday() |
6 |
.strftime(format) |
Formats a date as text | d.strftime("%d %b %Y") |
'15 Mar 2026' |
datetime.strptime(text, format) |
Reads a date from text | datetime.strptime("15/03/2026", "%d/%m/%Y") |
2026-03-15 00:00:00 |
.isoformat() |
The standard YYYY-MM-DD text form |
d.isoformat() |
'2026-03-15' |
date.fromisoformat(text) |
Reads a YYYY-MM-DD date |
date.fromisoformat("2026-03-15") |
2026-03-15 |
timedelta(...) |
A length of time: days, hours, minutes, seconds, weeks |
d + timedelta(days=30) |
2026-04-14 |
date2 - date1 |
The time between two dates, as a timedelta |
(date(2026, 12, 25) - d).days |
285 |
.total_seconds() |
A timedelta in seconds |
timedelta(hours=1, minutes=30).total_seconds() |
5400.0 |
.replace(...) |
A copy with some parts changed | d.replace(year=2027) |
2027-03-15 |
time.perf_counter() |
A precise clock for timing code (import time) |
time.perf_counter() |
e.g. 1234.5678 |
Summary
- Import what you need:
from datetime import date, datetime, timedelta. date.today()anddatetime.now()give the current date and time.strftime()formats a date as text, andstrptime()parses text into a date. Both use codes like%Y,%mand%d.- Add or subtract a
timedeltato move dates. Subtracting two dates gives atimedelta. - Dates compare and sort naturally.
Know something worth teaching? Write your own tutorial with runnable examples and share it.