Week 3. The ops team's log timestamps are just strings right now — you can print them, but you can't do math on them. What happens when they ask "show me all errors from the last 72 hours"?
Right now I'd compare strings lexicographically — ISO timestamps sort correctly if they're in the same format. But if the format varies between services, string comparison breaks. And "72 hours ago" is not something I can express as a string filter.
That's exactly the problem datetime solves. The module turns timestamps into objects you can do arithmetic with — add durations, subtract moments, compare across formats. Here are the three classes you'll use:
from datetime import datetime, date, timedelta
# Current moment
now = datetime.utcnow()
print(now) # 2026-04-07 09:14:33.456789
# Specific moment from components
event = datetime(2026, 4, 7, 9, 14, 33)
print(event) # 2026-04-07 09:14:33
# Duration arithmetic
one_day = timedelta(days=1)
yesterday = now - one_day
print(yesterday) # 2026-04-06 09:14:33.456789So timedelta represents a duration — not a moment in time, but an interval. And subtracting one timedelta from a datetime gives me another datetime, 24 hours earlier.
And subtracting two datetime objects gives you a timedelta. That's how you measure elapsed time:
from datetime import datetime
start = datetime(2026, 4, 7, 9, 0, 0)
end = datetime(2026, 4, 7, 11, 30, 45)
elapsed = end - start
print(elapsed) # 2:30:45
print(elapsed.total_seconds()) # 9045.0.total_seconds() gives me the elapsed time as a single number. The ops team wants to flag requests that took more than 30 seconds from start log to end log. I can parse both timestamps, subtract, and check .total_seconds() > 30. That was a string-manipulation nightmare before.
Exactly. And for filtering by time window — "all errors in the last 72 hours" — the pattern is:
from datetime import datetime, timedelta
cutoff = datetime.utcnow() - timedelta(hours=72)
# Assuming log_entry["parsed_ts"] is a datetime object
recent_errors = [
entry for entry in log_entries
if entry["parsed_ts"] >= cutoff and entry["level"] == "ERROR"
]But the log entries have timestamps as strings — I need to get them from string to datetime first. Is that datetime.fromisoformat()?
datetime.fromisoformat() for ISO 8601 strings. For other formats you'd use datetime.strptime() — but that's tomorrow's lesson with the format codes. For now, fromisoformat() handles the clean ISO timestamps in the ops team's JSON logs:
from datetime import datetime
ts_string = "2026-04-07T09:14:33"
dt = datetime.fromisoformat(ts_string)
print(dt.year, dt.month, dt.day) # 2026 4 7
print(dt.hour, dt.minute) # 9 14And today's problem: parse a list of log entries with ISO timestamp strings, filter to those within a time window, compute duration statistics — min, max, mean elapsed time for a set of events. All datetime arithmetic.
The timedelta attributes you'll need: .days, .seconds (seconds portion, not total), .total_seconds() (the total as a float). For display, str(timedelta) gives "2:30:45" format. For arithmetic comparisons, total_seconds() is the right number.
I'm already seeing the full pipeline. Parse timestamp strings to datetime objects with fromisoformat(). Filter with >= cutoff comparisons. Compute elapsed time by subtraction. Return total_seconds() for the statistics. The datetime module handles the time math — my Python handles the filtering and aggregation logic.
You described the architecture before writing a line. Week 3 Maya is thinking like a data pipeline designer. Tomorrow: strptime and strftime — the format codes that let you parse any timestamp format, not just ISO 8601. The ops team's legacy logs have timestamps like 07/Apr/2026:09:14:33 +0000 and you'll need to handle those too.
A timestamp stored as a string is data you can display. A timestamp stored as a datetime object is data you can compute with — compare, subtract, add durations, format for output. The datetime module's core value is this transformation: string in, computable object out.
datetime.date represents a calendar date (year, month, day) with no time component. datetime.time represents a time of day (hour, minute, second, microsecond, timezone) with no date component. datetime.datetime represents both, and is the class you'll use 90% of the time for log analysis. datetime.timedelta represents a duration — an amount of time, not a point in time.
datetime - datetime = timedelta (elapsed time between two moments).
datetime + timedelta = datetime (moment shifted forward by a duration).
datetime - timedelta = datetime (moment shifted backward by a duration).
timedelta + timedelta = timedelta (sum of two durations).
These operations raise TypeError if you mix types — you cannot add two datetime objects.
A timedelta stores its value internally as days, seconds, and microseconds. .days and .seconds give those components individually — .seconds is the seconds within the current day, not the total. .total_seconds() is almost always what you want: the entire duration expressed as a floating-point number of seconds. For formatting, str(td) gives "D day, H:MM:SS" or "H:MM:SS" for sub-day durations.
datetime.utcnow() returns a naive datetime — no timezone information attached. datetime.now(timezone.utc) returns an aware datetime with UTC timezone. Naive and aware datetimes cannot be compared or subtracted — Python raises TypeError. For log analysis where all timestamps are in UTC (as they should be), naive datetimes are simpler. When log files mix timezones — which happens when multiple data centers contribute — you need aware datetimes and the zoneinfo module (Python 3.9+).
datetime.fromisoformat(s) parses ISO 8601 format strings without a format code. It handles 2026-04-07, 2026-04-07T09:14:33, and 2026-04-07 09:14:33. In Python 3.11+, it handles the full ISO 8601 spec including timezone offsets. datetime.strptime(s, fmt) is the general parser for any format — covered in the next lesson.
Sign up to write and run code in this lesson.
Week 3. The ops team's log timestamps are just strings right now — you can print them, but you can't do math on them. What happens when they ask "show me all errors from the last 72 hours"?
Right now I'd compare strings lexicographically — ISO timestamps sort correctly if they're in the same format. But if the format varies between services, string comparison breaks. And "72 hours ago" is not something I can express as a string filter.
That's exactly the problem datetime solves. The module turns timestamps into objects you can do arithmetic with — add durations, subtract moments, compare across formats. Here are the three classes you'll use:
from datetime import datetime, date, timedelta
# Current moment
now = datetime.utcnow()
print(now) # 2026-04-07 09:14:33.456789
# Specific moment from components
event = datetime(2026, 4, 7, 9, 14, 33)
print(event) # 2026-04-07 09:14:33
# Duration arithmetic
one_day = timedelta(days=1)
yesterday = now - one_day
print(yesterday) # 2026-04-06 09:14:33.456789So timedelta represents a duration — not a moment in time, but an interval. And subtracting one timedelta from a datetime gives me another datetime, 24 hours earlier.
And subtracting two datetime objects gives you a timedelta. That's how you measure elapsed time:
from datetime import datetime
start = datetime(2026, 4, 7, 9, 0, 0)
end = datetime(2026, 4, 7, 11, 30, 45)
elapsed = end - start
print(elapsed) # 2:30:45
print(elapsed.total_seconds()) # 9045.0.total_seconds() gives me the elapsed time as a single number. The ops team wants to flag requests that took more than 30 seconds from start log to end log. I can parse both timestamps, subtract, and check .total_seconds() > 30. That was a string-manipulation nightmare before.
Exactly. And for filtering by time window — "all errors in the last 72 hours" — the pattern is:
from datetime import datetime, timedelta
cutoff = datetime.utcnow() - timedelta(hours=72)
# Assuming log_entry["parsed_ts"] is a datetime object
recent_errors = [
entry for entry in log_entries
if entry["parsed_ts"] >= cutoff and entry["level"] == "ERROR"
]But the log entries have timestamps as strings — I need to get them from string to datetime first. Is that datetime.fromisoformat()?
datetime.fromisoformat() for ISO 8601 strings. For other formats you'd use datetime.strptime() — but that's tomorrow's lesson with the format codes. For now, fromisoformat() handles the clean ISO timestamps in the ops team's JSON logs:
from datetime import datetime
ts_string = "2026-04-07T09:14:33"
dt = datetime.fromisoformat(ts_string)
print(dt.year, dt.month, dt.day) # 2026 4 7
print(dt.hour, dt.minute) # 9 14And today's problem: parse a list of log entries with ISO timestamp strings, filter to those within a time window, compute duration statistics — min, max, mean elapsed time for a set of events. All datetime arithmetic.
The timedelta attributes you'll need: .days, .seconds (seconds portion, not total), .total_seconds() (the total as a float). For display, str(timedelta) gives "2:30:45" format. For arithmetic comparisons, total_seconds() is the right number.
I'm already seeing the full pipeline. Parse timestamp strings to datetime objects with fromisoformat(). Filter with >= cutoff comparisons. Compute elapsed time by subtraction. Return total_seconds() for the statistics. The datetime module handles the time math — my Python handles the filtering and aggregation logic.
You described the architecture before writing a line. Week 3 Maya is thinking like a data pipeline designer. Tomorrow: strptime and strftime — the format codes that let you parse any timestamp format, not just ISO 8601. The ops team's legacy logs have timestamps like 07/Apr/2026:09:14:33 +0000 and you'll need to handle those too.
A timestamp stored as a string is data you can display. A timestamp stored as a datetime object is data you can compute with — compare, subtract, add durations, format for output. The datetime module's core value is this transformation: string in, computable object out.
datetime.date represents a calendar date (year, month, day) with no time component. datetime.time represents a time of day (hour, minute, second, microsecond, timezone) with no date component. datetime.datetime represents both, and is the class you'll use 90% of the time for log analysis. datetime.timedelta represents a duration — an amount of time, not a point in time.
datetime - datetime = timedelta (elapsed time between two moments).
datetime + timedelta = datetime (moment shifted forward by a duration).
datetime - timedelta = datetime (moment shifted backward by a duration).
timedelta + timedelta = timedelta (sum of two durations).
These operations raise TypeError if you mix types — you cannot add two datetime objects.
A timedelta stores its value internally as days, seconds, and microseconds. .days and .seconds give those components individually — .seconds is the seconds within the current day, not the total. .total_seconds() is almost always what you want: the entire duration expressed as a floating-point number of seconds. For formatting, str(td) gives "D day, H:MM:SS" or "H:MM:SS" for sub-day durations.
datetime.utcnow() returns a naive datetime — no timezone information attached. datetime.now(timezone.utc) returns an aware datetime with UTC timezone. Naive and aware datetimes cannot be compared or subtracted — Python raises TypeError. For log analysis where all timestamps are in UTC (as they should be), naive datetimes are simpler. When log files mix timezones — which happens when multiple data centers contribute — you need aware datetimes and the zoneinfo module (Python 3.9+).
datetime.fromisoformat(s) parses ISO 8601 format strings without a format code. It handles 2026-04-07, 2026-04-07T09:14:33, and 2026-04-07 09:14:33. In Python 3.11+, it handles the full ISO 8601 spec including timezone offsets. datetime.strptime(s, fmt) is the general parser for any format — covered in the next lesson.