Lesson 12 - Working with Dates, Times, and Timezones

Introduction

Working with dates and times is essential for many applications—from recording when a book was published to tracking order timestamps to scheduling tasks. Python’s datetime module provides powerful tools for handling dates, times, and time intervals.

In this lesson, we’ll learn:

  • Creating date and time objects
  • Formatting dates and times for display
  • Parsing date strings
  • Working with time deltas (intervals)
  • Timezone-aware datetime objects
  • Practical examples for real-world applications

Basic Date and Time Objects

Python’s datetime module provides several classes:

  • datetime: Date and time together
  • date: Date only (year, month, day)
  • time: Time only (hour, minute, second, microsecond)
  • timedelta: Duration or difference between dates/times

Creating Date Objects

from datetime import date, datetime

# Current date
today = date.today()
print(f"Today: {today}")  # 2025-11-11

# Specific date
book_published = date(2019, 7, 23)
print(f"Book published: {book_published}")  # 2019-07-23

# Access components
print(f"Year: {book_published.year}")
print(f"Month: {book_published.month}")
print(f"Day: {book_published.day}")

Creating DateTime Objects

from datetime import datetime

# Current date and time
now = datetime.now()
print(f"Now: {now}")  # 2025-11-11 14:30:45.123456

# Specific datetime
order_time = datetime(2024, 11, 11, 14, 30, 0)
print(f"Order placed: {order_time}")  # 2024-11-11 14:30:00

# Today at midnight
today_midnight = datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)
print(f"Today at midnight: {today_midnight}")

Formatting Dates and Times

Use strftime() (string format time) to format dates as strings:

from datetime import datetime

now = datetime.now()

# Common formats
print(now.strftime("%Y-%m-%d"))  # 2025-11-11
print(now.strftime("%d/%m/%Y"))  # 11/11/2025
print(now.strftime("%B %d, %Y"))  # November 11, 2025
print(now.strftime("%A, %B %d, %Y"))  # Tuesday, November 11, 2025
print(now.strftime("%I:%M %p"))  # 02:30 PM
print(now.strftime("%Y-%m-%d %H:%M:%S"))  # 2025-11-11 14:30:45

Common format codes:

  • %Y - 4-digit year (2025)
  • %m - 2-digit month (01-12)
  • %d - 2-digit day (01-31)
  • %H - Hour 24-hour (00-23)
  • %I - Hour 12-hour (01-12)
  • %M - Minute (00-59)
  • %S - Second (00-59)
  • %p - AM/PM
  • %A - Full weekday name
  • %B - Full month name

Practical Example: Book Publication Dates

from datetime import datetime

books = [
    {'title': 'Python Crash Course', 'published': datetime(2019, 7, 23)},
    {'title': 'Automate the Boring Stuff', 'published': datetime(2020, 1, 15)},
    {'title': 'Learning Python', 'published': datetime(2013, 6, 12)},
]

print("Book Publication Dates:")
for book in books:
    formatted = book['published'].strftime("%B %d, %Y")
    print(f"  '{book['title']}' - Published: {formatted}")

Output:

Book Publication Dates:
  'Python Crash Course' - Published: July 23, 2019
  'Automate the Boring Stuff' - Published: January 15, 2020
  'Learning Python' - Published: June 12, 2013

Parsing Date Strings

Use strptime() (string parse time) to parse strings into datetime objects:

from datetime import datetime

# Parse various date formats
date_strings = [
    ("2024-11-11", "%Y-%m-%d"),
    ("11/11/2024", "%m/%d/%Y"),
    ("November 11, 2024", "%B %d, %Y"),
    ("11-Nov-2024 14:30:00", "%d-%b-%Y %H:%M:%S"),
]

for date_str, format_str in date_strings:
    parsed = datetime.strptime(date_str, format_str)
    print(f"{date_str:25}{parsed}")

Output:

2024-11-11                → 2024-11-11 00:00:00
11/11/2024                → 2024-11-11 00:00:00
November 11, 2024         → 2024-11-11 00:00:00
11-Nov-2024 14:30:00      → 2024-11-11 14:30:00

Practical Example: Processing Order Data

from datetime import datetime

order_data = [
    "2024-11-10 09:15:30,Python Basics,29.99",
    "2024-11-10 14:22:45,Python Advanced,49.99",
    "2024-11-11 10:05:12,Data Science 101,39.99",
]

