artisan.dev

Unix Timestamps Explained: Seconds vs Milliseconds, Timezones and 2038

By Tahsin Abrar · Updated

A Unix timestamp — also called epoch time or POSIX time — is the number of seconds that have elapsed since 00:00:00 UTC on 1 January 1970, ignoring leap seconds. It is a single integer with no timezone attached, which is exactly why APIs, databases, logs and JWTs love it.

This guide covers how to read timestamps, the seconds-versus-milliseconds trap, conversions in common languages, and the edge cases that cause real bugs. The Unix Timestamp Converter converts in both directions and shows the current epoch time live.

Try itOpen the Unix Timestamp Converter — runs in your browser

Seconds, milliseconds, or microseconds?

Different systems count in different units, and mixing them up gives dates in 1970 or tens of thousands of years in the future. The number of digits is the quickest tell for present-day dates:

DigitsUnitExampleUsed by
10Seconds1791500000Unix tools, PHP time(), Python time.time() (as float), JWT exp/iat, most APIs
13Milliseconds1791500000000JavaScript Date.now(), Java System.currentTimeMillis(), many JSON APIs
16Microseconds1791500000000000PostgreSQL internals, some tracing systems
19Nanoseconds1791500000000000000Go time.UnixNano(), Prometheus, InfluxDB

Classic bug: passing a seconds value to JavaScript's new Date(), which expects milliseconds. new Date(1791500000) is 21 January 1970. Multiply by 1000 first.

Converting in code

JavaScript

Math.floor(Date.now() / 1000);                 // now, in seconds
new Date(1791500000 * 1000).toISOString();     // seconds → ISO 8601 (UTC)
Date.parse("2026-10-09T12:00:00Z") / 1000;    // ISO → seconds

Python

import time, datetime as dt
int(time.time())                                            # now
dt.datetime.fromtimestamp(1791500000, tz=dt.timezone.utc)   # → aware datetime
int(dt.datetime(2026, 10, 9, tzinfo=dt.timezone.utc).timestamp())

SQL

-- PostgreSQL
SELECT to_timestamp(1791500000);                 -- timestamptz
SELECT extract(epoch FROM now())::bigint;

-- MySQL
SELECT FROM_UNIXTIME(1791500000);                -- in the session time zone
SELECT UNIX_TIMESTAMP();

Shell

date +%s                       # now
date -u -d @1791500000         # GNU date (Linux)
date -u -r 1791500000          # BSD date (macOS)

Timezones: the timestamp has none

A Unix timestamp identifies an instant, the same everywhere on Earth. Timezones only matter when you display it or parse a human-written date. Most bugs come from parsing a date string without a zone:

  • new Date("2026-10-09") (date only) is parsed as UTC midnight, but new Date("2026-10-09T00:00") (date and time, no offset) is parsed as local time. Always include Z or an offset like +06:00.
  • Python's datetime.fromtimestamp(ts) without tz= returns a naive datetime in the server's local zone; datetime.utcfromtimestamp() is deprecated since Python 3.12.
  • MySQL's FROM_UNIXTIME() and TIMESTAMP columns use the session time zone; DATETIME columns store no zone at all.

A good rule: store and transmit instants as UTC (epoch numbers or ISO 8601 with Z), and convert to a user's zone only at the edge, in the UI. The timezone tab of the time tools helps check what a moment looks like across zones.

Leap seconds and the Year 2038 problem

Unix time pretends every day has exactly 86,400 seconds, so leap seconds are not counted; during one, clocks repeat or smear a second. For application code this almost never matters, and leap seconds are scheduled to be abolished by 2035.

The Year 2038 problem is more concrete. A signed 32-bit integer can hold at most 2,147,483,647, which as a Unix timestamp is 03:14:07 UTC on 19 January 2038. One second later it overflows to a negative number — December 1901. Modern 64-bit operating systems and languages are fine, but watch for:

  • Database columns declared INT to hold timestamps — use BIGINT.
  • MySQL's TIMESTAMP type, whose range ends in 2038; DATETIME goes to year 9999.
  • Embedded devices, old file formats and binary protocols with 32-bit time fields.
  • Expiry dates already more than 12 years out, such as long-lived certificates or long-term schedules, which already cross the limit today.

Timestamps you will meet in JWTs

JSON Web Tokens use NumericDate values — seconds, not milliseconds — for exp (expiry), iat (issued at) and nbf (not before). A token whose exp looks like 1791500000 is fine; a 13-digit value means the issuer used milliseconds and most libraries will treat the token as valid for tens of thousands of years. The JWT Decoder shows these claims as readable dates.

FAQ

Is a Unix timestamp always in UTC?
Yes. It counts seconds since 1970-01-01T00:00:00Z, so it has no timezone of its own. Only the human-readable form you convert it to depends on a timezone.
How do I know if a timestamp is in seconds or milliseconds?
For current dates, 10 digits means seconds and 13 digits means milliseconds. If converting gives a date in January 1970, you probably passed seconds to something that expects milliseconds.
What happens in 2038?
Systems that store Unix time in a signed 32-bit integer overflow at 03:14:07 UTC on 19 January 2038. 64-bit systems are unaffected, but old database columns, file formats and embedded devices need checking.

More guides