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, and timedelta.
  • Create date, time, and datetime objects representing specific points in time.
  • Get the current local date and time using date.today() and datetime.now().
  • Create timedelta objects representing durations or differences between dates/times.
  • Perform arithmetic operations with date, datetime, and timedelta objects.
  • Format date and time objects into strings using strftime().
  • Parse strings into datetime objects using strptime().

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 two date, time, or datetime 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.

Python
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}")

Conceptual structure of a datetime.datetime object.
datetime Object Structure A main rectangle labeled datetime object connected to smaller rectangles representing its attributes (year, month, day, hour, minute, second, microsecond). datetime Object 2024 .year 10 .month 31 .day 18 .hour 00 .minute 00 .second 0 .microsecond

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).
Python
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.

Python
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'>

Visualization of a timedelta representing the duration between two points in time.
Timedelta Concept A timeline with two points marked (dt1, dt2). A double-headed arrow labeled ‘timedelta’ spans the distance between them. Time dt1 (2024-04-10 10:00) dt2 (2024-04-12 12:30) timedelta = dt2 – dt1 (2 days, 2:30:00)

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
Python
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.)

Python
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:

Python
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

DateTime: Loading…
Format:
Output:

Example 1: Basic Date/Time Operations and Formatting

Python
# 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 and datetime objects for specific and current times.
  • Shows formatting using strftime with different directives.
  • Calculates the difference between two datetime objects, resulting in a timedelta.
  • Adds a timedelta to a datetime object to find a future time.

Example 2: Parsing and Calculating Age

Python
# 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 a datetime 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+'s zoneinfo for robust timezone support when needed.
  • strftime vs. strptime: Confusing the two functions – strftime formats a date/time object into a string, while strptime 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 a ValueError. Similarly, using incorrect directives in strftime produces wrong output.
  • strptime Returns datetime: Remember that strptime always returns a datetime 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. Adding timedelta(days=365) doesn't always land on the same date next year (because of leap years). Use libraries like dateutil 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() and datetime.now().
  • timedelta represents durations and supports arithmetic with date and datetime 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 into datetime objects; the format string must match the input string exactly.

Exercises & Mini Projects

Exercises

  1. 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)
  2. 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.
  3. String Parsing: Parse the string "01-Jan-2023 15:45" into a datetime object using strptime. Print the resulting object.
  4. Time Difference: Create two datetime objects representing 9:00 AM and 5:30 PM on the same day. Calculate the timedelta between them and print the difference in hours and minutes (you'll need to access the timedelta's total seconds and calculate).
  5. 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:

  1. Import datetime.
  2. 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'} ]
  3. Get Today's Date: Get the current date using datetime.date.today().
  4. 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 a date object. Handle potential ValueError 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 a timedelta.
      • 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.

Additional Sources:

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top