orders = []
for line in order_data:
    timestamp_str, title, price = line.split(',')
    timestamp = datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S")

    orders.append({
        'timestamp': timestamp,
        'title': title,
        'price': float(price)
    })

# Display orders
print("Orders:")
for order in orders:
    time_formatted = order['timestamp'].strftime("%b %d at %I:%M %p")
    print(f"  {time_formatted}: {order['title']} (${order['price']})")

Output:

Orders:
  Nov 10 at 09:15 AM: Python Basics ($29.99)
  Nov 10 at 02:22 PM: Python Advanced ($49.99)
  Nov 11 at 10:05 AM: Data Science 101 ($39.99)

Time Deltas: Working with Durations

timedelta represents a duration—the difference between two dates/times:

from datetime import datetime, timedelta

now = datetime.now()

# Create timedeltas
one_day = timedelta(days=1)
one_week = timedelta(weeks=1)
one_hour = timedelta(hours=1)
two_days_three_hours = timedelta(days=2, hours=3, minutes=30)

# Add/subtract timedeltas
tomorrow = now + one_day
yesterday = now - one_day
next_week = now + one_week

print(f"Now: {now.strftime('%Y-%m-%d %H:%M')}")
print(f"Tomorrow: {tomorrow.strftime('%Y-%m-%d %H:%M')}")
print(f"Yesterday: {yesterday.strftime('%Y-%m-%d %H:%M')}")
print(f"Next week: {next_week.strftime('%Y-%m-%d %H:%M')}")

Calculating Differences

from datetime import datetime

book_published = datetime(2019, 7, 23)
now = datetime.now()

age = now - book_published

print(f"Book was published {age.days} days ago")
print(f"That's approximately {age.days // 365} years")

Practical Example: Subscription Expiration

from datetime import datetime, timedelta

class Subscription:
    def __init__(self, user, start_date, duration_days=30):
        self.user = user
        self.start_date = start_date
        self.end_date = start_date + timedelta(days=duration_days)

    def is_active(self):
        return datetime.now() < self.end_date

    def days_remaining(self):
        remaining = self.end_date - datetime.now()
        return max(0, remaining.days)

    def display_status(self):
        if self.is_active():
            days = self.days_remaining()
            print(f"{self.user}: Active ({days} days remaining)")
            print(f"  Expires: {self.end_date.strftime('%B %d, %Y')}")
        else:
            print(f"{self.user}: Expired")
            print(f"  Expired on: {self.end_date.strftime('%B %d, %Y')}")

# Create subscriptions
sub1 = Subscription("Alice", datetime.now() - timedelta(days=5), duration_days=30)
sub2 = Subscription("Bob", datetime.now() - timedelta(days=35), duration_days=30)

sub1.display_status()
print()
sub2.display_status()

Output:

Alice: Active (25 days remaining)
  Expires: December 06, 2025

Bob: Expired
  Expired on: October 07, 2025

Working with Timezones

By default, datetime objects are “naive” (no timezone info). For timezone-aware datetimes, use pytz or Python 3.9+ zoneinfo:

from datetime import datetime
from zoneinfo import ZoneInfo

# Create timezone-aware datetime
utc_now = datetime.now(ZoneInfo("UTC"))
eastern = datetime.now(ZoneInfo("America/New_York"))
tokyo = datetime.now(ZoneInfo("Asia/Tokyo"))

print(f"UTC:     {utc_now.strftime('%Y-%m-%d %H:%M:%S %Z')}")
print(f"Eastern: {eastern.strftime('%Y-%m-%d %H:%M:%S %Z')}")
print(f"Tokyo:   {tokyo.strftime('%Y-%m-%d %H:%M:%S %Z')}")

Converting Between Timezones

from datetime import datetime
from zoneinfo import ZoneInfo

# Create a datetime in one timezone
eastern_time = datetime(2024, 11, 11, 14, 30, tzinfo=ZoneInfo("America/New_York"))

# Convert to other timezones
utc_time = eastern_time.astimezone(ZoneInfo("UTC"))
tokyo_time = eastern_time.astimezone(ZoneInfo("Asia/Tokyo"))

print(f"Eastern: {eastern_time.strftime('%I:%M %p %Z')}")
print(f"UTC:     {utc_time.strftime('%I:%M %p %Z')}")
print(f"Tokyo:   {tokyo_time.strftime('%I:%M %p %Z')}")

