APIs, databases and file systems often exchange times as seconds since the epoch.
A Unix timestamp counts seconds since 1970-01-01 00:00:00 UTC (the epoch). It's time-zone independent, which makes it a compact interchange format.
Conversions:
dt.timestamp()turns an aware datetime into a float timestamp. On a naive datetime it assumes local time, a frequent source of hour-sized bugs.datetime.fromtimestamp(ts, tz=UTC)builds an aware datetime. Withouttzit returns a naive local time.time.time()returns the current timestamp directly;time.time_ns()returns integer nanoseconds.
JavaScript and many APIs use milliseconds: divide by 1000 before fromtimestamp. A value around 1.7 billion is seconds; around 1.7 trillion is milliseconds.
Which clock to use:
time.time()/datetime.now(UTC): wall-clock time. It can jump backwards or forwards when NTP or a user adjusts the clock.time.monotonic(): never goes backwards; use it for timeouts and intervals.time.perf_counter(): monotonic with the highest resolution; use it for benchmarking.
Python's datetime has no leap seconds: like Unix time, it assumes every day has exactly 86,400 seconds.
Example
import time
from datetime import UTC, datetime
dt = datetime(2025, 3, 1, 12, 0, tzinfo=UTC)
ts = dt.timestamp()
print(ts) # 1740830400.0
print(datetime.fromtimestamp(ts, tz=UTC)) # 2025-03-01 12:00:00+00:00
js_ms = 1740830400123 # milliseconds from a JS client
print(datetime.fromtimestamp(js_ms / 1000, tz=UTC))
# 2025-03-01 12:00:00.123000+00:00
start = time.monotonic()
time.sleep(0.05)
elapsed = time.monotonic() - start
print(0.04 < elapsed < 1) # True
print(isinstance(time.time_ns(), int)) # TrueRemember
- Timestamps are seconds since 1970-01-01 UTC, independent of zones
- Always pass
tz=UTCtofromtimestampand calltimestamp()on aware values - Use
monotonicorperf_counterfor durations, not wall-clock time
Watch out for
- Feeding millisecond timestamps to
fromtimestamp, producing dates tens of thousands of years ahead (or an error).