← Back to blog

Developers: Quick Code: Seconds, Floats, ms to UTC Datetimes in Python

September 17, 2026
Developers: Quick Code: Seconds, Floats, ms to UTC Datetimes in Python

Use datetime.fromtimestamp(ts, tz=timezone.utc) to convert a POSIX seconds timestamp into a timezone-aware UTC datetime. This is the standard, correct method for turning epoch to datetime in Python. If your timestamp is in milliseconds, divide by 1,000 first. Skip the tz argument and you get a naive datetime tied silently to your machine's local settings, which is exactly how timezone bugs sneak into production code.


TL;DR:

  • Always use datetime.fromtimestamp(ts, tz=timezone.utc) for converting epoch seconds to timezone-aware UTC datetimes, especially avoiding naive datetimes that assume local time zones.
  • When handling millisecond timestamps, divide by 1000 before conversion, or use pandas' pd.to_datetime(series, unit='ms', utc=True) for bulk processing.
  • Converting float seconds preserves microsecond precision, which is crucial for detailed event timing and logging.
  • Comparing naive datetimes from different zones causes errors; always normalize to UTC before performing comparisons or calculations.
  • For debugging or quick checks, time.strftime with gmtime provides fast string outputs but lacks datetime object functionality.

Pingfloat
Check Your Website Health
Pingfloat brings uptime monitoring, SSL checks, and network diagnostics together for clearer web management and faster troubleshooting.
Explore Pingfloat tools

Table of Contents

Quick Code Recipes: Seconds, Floats, and Millisecond Timestamps

Most epoch-to-datetime conversion problems fall into three buckets: whole seconds, seconds with fractional precision, and millisecond timestamps from JavaScript or mobile APIs. Here's how to handle each.

Integer seconds to an aware UTC datetime:

from datetime import datetime, timezone
dt = datetime.fromtimestamp(1700000000, tz=timezone.utc)
print(dt)  # 2023-11-14 22:13:20+00:00

Float seconds, preserving microseconds:

dt = datetime.fromtimestamp(1700000000.123456, tz=timezone.utc)
print(dt.microsecond)  # 123456

datetime.fromtimestamp() handles fractional seconds natively, which matters if you're logging event timing down to the millisecond and need that precision preserved through the conversion.

Millisecond (13-digit) timestamps, common from JavaScript's Date.now() or mobile SDKs, need scaling before conversion:

ms_timestamp = 1700000000123
dt = datetime.fromtimestamp(ms_timestamp / 1000, tz=timezone.utc)

For bulk data, pd.to_datetime(series, unit='ms') skips the manual division entirely.

Quick formatted string with the time module:

import time
time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(1700000000))
# '2023-11-14 22:13:20'

This strftime-and-gmtime approach is handy for a throwaway debug print, but it drops fractional seconds and returns a plain string, not a datetime object you can do math on.

  • datetime.fromtimestamp(ts, tz=timezone.utc): best default for aware datetimes
  • datetime.fromtimestamp(ts / 1000, tz=timezone.utc): for millisecond inputs
  • time.gmtime() + time.strftime(): fast, string-only output for logs

Why Timezone-Aware Conversion Matters

Python's naive datetimes carry no timezone information at all. A naive datetime built from datetime.fromtimestamp(ts) without a tz argument silently assumes your system's local timezone, according to the official datetime documentation. Run that code on a server in UTC and a laptop in California, and you get two different wall-clock times from the identical epoch value.

Mix naive and aware datetimes in a subtraction or comparison and Python throws a TypeError rather than silently producing a wrong answer, which is actually the friendlier failure mode. The dangerous case is when both objects are naive but represent different zones. Python compares the numbers with no idea they mean different moments, and you get a wrong result with no error at all.

The fix is consistent: always pass tz explicitly.

Naive and aware datetime paths

from datetime import datetime, timezone
from zoneinfo import ZoneInfo

utc_dt = datetime.fromtimestamp(1700000000, tz=timezone.utc)
la_dt = utc_dt.astimezone(ZoneInfo('America/Los_Angeles'))
print(la_dt)  # 2023-11-14 14:13:20-08:00

zoneinfo, part of the standard library since Python 3.9, replaces older pytz workflows for most new projects. Avoid datetime.utcfromtimestamp(): it returns a naive datetime that only looks like UTC, and the official docs note it's deprecated in favor of the explicit tz-argument approach.

  • Always convert incoming epoch values to UTC first, before any display logic
  • Reserve local-zone conversion for the final rendering step, not for storage or math
  • Never store or compare naive datetimes across systems that might run in different zones

Pro Tip: Normalize every timestamp to UTC the moment it enters your system, do all your arithmetic and comparisons there, and only convert to a local zone at the very last step, right before you display it to a user.

Converting Large Datasets: Pandas and Vectorized Tools

Looping over thousands of rows calling datetime.fromtimestamp() one at a time works, but it's slow and easy to get wrong at scale. Pandas solves both problems with pd.to_datetime():