Practical Example: Book Order System with Timestamps

from datetime import datetime, timedelta
from zoneinfo import ZoneInfo

class BookOrder:
    def __init__(self, order_id, book_title, customer, timezone="UTC"):
        self.order_id = order_id
        self.book_title = book_title
        self.customer = customer
        self.timezone = ZoneInfo(timezone)
        self.order_time = datetime.now(self.timezone)
        self.estimated_delivery = self.order_time + timedelta(days=5)

    def display_order(self):
        print(f"\nOrder #{self.order_id}")
        print(f"  Book: {self.book_title}")
        print(f"  Customer: {self.customer}")
        print(f"  Ordered: {self.order_time.strftime('%B %d, %Y at %I:%M %p %Z')}")
        print(f"  Est. Delivery: {self.estimated_delivery.strftime('%B %d, %Y')}")

    def time_since_order(self):
        now = datetime.now(self.timezone)
        elapsed = now - self.order_time

        hours = elapsed.total_seconds() / 3600
        if hours < 1:
            minutes = elapsed.total_seconds() / 60
            return f"{int(minutes)} minutes ago"
        elif hours < 24:
            return f"{int(hours)} hours ago"
        else:
            return f"{elapsed.days} days ago"

# Create orders in different timezones
order1 = BookOrder(1001, "Python Crash Course", "Alice", "America/New_York")
order2 = BookOrder(1002, "Data Science 101", "Bob", "Europe/London")
order3 = BookOrder(1003, "Machine Learning", "Charlie", "Asia/Tokyo")

for order in [order1, order2, order3]:
    order.display_order()
    print(f"  Placed: {order.time_since_order()}")

Comparing and Sorting Dates

from datetime import datetime

books = [
    {'title': 'Python Crash Course', 'published': datetime(2019, 7, 23)},
    {'title': 'Automate the Boring Stuff', 'published': datetime(2020, 1, 15)},
    {'title': 'Learning Python', 'published': datetime(2013, 6, 12)},
    {'title': 'Data Science Handbook', 'published': datetime(2016, 11, 25)},
]

# Sort by publication date (oldest first)
sorted_books = sorted(books, key=lambda x: x['published'])

print("Books by publication date (oldest first):")
for book in sorted_books:
    pub_date = book['published'].strftime("%B %Y")
    print(f"  {pub_date}: {book['title']}")

# Find most recent book
newest = max(books, key=lambda x: x['published'])
print(f"\nNewest book: {newest['title']} ({newest['published'].year})")

# Filter books from last 5 years
five_years_ago = datetime.now() - timedelta(days=365*5)
recent_books = [b for b in books if b['published'] > five_years_ago]

print(f"\nBooks from last 5 years:")
for book in recent_books:
    print(f"  {book['title']} ({book['published'].year})")

Output:

Books by publication date (oldest first):
  June 2013: Learning Python
  November 2016: Data Science Handbook
  July 2019: Python Crash Course
  January 2020: Automate the Boring Stuff

Newest book: Automate the Boring Stuff (2020)

Books from last 5 years:
  Python Crash Course (2019)
  Automate the Boring Stuff (2020)

Working with Weekdays

from datetime import datetime, timedelta

def next_business_day(start_date):
    """Get next business day (Monday-Friday)"""
    next_day = start_date + timedelta(days=1)

    # If weekend (5=Saturday, 6=Sunday), skip to Monday
    while next_day.weekday() >= 5:
        next_day += timedelta(days=1)

    return next_day

# Test it
today = datetime.now()
next_bd = next_business_day(today)

print(f"Today: {today.strftime('%A, %B %d')}")
print(f"Next business day: {next_bd.strftime('%A, %B %d')}")

# Count business days in a range
def count_business_days(start_date, end_date):
    """Count business days between two dates"""
    count = 0
    current = start_date

    while current <= end_date:
        if current.weekday() < 5:  # Monday-Friday
            count += 1
        current += timedelta(days=1)

    return count

start = datetime(2024, 11, 1)
end = datetime(2024, 11, 30)
biz_days = count_business_days(start, end)

print(f"\nBusiness days in November 2024: {biz_days}")

Comprehensive Example: Book Analytics Dashboard

from datetime import datetime, timedelta
from collections import Counter

