Skip to Content
Course content

217: The datetime Module in Depth

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was reviewing a PR for a colleague who had built a "daily" cleanup script. He used datetime.now() to check if the current date matched the scheduled cleanup date. It worked perfectly in development. Then we deployed it to a cluster of servers across three different AWS regions. Suddenly, the script was running twice for some users and not at all for others because the servers were set to UTC, but the logic was implicitly assuming the local time of the developer's laptop in New York. He spent an entire weekend chasing a "ghost" bug that turned out to be the classic mistake of using naive datetime objects.

If you've ever felt like time is a lie in programming, you're not alone. The datetime module is powerful, but it has some sharp edges that will cut you if you aren't careful. Most people know how to get the current time, but to actually master this module, you need to understand the distinction between "naive" and "aware" objects.

The Naive vs. Aware Divide

In Python, a naive object is one that doesn't contain any timezone information. It's just a set of numbers (year, month, day, etc.). The problem is that Python doesn't know if that time is UTC, EST, or Martian Standard Time—it just assumes it's "whatever the system says." An aware object, on the other hand, includes a tzinfo object that anchors it to a specific point in global time.

I always recommend sticking to UTC internally for everything. Store your data in UTC, do your math in UTC, and only convert to a local timezone at the very last second when you're displaying a string to a human. Since Python 3.9, the zoneinfo module has become the gold standard for this. Here is how you actually handle a timezone-aware object:

from datetime import datetime, timezone
from zoneinfo import ZoneInfo

# The wrong way (Naive)
naive_now = datetime.now() 

# The right way (Aware - UTC)
utc_now = datetime.now(timezone.utc)

# Converting UTC to a specific local time
tokyo_time = utc_now.astimezone(ZoneInfo("Asia/Tokyo"))

print(f"UTC: {utc_now}")
print(f"Tokyo: {tokyo_time}")

Slicing Time with Timedelta

You'll rarely just want the current time; usually, you want to know when something expires or how long ago a user logged in. This is where timedelta comes in. Think of a datetime object as a point on a map and a timedelta as the distance between two points.

One thing that trips people up is that you can't just add "one month" to a date using timedelta because months vary in length. timedelta handles days, seconds, and microseconds. If you need to do complex calendar math (like "the last Friday of next month"), you'll want to look into the dateutil library, but for 90% of your tasks, timedelta is your best friend.

from datetime import datetime, timedelta

# Calculate a trial expiration date (14 days from now)
start_date = datetime.now(timezone.utc)
expiry_date = start_date + timedelta(days=14)

# Find out exactly how much time is left
time_remaining = expiry_date - start_date
print(f"Seconds until expiration: {time_remaining.total_seconds()}")

Parsing the Chaos of Date Strings

At some point, you're going to have to deal with a CSV or an API that sends you dates as strings. This is where strptime (string-parse time) and strftime (string-format time) come in. I remember a trick to keep these straight: p is for parse (string to object), and f is for format (object to string).

The format codes (like %Y for a four-digit year) feel like a secret language, but you'll memorize the common ones quickly. Just be wary of %y (lowercase), which only gives you the last two digits of the year—a recipe for Y2K-style disasters if you're not careful.

# Parsing a log timestamp: "2023-10-25 14:30:05"
log_string = "2023-10-25 14:30:05"
dt_object = datetime.strptime(log_string, "%Y-%m-%d %H:%M:%S")

# Formatting it for a friendly user UI: "Oct 25, 2023"
friendly_date = dt_object.strftime("%b %d, %Y")
print(friendly_date) # Output: Oct 25, 2023



📋 Practical Task

Exercise: Global Webinar Countdown Timer

You are building a notification system for a global webinar. The webinar starts at a fixed UTC time, but you need to display the countdown and the local start time for users in different cities.

Your Task: Write a script that does the following:

  • Define a webinar start time for December 1st, 2025, at 15:00 (3 PM) UTC. Ensure this object is timezone-aware.
  • Create a list of cities and their corresponding IANA timezone strings (e.g., {"New York": "America/New_York", "London": "Europe/London", "Sydney": "Australia/Sydney"}).
  • For each city, calculate and print the local start time of the webinar in a readable format (e.g., "New York: 2025-12-01 10:00:00").
  • Calculate the total number of days and hours remaining from the moment the script is run until the webinar starts.

Hint: Use zoneinfo.ZoneInfo for the timezones and datetime.now(timezone.utc) to get the current time for your calculations.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.