devkult_
tools29converters34
home/blog/unix-timestamps-explained
timejavascriptdatabases

Unix Timestamps Explained: Seconds vs Milliseconds, Time Zones and 2038

A Unix timestamp is the number of seconds since 1970-01-01 00:00:00 UTC, an instant known as the Unix epoch. 1700000000 is 14 November 2023, 22:13:20 UTC. It's a single number with no time zone, no format and no ambiguity — which is why APIs, databases, logs and JWTs all use it.

Seconds or milliseconds?

The most common timestamp bug is mixing the two. JavaScript counts milliseconds; most other systems count seconds. Count the digits:

Digits Unit Example
10 seconds 1700000000
13 milliseconds 1700000000000
16 microseconds 1700000000000000

Feed seconds to something expecting milliseconds and you land in January 1970:

new Date(1700000000).toISOString();         // "1970-01-20T16:13:20.000Z" — wrong
new Date(1700000000 * 1000).toISOString();  // "2023-11-14T22:13:20.000Z"

Going the other way, the classic symptom is a date tens of thousands of years in the future.

Getting and converting timestamps

JavaScript:

Date.now();                        // milliseconds
Math.floor(Date.now() / 1000);     // seconds
new Date(seconds * 1000);          // timestamp → Date
Math.floor(date.getTime() / 1000); // Date → seconds

Python:

import time
from datetime import datetime, timezone

int(time.time())                                   # seconds
datetime.fromtimestamp(1700000000, tz=timezone.utc)  # → aware datetime
int(datetime.now(timezone.utc).timestamp())        # datetime → seconds

Pass tz=timezone.utc — without it, fromtimestamp converts to the machine's local time.

PostgreSQL:

SELECT extract(epoch FROM now())::bigint;  -- seconds
SELECT to_timestamp(1700000000);           -- → timestamptz

MySQL:

SELECT UNIX_TIMESTAMP();             -- seconds
SELECT FROM_UNIXTIME(1700000000);    -- → DATETIME in the session time zone

Shell:

date +%s                 # now, in seconds
date -u -d @1700000000   # GNU/Linux: timestamp → date
date -u -r 1700000000    # macOS/BSD

For quick one-off conversions, use the timestamp to date and date to timestamp converters.

Time zones

A timestamp has no time zone — it names an instant, and that instant is the same everywhere. Time zones only appear when you display it: 1700000000 is 22:13 in London and 17:13 in New York, but it's one moment.

That's why storing timestamps (or timestamptz in PostgreSQL) is safer than storing local date strings. Convert to the user's zone at the edge, when you render:

new Date(1700000000 * 1000).toLocaleString("en-US", { timeZone: "America/New_York" });
// "11/14/2023, 5:13:20 PM"

JWTs use seconds

The exp, iat and nbf claims in a JSON Web Token are seconds, not milliseconds. Comparing them with Date.now() directly makes every token look expired decades ago — or valid until the year 55,000:

const expired = payload.exp * 1000 < Date.now();

Paste a token into the JWT decoder to see its claims as dates.

Leap seconds

Unix time pretends every day has exactly 86,400 seconds. When a leap second is inserted, Unix time repeats a second instead of counting it. For almost all application code this doesn't matter — just don't use timestamp differences for precise scientific timing.

The year 2038 problem

A signed 32-bit integer can hold values up to 2,147,483,647. As a Unix timestamp, that's 2038-01-19 03:14:07 UTC; one second later, a 32-bit counter wraps to a negative number and the date jumps back to 1901.

Modern operating systems and languages use 64-bit timestamps, which last for billions of years. The risk is in the corners: INT database columns, binary file formats and protocols with 32-bit fields, and older embedded devices. MySQL's TIMESTAMP type has the same limit — use DATETIME or a BIGINT for dates past 2038. If you're designing a schema today, use 64-bit integers for anything that holds a timestamp.

More from the blog