# Sample sales data with timestamps
sales_data = [
    ("2024-11-01 10:30:00", "Python Basics", 29.99),
    ("2024-11-01 14:15:00", "Python Advanced", 49.99),
    ("2024-11-02 09:45:00", "Python Basics", 29.99),
    ("2024-11-02 16:20:00", "Data Science 101", 39.99),
    ("2024-11-03 11:10:00", "Python Basics", 29.99),
    ("2024-11-05 13:30:00", "Machine Learning", 59.99),
    ("2024-11-05 15:45:00", "Python Advanced", 49.99),
    ("2024-11-06 10:00:00", "Data Science 101", 39.99),
]

# Parse sales data
sales = []
for timestamp_str, title, price in sales_data:
    timestamp = datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S")
    sales.append({
        'timestamp': timestamp,
        'title': title,
        'price': price
    })

# Analytics
print("=== BOOK SALES ANALYTICS ===\n")

# 1. Total sales
total_revenue = sum(sale['price'] for sale in sales)
print(f"Total Revenue: ${total_revenue:.2f}")
print(f"Total Transactions: {len(sales)}\n")

# 2. Sales by day
sales_by_day = {}
for sale in sales:
    day = sale['timestamp'].date()
    sales_by_day[day] = sales_by_day.get(day, 0) + sale['price']

print("Sales by Day:")
for day in sorted(sales_by_day.keys()):
    revenue = sales_by_day[day]
    day_name = datetime.combine(day, datetime.min.time()).strftime("%A, %B %d")
    print(f"  {day_name}: ${revenue:.2f}")

# 3. Peak sales time
sales_by_hour = Counter(sale['timestamp'].hour for sale in sales)
peak_hour = sales_by_hour.most_common(1)[0][0]
print(f"\nPeak Sales Hour: {peak_hour}:00 - {peak_hour+1}:00")

# 4. Best-selling book
book_sales = Counter(sale['title'] for sale in sales)
best_seller = book_sales.most_common(1)[0]
print(f"Best Seller: {best_seller[0]} ({best_seller[1]} copies)")

# 5. Time since last sale
last_sale = max(sales, key=lambda x: x['timestamp'])
now = datetime.now()
time_since = now - last_sale['timestamp']

print(f"\nLast Sale: {last_sale['title']}")
print(f"  Time: {last_sale['timestamp'].strftime('%B %d at %I:%M %p')}")
print(f"  ({time_since.days} days, {time_since.seconds // 3600} hours ago)")

Output:

=== BOOK SALES ANALYTICS ===

Total Revenue: $328.92
Total Transactions: 8

Sales by Day:
  Friday, November 01: $79.98
  Saturday, November 02: $69.98
  Sunday, November 03: $29.99
  Tuesday, November 05: $109.98
  Wednesday, November 06: $39.99

Peak Sales Hour: 10:00 - 11:00
Best Seller: Python Basics (3 copies)

Last Sale: Data Science 101
  Time: November 06 at 10:00 AM
  (5 days, 4 hours ago)

Summary

In this lesson, we learned how to work with dates and times in Python:

  • date and datetime objects for representing dates and times
  • strftime() for formatting dates as strings
  • strptime() for parsing strings into datetime objects
  • timedelta for representing durations and calculating differences
  • Timezone-aware datetimes using zoneinfo
  • Comparing, sorting, and filtering by dates
  • Working with weekdays and business days

Common use cases:

  • Recording timestamps
  • Formatting dates for display
  • Parsing dates from various sources
  • Calculating durations and deadlines
  • Timezone conversions
  • Filtering data by date ranges
  • Scheduling and calendar operations

Best practices:

  • Use timezone-aware datetimes for applications with users in multiple timezones
  • Store datetimes in UTC, convert to local timezone for display
  • Use ISO format (YYYY-MM-DD) for consistency
  • Be careful with date arithmetic (months have different lengths)
  • Consider using libraries like python-dateutil for complex date parsing

Congratulations!

You’ve completed the Python Advanced module! You now have a solid foundation in:

  1. Object-oriented programming (classes, inheritance, polymorphism)
  2. Advanced function concepts (closures, decorators, type hints)
  3. Iterators and generators
  4. Context managers
  5. Regular expressions
  6. Advanced collections
  7. Date and time handling

These skills will help you write more sophisticated, efficient, and maintainable Python code. Keep practicing and building projects to reinforce what you’ve learned!