Python · 07/10 6 min
Time and numbers in Python
Seconds vs datetimes, naive vs aware, timedelta math, Decimal money and banker's rounding.
Clocks
| Call | Gives | Use for |
|---|---|---|
time.time() |
float seconds since epoch | timestamps |
time.monotonic() |
float seconds, never jumps back | measuring intervals, rate limiters |
datetime.now(timezone.utc) |
aware datetime | storing and comparing times |
Rate limiters and TTL caches should use monotonic() (or an injected clock, so tests can control it). A wall clock can jump when the system time changes.
Naive vs aware datetimes
from datetime import datetime, timezone, timedelta
naive = datetime(2024, 3, 1, 9, 0) # no tzinfo
aware = datetime(2024, 3, 1, 9, 0, tzinfo=timezone.utc)
naive < aware # TypeError
Pick aware UTC everywhere internally; convert at the edges. datetime.utcnow() returns a naive value (and is deprecated in 3.12).
Intervals
def overlaps(a_start, a_end, b_start, b_end):
return a_start < b_end and b_start < a_end # half-open: touching is not overlapping
Meeting at 10:00–11:00 and 11:00–12:00 don't overlap. Using <= books them as a clash.
timedelta.seconds is only the seconds part (0–86399). Use td.total_seconds().
Integers and floats
7 / 2 # 3.5 true division always returns float
7 // 2 # 3 floor division
-7 // 2 # -4 rounds toward minus infinity
0.1 + 0.2 == 0.3 # False → math.isclose(a, b)
Money: Decimal
from decimal import Decimal, ROUND_HALF_UP
Decimal("0.1") + Decimal("0.2") == Decimal("0.3") # True
Decimal(0.1) # 0.1000000000000000055511151231257827…
total.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
Always build Decimal from strings (or ints), never floats.
round() is banker's rounding
round(2.5) # 2
round(3.5) # 4
round(0.125, 2) # 0.12 (binary float, and ties go to even)
If the spec says "round half up", use Decimal.quantize with ROUND_HALF_UP.
Practise it now
Next in Python
Reading Python test failures