import pandas as pd
df['datetime'] = pd.to_datetime(df['epoch'], unit='s', utc=True)

Swap unit='s' for unit='ms' or unit='ns' depending on your source data, and pandas handles the scaling internally rather than you writing manual division into every pipeline. Pandas stores datetimes internally as nanoseconds, so pd.to_datetime() is generally the more reliable choice for bulk conversion than a Python-level loop, both for speed and for avoiding precision drift on large integer timestamps.

For anything under a few thousand rows, a plain Python loop or list comprehension is perfectly fine, and arguably more readable. Reach for pandas once you're processing log files, CSV exports, or API responses with tens of thousands of rows.

  • pd.to_datetime(series, unit='s', utc=True) for whole-second epoch columns
  • pd.to_datetime(series, unit='ms', utc=True) for millisecond timestamps
  • Arrow or dateutil for parsing mixed or inconsistent date string formats alongside epoch values

Common Mistakes That Break Epoch Conversions

Most epoch-to-datetime bugs trace back to one of a handful of repeat offenders.

  1. Comparing naive datetimes from different zones. Convert both sides to UTC before comparing or subtracting, every time.
  2. Losing precision with float math. Repeated float division on large timestamps can introduce tiny rounding errors. Prefer integer division for millisecond scaling when microsecond accuracy isn't needed.
  3. Assuming leap seconds are handled. POSIX time, which Python follows, ignores leap seconds by convention, so don't expect sub-second astronomical accuracy from raw epoch math.
  4. Forgetting that timestamp() behavior depends on tzinfo. Calling .timestamp() on a naive datetime assumes local time, per Note, which can silently shift your result if you're not on the zone you think you're on.

Converting Epoch Fields From CSVs and Log Files

Real-world epoch data usually arrives as a column in a CSV or a field buried in a log line, not a single clean variable. The pattern for CSVs is short:

df = pd.read_csv('events.csv')
df['ts'] = pd.to_datetime(df['epoch_ms'], unit='ms', utc=True)

For smaller datasets, or when you're parsing epoch values out of raw log text, a list or generator comprehension keeps things simple:

timestamps = [1700000000, 1700003600, 1700007200]
dates = [datetime.fromtimestamp(t, tz=timezone.utc) for t in timestamps]

When you need to write results back out, ISO 8601 is the safest format, since it embeds the offset and avoids ambiguity for anyone reading the file later:

  • dt.isoformat() produces 2023-11-14T22:13:20+00:00
  • dt.strftime('%Y-%m-%dT%H:%M:%S%z') gives you custom control over the format if you need something non-standard

What I'd Tell Any Team Standardizing on This

Three rules cover almost every epoch conversion issue you'll run into: default to timezone-aware UTC datetimes, always scale millisecond inputs down to seconds before conversion, and switch to vectorized tools like pandas the moment you're processing more than a few hundred values in a loop. Teams that skip the first rule are the ones filing mysterious "off by a few hours" bug reports six months later. When you just need to sanity-check a value fast, Pingfloat's Unix Timestamp & Epoch Converter is a quick way to confirm your code's output matches expectations before you ship it.

— Andreas

Verify Your Conversions Without Writing Throwaway Scripts

Pingfloat gives you a faster way to sanity-check epoch conversions than spinning up a Python shell every time you doubt your own code. The Unix Timestamp & Epoch Converter takes seconds, milliseconds, or nanoseconds and shows the ISO 8601 result with the timezone spelled out, so you can confirm your fromtimestamp() output is right before it ships.

Pingfloat

Pair it with the Time Zone Converter when you're debugging a zoneinfo conversion and want to see the local time in another region without touching a terminal. Both tools are free, require no sign-up, and sit alongside Pingfloat's broader set of developer utilities, including uptime monitoring and SSL checks, for when a bad timestamp turns out to be the smallest problem on your plate. Open the converter, paste in your epoch value, and confirm your code's output in under ten seconds.

Sources

FAQ

How Do You Convert Epoch Time to Datetime?

Call datetime.fromtimestamp(ts, tz=timezone.utc) with your epoch seconds value to get a timezone-aware UTC datetime object. If the timestamp is in milliseconds, divide by 1,000 before passing it in.

How Do I Convert an Epoch to a Timestamp?

"Epoch" and "timestamp" usually refer to the same thing: seconds since January 1, 1970, UTC. To go the other direction and turn a datetime object back into that number, call .timestamp() on it, though the result depends on whether the datetime is naive or timezone-aware.

Is Epoch the Same as UTC?

Not exactly. Epoch time is a count of seconds elapsed since a fixed UTC reference point (1970-01-01 00:00:00 UTC), while UTC itself is a time standard. The epoch number is universal and timezone-independent; UTC is one specific timezone you convert that number into for display.

What Is an Epoch in Python?

In Python, an epoch timestamp is a number, typically an integer or float, representing seconds since the Unix epoch. Python's time and datetime modules both work with these values, with time.gmtime(0) returning the epoch's starting point as a struct_time.