Unixdates

Guides · 8 min read

Unix timestamps in Python: a practical guide

Python has three different time APIs and they all do slightly different things. Here is when to use time.time, when to use datetime, when to use Arrow or Pendulum, and the pitfalls in between.

Published 22 April 2026

Python’s date and time story is the result of about 25 years of evolution. The standard library has time, datetime, calendar, and zoneinfo, plus more in third-party land. They overlap, they sometimes contradict each other, and the canonical advice has shifted twice — once for pytz, and once for zoneinfo in Python 3.9.

This guide is the modern, opinionated take: what to use today, what to skip, and how to deal with Unix timestamps without writing bugs.

Getting the current Unix timestamp

The shortest version is time.time(), which returns a float of seconds since the epoch:

import time
seconds = time.time()
# 1745301600.847123

time.time() returns sub-second precision in a 64-bit float, but the trailing digits aren’t always meaningful — different platforms have different clock resolutions. Don’t rely on it as a microsecond clock; use time.time_ns() (added in 3.7) if you want nanoseconds in an integer:

nanos = time.time_ns()       # int, nanoseconds since 1970
millis = time.time_ns() // 1_000_000

If you only want integer seconds:

seconds = int(time.time())

datetime — the modern way

For most application code, datetime is what you want. It is calendar-aware, timezone-aware (since Python 3.9 with zoneinfo), and produces nice ISO strings.

from datetime import datetime, timezone

now = datetime.now(timezone.utc)            # timezone-aware UTC
print(now.timestamp())                      # 1745301600.847123 — seconds since epoch
print(now.isoformat())                      # "2026-04-22T08:00:00.847123+00:00"

Always pass tz=timezone.utc (or another zone) explicitly. A “naive” datetime — one without a timezone — is a bug magnet. .timestamp() on a naive datetime assumes local time, which means the same code produces different results on different machines.

# Avoid: naive datetime.
naive = datetime.now()                      # local time, no tzinfo
naive.timestamp()                           # depends on the host's TZ env var

# Prefer: timezone-aware.
aware = datetime.now(timezone.utc)
aware.timestamp()                           # always the right number

Converting a Unix timestamp to a datetime

from datetime import datetime, timezone

ts = 1745301600
d = datetime.fromtimestamp(ts, tz=timezone.utc)
# datetime(2026, 4, 22, 8, 0, 0, tzinfo=timezone.utc)

Two important details:

  • datetime.fromtimestamp(ts) without a tz argument returns a naive datetime in local time. Almost always wrong.
  • datetime.utcfromtimestamp(ts) returns a naive datetime in UTC. Less wrong, but still naive — don’t use it. It’s deprecated in 3.12.

For milliseconds, divide before converting:

ms = 1745301600847
d = datetime.fromtimestamp(ms / 1000, tz=timezone.utc)

If you have a 13-digit number, it is almost certainly milliseconds. There’s a whole guide on telling the units apart.

Working with timezones (zoneinfo)

Python 3.9 added zoneinfo, which uses the IANA timezone database that ships with your operating system. It replaced the old pytz advice and is now the canonical way to do timezones.

from datetime import datetime
from zoneinfo import ZoneInfo

oslo = ZoneInfo("Europe/Oslo")
now = datetime.now(oslo)
print(now)                          # 2026-04-22 10:00:00+02:00
print(now.utcoffset())              # 2:00:00 — DST is honoured

# Convert between zones:
ny = now.astimezone(ZoneInfo("America/New_York"))
print(ny)                           # 2026-04-22 04:00:00-04:00

# Same instant either way:
print(now.timestamp() == ny.timestamp())   # True

If you’re on Python 3.8 or earlier, you need either pytz or dateutil. Upgrade if you can — the pytz API is famously confusing (localize() vs astimezone()), and zoneinfo cleans it all up.

On Windows, you may also need to install tzdata:

pip install tzdata

Linux and macOS have the IANA database installed system-wide.

ISO 8601 round-trips

datetime parses and formats ISO 8601 cleanly:

# Format
now.isoformat()
# "2026-04-22T10:00:00+02:00"

# Parse (Python 3.11+)
datetime.fromisoformat("2026-04-22T10:00:00+02:00")

# Parse a "Z"-suffixed string (Python 3.11+ supports this directly)
datetime.fromisoformat("2026-04-22T08:00:00Z")

For Python 3.10 and earlier, fromisoformat is more limited. The robust pre-3.11 incantation:

from datetime import datetime
import re

s = "2026-04-22T08:00:00Z"
d = datetime.fromisoformat(s.replace("Z", "+00:00"))

When to reach for a library

The standard library covers 90% of cases. Reach for an external library when you need:

  • Robust parsing of arbitrary user input. dateutil (pip install python-dateutil) handles “April 22, 2026”, “next Friday”, and most everything else you’ll encounter in user-entered strings.
  • Friendly relative formatting (“3 days ago”, “in 2 hours”). humanize is a small dependency that does this well.
  • A nicer overall API. pendulum (pip install pendulum) and arrow (pip install arrow) both wrap datetime with a more fluent interface and built-in parsing. They’re great if you do a lot of date math; overkill if you only need to format two timestamps a day.

If you find yourself using dateutil heavily, consider whether pendulum would consolidate it.

The five things to remember

  1. Use datetime.now(timezone.utc), not datetime.now(). Naive datetimes are bugs waiting to happen.
  2. Use datetime.fromtimestamp(ts, tz=timezone.utc), not the bare fromtimestamp(ts).
  3. Use zoneinfo for timezone work on Python 3.9+. pytz advice is obsolete.
  4. time.time_ns() if you need integer nanoseconds; time.time() if you’re fine with a float in seconds.
  5. isoformat() for output, fromisoformat() for input. Stick with ISO 8601 strings on the wire.

A reference snippet

from datetime import datetime, timezone
from zoneinfo import ZoneInfo

# Now in seconds (int) and as ISO 8601 (str):
seconds = int(datetime.now(timezone.utc).timestamp())
iso = datetime.now(timezone.utc).isoformat()

# Parse a timestamp in seconds:
ts = 1745301600
d = datetime.fromtimestamp(ts, tz=timezone.utc)

# Convert to Oslo time:
oslo_time = d.astimezone(ZoneInfo("Europe/Oslo"))

# Pretty print:
print(oslo_time.strftime("%A, %d %B %Y at %H:%M %Z"))
# "Wednesday, 22 April 2026 at 10:00 CEST"

If you have a number on hand and want to sanity-check what date it represents, paste it into the Unixdates converter and you’ll get the result in your local timezone, UTC, and ISO 8601 — all at once.

Need to convert a timestamp right now? Try the Unixdates converter — auto-detects seconds, milliseconds and microseconds.