Programming with Python | Chapter 20: Working with Dates and Times (datetime
module)
Chapter Objectives
- Understand the purpose of the
datetime
module in the standard library. - Learn about the main object types:
date
,time
,datetime
, andtimedelta
. - Create
date
,time
, anddatetime
objects representing specific points in time. - Get the current local date and time using
date.today()
anddatetime.now()
. - Create
timedelta
objects representing durations or differences between dates/times. - Perform arithmetic operations with
date
,datetime
, andtimedelta
objects. - Format date and time objects into strings using
strftime()
. - Parse strings into
datetime
objects usingstrptime()
.
Introduction
Handling dates and times is a common requirement in many applications, from logging events and scheduling tasks to calculating durations and displaying timestamps. Python’s built-in datetime
module provides a comprehensive set of classes and functions for representing and manipulating dates, times, and time intervals (durations). This chapter explores the primary objects within the datetime
module – date
, time
, datetime
, and timedelta
– and demonstrates how to create them, perform calculations, and format them into user-friendly strings or parse strings back into date/time objects.
Theory & Explanation
The datetime
Module Overview
The datetime
module supplies classes for working with dates and times. While timezone handling is also supported (using the tzinfo
abstract base class and third-party libraries like pytz
or the standard library’s zoneinfo
in Python 3.9+), this chapter focuses on “naive” date/time objects, which don’t contain timezone information.
Main Object Types:
date
: Represents a date (year, month, day). Attributes:year
,month
,day
.time
: Represents a time of day (hour, minute, second, microsecond). Attributes:hour
,minute
,second
,microsecond
.datetime
: Represents a specific point in time, combining both date and time information. Attributes:year
,month
,day
,hour
,minute
,second
,microsecond
.timedelta
: Represents a duration or the difference between twodate
,time
, ordatetime
objects. Stores days, seconds, microseconds.
Creating Date/Time Objects
You typically create these objects by importing the specific class from the datetime
module and calling its constructor.
import datetime
# Create a date object
d = datetime.date(2024, 12, 25) # Year, Month, Day
print(f"Date: {d}")
print(f"Year: {d.year}, Month: {d.month}, Day: {d.day}")
# Create a time object
t = datetime.time(14, 30, 5) # Hour, Minute, Second
print(f"\nTime: {t}")
print(f"Hour: {t.hour}, Minute: {t.minute}, Second: {t.second}")
# Can also include microseconds: datetime.time(14, 30, 5, 123456)
# Create a datetime object
dt = datetime.datetime(2024, 10, 31, 18, 0, 0) # Year, Month, Day, Hour, Minute, Second
print(f"\nDateTime: {dt}")
print(f"Year: {dt.year}, Hour: {dt.hour}")
datetime.datetime
object.Getting Current Date and Time
datetime.date.today()
: Returns the current local date.datetime.datetime.now()
: Returns the current local date and time.datetime.datetime.utcnow()
: Returns the current UTC (Coordinated Universal Time) date and time (naive, without timezone info unless specified).
import datetime
today_date = datetime.date.today()
print(f"Today's Date: {today_date}")
now_datetime = datetime.datetime.now()
print(f"Current Local DateTime: {now_datetime}")
now_utc = datetime.datetime.utcnow()
print(f"Current UTC DateTime: {now_utc}")
timedelta
Objects: Representing Durations
timedelta
objects are used to represent differences between dates/times or fixed durations.
import datetime
delta1 = datetime.timedelta(days=7, hours=3, minutes=30)
print(f"Timedelta 1: {delta1}") # Output: 7 days, 3:30:00
delta2 = datetime.timedelta(weeks=2, seconds=10)
print(f"Timedelta 2: {delta2}") # Output: 14 days, 0:00:10
# Calculate the difference between two datetimes
dt1 = datetime.datetime(2024, 4, 10, 10, 0, 0)
dt2 = datetime.datetime(2024, 4, 12, 12, 30, 0)
time_diff = dt2 - dt1
print(f"\nDifference between dt2 and dt1: {time_diff}") # Output: 2 days, 2:30:00
print(f"Type of difference: {type(time_diff)}") # <class 'datetime.timedelta'>
timedelta
representing the duration between two points in time.Date/Time Arithmetic
You can perform arithmetic using date
, datetime
, and timedelta
objects:
date/datetime + timedelta = date/datetime
date/datetime - timedelta = date/datetime
date/datetime - date/datetime = timedelta
timedelta +/- timedelta = timedelta
timedelta * number = timedelta
timedelta / number = timedelta
import datetime
today = datetime.date.today()
print(f"Today: {today}")
# Add 10 days to today
ten_days_later = today + datetime.timedelta(days=10)
print(f"10 days later: {ten_days_later}")
# Subtract 2 weeks from today
two_weeks_ago = today - datetime.timedelta(weeks=2)
print(f"2 weeks ago: {two_weeks_ago}")
now = datetime.datetime.now()
print(f"\nNow: {now}")
# Add 1 hour and 30 minutes
later = now + datetime.timedelta(hours=1, minutes=30)
print(f"1h 30m later: {later}")
# Calculate difference between two dates
date1 = datetime.date(2025, 1, 1)
date2 = datetime.date(2024, 1, 1)
days_between = date1 - date2
print(f"\nDays between {date1} and {date2}: {days_between.days}") # Access .days attribute
Formatting Dates and Times (strftime
)
Often, you need to display date/time objects in a specific, human-readable format. The strftime()
(string format time) method is used for this. It takes a format string containing special codes (directives) that represent parts of the date/time.
graph LR A["datetime object<br>(e.g., 2024-04-23 17:40:00)"] --> B{"Call <i>.strftime(format_string)</i>"}; C["Format String<br>(e.g., <i>'%Y/%m/%d %H:%M'</i>)"] --> B; B -- Uses directives<br>%Y -> 2024<br>%m -> 04<br>%d -> 23<br>%H -> 17<br>%M -> 40 --> D["Formatted String<br>(e.g., '2024/04/23 17:40')"]; style A fill:#DBEAFE,stroke:#60A5FA,stroke-width:1px style C fill:#FEF9C3,stroke:#F59E0B,stroke-width:1px style B fill:#EDE9FE,stroke:#5B21B6,stroke-width:1px style D fill:#D1FAE5,stroke:#34D399,stroke-width:1px
Common strftime Directives:
Directive | Meaning | Example Output |
---|---|---|
%Y |
Year with century | 2024 |
%y |
Year without century (zero-padded) | 24 |
%m |
Month as zero-padded number | 04, 11 |
%B |
Full month name | April, December |
%b or %h |
Abbreviated month name | Apr, Dec |
%d |
Day of month as zero-padded number | 05, 25 |
%A |
Full weekday name | Saturday, Monday |
%a |
Abbreviated weekday name | Sat, Mon |
%H |
Hour (24-hour clock, zero-padded) | 08, 17 |
%I |
Hour (12-hour clock, zero-padded) | 08, 05 |
%p |
Locale’s equivalent of AM/PM | AM, PM |
%M |
Minute (zero-padded) | 05, 30 |
%S |
Second (zero-padded) | 09, 55 |
%f |
Microsecond (zero-padded) | 123456, 000000 |
%Z |
Time zone name (empty if naive) | UTC, EST, |
%z |
UTC offset (+HHMM or -HHMM, empty if naive) | +0000, -0500, |
%c |
Locale’s appropriate date/time representation | Sat Apr 12 18:13:00 2025 |
%x |
Locale’s appropriate date representation | 04/12/25 |
%X |
Locale’s appropriate time representation | 18:13:00 |
%% |
A literal ‘%’ character | % |
(Many more directives exist; consult Python documentation.)
import datetime
now = datetime.datetime.now()
# Example formatting
print(f"ISO format: {now.isoformat()}") # Standard ISO 8601 format
print(f"YYYY-MM-DD: {now.strftime('%Y-%m-%d')}")
print(f"Full date: {now.strftime('%A, %B %d, %Y')}")
print(f"Time (12-hr): {now.strftime('%I:%M:%S %p')}")
print(f"Date and Time: {now.strftime('%Y/%m/%d %H:%M')}")
print(f"Locale specific: {now.strftime('%c')}")
Parsing Strings into Dates and Times (strptime
)
The reverse operation – converting a string containing date/time information into a datetime
object – is done using datetime.strptime()
(string parse time). You must provide the string and a format string that exactly matches the structure of the input string.
graph LR A["Input String<br>(e.g., '2024-12-25 09:00')"] --> B{"Call <i>datetime.strptime(string, format_string)</i>"}; C["Format String<br>(e.g., <i>'%Y-%m-%d %H:%M'</i>)"] --> B; B -- Matches string parts to directives --> D{Valid Match?}; D -- Yes --> E["datetime object<br>(e.g., 2024-12-25 09:00:00)"]; D -- No --> F[Raises ValueError]; style A fill:#D1FAE5,stroke:#34D399,stroke-width:1px style C fill:#FEF9C3,stroke:#F59E0B,stroke-width:1px style B fill:#EDE9FE,stroke:#5B21B6,stroke-width:1px style E fill:#DBEAFE,stroke:#60A5FA,stroke-width:1px style F fill:#FECACA,stroke:#B91C1C,stroke-width:1px
Syntax:
datetime_object = datetime.datetime.strptime(date_string, format_string)
```python
import datetime
date_str1 = "2024-07-20"
format_str1 = "%Y-%m-%d"
dt_obj1 = datetime.datetime.strptime(date_str1, format_str1)
# Note: strptime creates a datetime object, time defaults to 00:00:00
print(f"Parsed date 1: {dt_obj1}")
print(f"Parsed date 1 (date only): {dt_obj1.date()}") # Get just the date part
date_str2 = "15/Mar/2023 10:30 AM"
format_str2 = "%d/%b/%Y %I:%M %p" # Must match exactly
dt_obj2 = datetime.datetime.strptime(date_str2, format_str2)
print(f"Parsed datetime 2: {dt_obj2}")
# Incorrect format will raise ValueError
# date_str3 = "2023-03-15"
# format_str3 = "%d/%m/%Y" # Format doesn't match string
# try:
# datetime.datetime.strptime(date_str3, format_str3)
# except ValueError as e:
# print(f"\nError parsing: {e}")
If the input string doesn’t match the format string, strptime
raises a ValueError
.
Concept / Class | Description | Example Usage |
---|---|---|
datetime.date |
Represents a date (year, month, day). Naive by default. | import datetime d = datetime.date(2024, 12, 25) today = datetime.date.today() |
datetime.time |
Represents a time of day (hour, minute, second, microsecond). Naive by default. | import datetime t = datetime.time(14, 30, 5) |
datetime.datetime |
Represents a specific point in time (date + time). Naive by default. | import datetime dt = datetime.datetime(2024, 10, 31, 18, 0) now = datetime.datetime.now() |
datetime.timedelta |
Represents a duration or difference between two date/time objects. Stores days, seconds, microseconds. | import datetime delta = datetime.timedelta(days=7, hours=3) diff = dt2 - dt1 # Results in timedelta |
Arithmetic | Perform calculations using date , datetime , and timedelta objects. |
future_date = date_obj + delta past_date = dt_obj - delta duration = dt_obj2 - dt_obj1 |
strftime() (string format time) |
Formats a date , time , or datetime object into a string according to specified format codes (directives like %Y , %m , %d ). |
formatted = now.strftime('%Y-%m-%d %H:%M') # Output: '2024-04-23 17:40' (example) |
strptime() (string parse time) |
Parses a string representing a date and time into a datetime object, given a corresponding format string. Raises ValueError if the string doesn’t match the format. |
date_str = "2024/07/20 10:00" format_str = "%Y/%m/%d %H:%M" dt_obj = datetime.datetime.strptime(date_str, format_str) |
Code Examples
Example 1: Basic Date/Time Operations and Formatting
# datetime_basics.py
import datetime
# Get current date and time
now = datetime.datetime.now()
today = datetime.date.today()
print(f"Current datetime: {now}")
print(f"Current date: {today}")
# Create specific objects
birthday = datetime.date(1995, 6, 15)
appointment = datetime.datetime(2025, 5, 1, 11, 0, 0)
print(f"\nBirthday: {birthday}")
print(f"Appointment: {appointment}")
# Formatting
print(f"\nFormatted Birthday: {birthday.strftime('%B %d, %Y')}")
print(f"Formatted Appointment: {appointment.strftime('%a, %b %d - %I:%M %p')}")
# Calculate difference
time_until_appointment = appointment - now
print(f"\nTime until appointment: {time_until_appointment}")
# Note: timedelta doesn't have simple year/month attributes
print(f"Days until appointment: {time_until_appointment.days}")
# Add duration
event_duration = datetime.timedelta(hours=2, minutes=45)
event_end_time = appointment + event_duration
print(f"Appointment ends at: {event_end_time.strftime('%H:%M')}")
Explanation:
- Demonstrates creating
date
anddatetime
objects for specific and current times. - Shows formatting using
strftime
with different directives. - Calculates the difference between two
datetime
objects, resulting in atimedelta
. - Adds a
timedelta
to adatetime
object to find a future time.
Example 2: Parsing and Calculating Age
# calculate_age.py
import datetime
def calculate_age(birthdate_str, date_format="%Y-%m-%d"):
"""Calculates age based on a birthdate string."""
try:
birthdate = datetime.datetime.strptime(birthdate_str, date_format).date()
today = datetime.date.today()
# Basic age calculation (might be off by one day depending on birthday)
age = today.year - birthdate.year
# Adjust if birthday hasn't occurred yet this year
# Compare (month, day) tuples
if (today.month, today.day) < (birthdate.month, birthdate.day):
age -= 1
return age
except ValueError:
print(f"Error: Invalid date format or value for '{birthdate_str}'. Expected format: {date_format}")
return None
# --- Get input and calculate ---
dob_input = input("Enter your date of birth (YYYY-MM-DD): ")
age = calculate_age(dob_input)
if age is not None:
print(f"You are approximately {age} years old.")
# Test edge case
print("\nTesting someone born today:")
today_str = datetime.date.today().strftime("%Y-%m-%d")
age_today = calculate_age(today_str)
if age_today is not None:
print(f"Someone born today is {age_today} years old.") # Should be 0
Explanation:
- The
calculate_age
function takes a birthdate string and format. - It uses
strptime
to parse the string into adatetime
object and then gets the.date()
part. - It gets today's date.
- It performs a common (though slightly simplified) age calculation by subtracting years and then adjusting if the current month/day is before the birth month/day.
- Includes
try...except ValueError
to handle incorrect date strings or formats provided by the user.
Common Mistakes or Pitfalls
- Naive vs. Aware Objects: This chapter focused on naive objects (no timezone info). Performing arithmetic or comparisons between timezone-aware and naive objects, or across different timezones without proper handling, can lead to incorrect results. Use libraries like
pytz
or Python 3.9+'szoneinfo
for robust timezone support when needed. strftime
vs.strptime
: Confusing the two functions –strftime
formats a date/time object into a string, whilestrptime
parses a string into a date/time object.- Incorrect Format Strings: Providing a format string to
strptime
that doesn't exactly match the input date string will cause aValueError
. Similarly, using incorrect directives instrftime
produces wrong output. strptime
Returnsdatetime
: Remember thatstrptime
always returns adatetime
object, even if your format string only includes date components (time will be 00:00:00). Use.date()
on the result if you only need the date part.timedelta
Limitations:timedelta
objects store durations in days, seconds, and microseconds. They don't inherently understand months or years due to their variable lengths. Addingtimedelta(days=365)
doesn't always land on the same date next year (because of leap years). Use libraries likedateutil
for more complex relative date calculations if needed.
Chapter Summary
- The
datetime
module provides classes for date/time manipulation:date
,time
,datetime
,timedelta
. - Create specific instances using constructors like
datetime.date(Y, M, D)
. - Get current local date/time with
date.today()
anddatetime.now()
. timedelta
represents durations and supports arithmetic withdate
anddatetime
objects.- Use
strftime('format_string')
to format date/time objects into strings using specific directives (%Y
,%m
,%d
,%H
,%M
,%S
, etc.). - Use
datetime.strptime(date_string, 'format_string')
to parse strings intodatetime
objects; the format string must match the input string exactly.
Exercises & Mini Projects
Exercises
- Current Time Formatting: Get the current
datetime
and format it to display in these formats:MM/DD/YY
(e.g., 04/12/25)Month Day, Year
(e.g., April 12, 2025)Day of week, HH:MM AM/PM
(e.g., Saturday, 06:13 PM)
- Date Arithmetic: Create a
date
object for July 4, 2025. Calculate and print the date 60 days before it and the date 8 weeks after it. - String Parsing: Parse the string "01-Jan-2023 15:45" into a
datetime
object usingstrptime
. Print the resulting object. - Time Difference: Create two
datetime
objects representing 9:00 AM and 5:30 PM on the same day. Calculate thetimedelta
between them and print the difference in hours and minutes (you'll need to access thetimedelta
's total seconds and calculate). - Simple Countdown: Ask the user to enter a future date (YYYY-MM-DD). Parse their input. Calculate the number of days from today until that future date and print the result. Handle potential
ValueError
if the input is invalid.
Mini Project: Event Scheduler Reminder
Goal: Create a simple script that defines events (as dictionaries or objects) with names and dates, and then calculates how many days remain until each event.
Steps:
Import datetime
.- Define Events: Create a list of dictionaries (or a list of custom
Event
objects). Each dictionary/object should have at least a 'name' (string) and a 'date_str' (string in 'YYYY-MM-DD' format).events = [ {'name': 'Project Deadline', 'date_str': '2025-06-15'}, {'name': 'Team Meeting', 'date_str': '2025-04-20'}, {'name': 'Holiday Party', 'date_str': '2025-12-20'}, {'name': 'Past Event', 'date_str': '2024-01-10'} ]
- Get Today's Date: Get the current date using
datetime.date.today()
. - Iterate and Calculate:
- Loop through your
events
list. - Inside the loop, for each event:
- Print the event name.
- Use
datetime.datetime.strptime(event['date_str'], '%Y-%m-%d').date()
to parse the event's date string into adate
object. Handle potentialValueError
for invalid date strings within the loop (e.g., print a warning and continue). - Calculate the difference between the event date and today's date (
event_date - today
). This gives atimedelta
. - Check if the difference (
timedelta.days
) is negative. If so, print that the event has passed. - If the difference is zero, print that the event is today.
- If the difference is positive, print the number of days remaining until the event (e.g., "Days remaining: [timedelta.days]").
- Add a blank line for readability between events.
- Loop through your
Additional Sources: