05 / BUILDING USEFUL TOOLS · LESSON 17

Dates with a timezone

Calculate elapsed hours between timezone-aware timestamps.

13 min · learn at your pace

A timestamp needs context

datetime.fromisoformat() parses ISO-formatted text into a datetime. A timestamp with Z or a numeric offset identifies an instant; one without an offset is naive. Mixing naive and aware values can fail or make a report ambiguous.

Use datetime.now(timezone.utc) when you need the current instant in your own tool. Tests should supply a fixed reference time so yesterday’s correct solution still passes tomorrow. This course therefore passes reference timestamps explicitly.

Subtract instants, not clock labels

Subtracting aware datetimes accounts for their UTC offsets and returns a timedelta. Use total_seconds() to include the entire duration. Its seconds attribute is only the seconds portion after whole days are removed, so it is wrong for multi-day age calculations.

Divide by 3600 to get hours. A future timestamp produces a negative age; that can be useful evidence of clock skew and should not be silently turned into zero. Reject naive inputs clearly instead of guessing their timezone. Your browser’s local timezone should not change the result.

Try an example.py
from datetime import datetime
start = datetime.fromisoformat("2026-01-01T08:00:00+10:00")
end = datetime.fromisoformat("2026-01-01T00:00:00Z")
print((end - start).total_seconds() / 3600)
YOUR TURN

Put it into practice.

Define age_hours(timestamp, reference). Parse both ISO timestamps, require timezone information, and return (reference - timestamp) in hours. Accept different UTC offsets and negative ages. Raise ValueError for naive timestamps.

See an explained solution
One way to solve it.py
from datetime import datetime
def age_hours(timestamp, reference):
    start = datetime.fromisoformat(timestamp)
    end = datetime.fromisoformat(reference)
    if start.utcoffset() is None or end.utcoffset() is None:
        raise ValueError("Both timestamps need a timezone")
    return (end - start).total_seconds() / 3600

Timezone-aware subtraction compares instants correctly. A fixed reference makes tests deterministic, and total_seconds preserves both long durations and negative results.