Python datetime Module
In this tutorial, we'll use the datetime module to create, read, format, compare, and calculate with dates and times.
Python's datetime module provides the main tools for working with calendar dates, clock times, durations, timestamps, and time zones.
It is part of the standard library, so there is nothing to install.
from datetime import date, datetime, time, timedelta, timezone
The names are similar, but they represent different things:
| Class | Represents | | --- | --- | | date | A year, month, and day | | time | A time of day without a date | | datetime | A date and time together | | timedelta | A duration or difference | | timezone | A fixed offset from UTC |
Creating dates and datetimes
Create a date by supplying its year, month, and day:
from datetime import date
launch_date = date(2026, 7, 20)
print(launch_date)
print(launch_date.year)
print(launch_date.month)
print(launch_date.day)
Output:
2026-07-20
2026
7
20
A datetime also contains an hour, minute, second, and optional microsecond:
from datetime import datetime
lesson_start = datetime(2026, 7, 20, 18, 30)
print(lesson_start)
print(lesson_start.hour)
Python validates these values. date(2026, 2, 30) raises ValueError because that date does not exist.
Getting today's date and the current time
Use date.today() when you only need the local calendar date:
from datetime import date
today = date.today()
print(today)
For an exact moment, prefer an aware UTC datetime:
from datetime import datetime, timezone
now_utc = datetime.now(timezone.utc)
print(now_utc)
The UTC offset appears in the result:
2026-07-20 12:34:56.789012+00:00
The older datetime.utcnow() method returns a datetime with no timezone information. datetime.now(timezone.utc) makes the meaning explicit and is safer when timestamps move between systems.
Parsing ISO dates and datetimes
APIs and databases commonly use ISO 8601 text. Python can parse many ISO values directly:
from datetime import date, datetime
day = date.fromisoformat("2026-07-20")
meeting = datetime.fromisoformat("2026-07-20T18:30:00+01:00")
print(day)
print(meeting)
fromisoformat() returns an object, not another string. This means you can compare it, calculate with it, or read its individual fields.
Parsing other date formats with strptime
When incoming text uses another format, use datetime.strptime():
from datetime import datetime
text = "20/07/2026 18:30"
value = datetime.strptime(text, "%d/%m/%Y %H:%M")
print(value)
Common format codes include:
| Code | Meaning | Example | | --- | --- | --- | | %Y | Four-digit year | 2026 | | %m | Two-digit month | 07 | | %d | Two-digit day | 20 | | %H | Hour using a 24-hour clock | 18 | | %M | Minute | 30 | | %S | Second | 45 | | %A | Full weekday name | Monday | | %B | Full month name | July |
The format must describe the text exactly. Using %m/%d/%Y for 20/07/2026 fails because Python tries to treat 20 as a month.
Formatting dates with strftime
strftime() performs the opposite conversion: it turns a date or datetime into text.
from datetime import date
value = date(2026, 7, 20)
print(value.strftime("%d/%m/%Y"))
print(value.strftime("%A, %d %B %Y"))
Output:
20/07/2026
Monday, 20 July 2026
Use .isoformat() when another program will consume the value:
print(value.isoformat())
2026-07-20
ISO format is unambiguous, sorts naturally, and is widely supported.
Adding and subtracting time
A timedelta represents a duration:
from datetime import date, timedelta
start = date(2026, 7, 20)
print(start + timedelta(days=10))
print(start - timedelta(weeks=1))
Output:
2026-07-30
2026-07-13
Python handles month ends, year ends, and leap years for you.
Subtracting two dates produces a timedelta:
from datetime import date
start = date(2026, 7, 20)
finish = date(2026, 8, 3)
difference = finish - start
print(difference.days)
14
For datetimes, use .total_seconds() when you need the complete duration:
from datetime import datetime
start = datetime(2026, 7, 20, 9, 15)
finish = datetime(2026, 7, 20, 11, 0)
difference = finish - start
print(difference.total_seconds() / 60)
105.0
The .seconds attribute only contains the seconds left after whole days have been removed. It is not always the total duration.
Comparing dates
Date and datetime objects support normal comparisons:
from datetime import date
deadline = date(2026, 8, 1)
today = date(2026, 7, 20)
if today < deadline:
print("There is still time")
Compare objects of compatible types. Comparing a date directly with a datetime, or an aware datetime with a naive datetime, can raise TypeError or produce unclear code.
Combining a date and a time
Forms often collect these values separately. Join them with datetime.combine():
from datetime import date, datetime, time
day = date(2026, 7, 20)
start_time = time(18, 30)
starts_at = datetime.combine(day, start_time)
print(starts_at)
2026-07-20 18:30:00
Unix timestamps
A Unix timestamp counts seconds from 1 January 1970 at UTC.
from datetime import datetime, timezone
value = datetime.fromtimestamp(0, tz=timezone.utc)
print(value.isoformat())
1970-01-01T00:00:00+00:00
Convert an aware datetime back with .timestamp():
print(value.timestamp())
0.0
Always be clear about the timezone when converting timestamps. Otherwise the result may depend on the machine running the code.
Naive and aware datetimes
A naive datetime has no timezone information:
from datetime import datetime
naive = datetime(2026, 7, 20, 18, 30)
print(naive.tzinfo)
None
An aware datetime includes an offset or timezone:
from datetime import datetime, timezone
aware = datetime(2026, 7, 20, 18, 30, tzinfo=timezone.utc)
print(aware.isoformat())
2026-07-20T18:30:00+00:00
Use timezone(timedelta(...)) for a fixed offset. For real locations whose offset changes with daylight saving time, use the standard-library zoneinfo module:
from datetime import datetime
from zoneinfo import ZoneInfo
london_time = datetime(
2026, 7, 20, 18, 30,
tzinfo=ZoneInfo("Europe/London"),
)
new_york_time = london_time.astimezone(ZoneInfo("America/New_York"))
print(london_time.isoformat())
print(new_york_time.isoformat())
astimezone() preserves the same instant while changing how it is displayed. Do not replace tzinfo merely to convert an existing datetime; that relabels the value instead of converting it.
A practical example: calculating age
An age is not simply the difference between two years. You must check whether the birthday has occurred:
from datetime import date
def age_on(birth_date, current_date):
years = current_date.year - birth_date.year
birthday_has_not_happened = (
current_date.month,
current_date.day,
) < (
birth_date.month,
birth_date.day,
)
return years - int(birthday_has_not_happened)
birth = date(2000, 12, 10)
today = date(2026, 7, 20)
print(age_on(birth, today))
25
Common datetime mistakes
- Storing local times without recording their timezone.
- Assuming all days contain exactly 24 local hours around daylight-saving changes.
- Confusing
%mfor month with%Mfor minute. - Using
.secondswhen.total_seconds()is required. - Comparing text dates instead of parsing them first.
- Using a fixed UTC offset when a real geographical timezone is required.
- Mixing aware and naive datetimes.
Practice
1. Parse "31/12/2026 23:45" into a datetime. 2. Format it as "Thursday, 31 December 2026". 3. Add 90 minutes and observe the new date and year. 4. Write a function that returns the Monday for any supplied date. 5. Convert an aware London datetime to New York time.
When you are ready, try the [interactive Python coding exercises](/codingexercises), including the premium Datetime practice pack.