What Is a Unix Timestamp and How Do You Read One?
The authoritative reference for understanding Unix epoch time — what each one looks like, how to use it, and how timestamps interact in real systems.
TL;DR — Key Points
What Is a Unix Timestamp?
A Unix timestamp is a single integer that represents a specific moment in time by counting the number of seconds that have elapsed since January 1, 1970, 00:00:00 UTC — a reference point known as the Unix Epoch. It is the universal language of time in software, appearing in server logs, database records, API responses, authentication tokens, cache headers, analytics events, and virtually every system where two machines need to agree on when something happened.
When you see a number like 1716278400 in a JSON response or a database column, that is a Unix timestamp. It means exactly Wednesday, May 21, 2026, 00:00:00 UTC — not approximately, not in a particular timezone, but precisely that moment in time anywhere on Earth. The beauty of Unix timestamps is their simplicity: one number, no ambiguity, no timezone conversions required at storage time.
Unix timestamps are used by Linux and Unix operating systems natively, by Python's time.time() function, by PostgreSQL's EXTRACT(EPOCH) function, by most REST APIs including GitHub, Stripe, Twilio, and AWS, and by JWT tokens for expiry (exp) and issued-at (iat) claims. If you work with software in any capacity, you will encounter Unix timestamps regularly.
The key mental model is this: a Unix timestamp is just arithmetic. To find what timestamp corresponds to a moment, count the seconds from January 1, 1970. To find what moment a timestamp represents, add that many seconds to January 1, 1970. Everything else — timezones, daylight saving, leap years — is handled by conversion functions, not by the timestamp itself. The timestamp is always UTC, always an integer, always counting from the Epoch.
Seconds vs Milliseconds vs Microseconds
The single most common source of timestamp bugs in software is the confusion between seconds, milliseconds, and occasionally microseconds. The formats look similar but differ by factors of 1,000. Passing a milliseconds timestamp where seconds are expected — or the reverse — produces dates that are off by 1,000 years or are stuck in January 1970.
The fastest way to identify the format is by digit count. For any date between 2001 and 2286, a seconds timestamp is exactly 10 digits long, a milliseconds timestamp is exactly 13 digits, and a microseconds timestamp is 16 digits. If you see 1716278400 (10 digits) it is seconds. If you see 1716278400000 (13 digits) it is milliseconds. This rule works reliably for all modern dates.
JavaScript is the primary source of millisecond timestamps because Date.now() and new Date().getTime() both return milliseconds. This is why passing a seconds timestamp directly to new Date(1716278400) gives you January 20, 1970 — JavaScript interprets the 10-digit number as only about 19 days of milliseconds. The fix is always: new Date(1716278400 * 1000).
On the server side, the convention is almost universally seconds. Python's time.time(), PostgreSQL's EXTRACT(EPOCH), most REST APIs, and JWT tokens all use seconds. When you receive a 10-digit timestamp from a backend API, treat it as seconds. When you generate one in JavaScript frontend code and it is 13 digits, it is milliseconds.
Format Comparison Table
Use this reference when you encounter an unknown timestamp and need to identify its format and convert it correctly.
| Feature | Seconds | Milliseconds | Microseconds |
|---|---|---|---|
| Digit count | 10 digits | 13 digits | 16 digits |
| Example value | 1716278400 | 1716278400000 | 1716278400000000 |
| Precision | 1 second | 1 millisecond | 1 microsecond |
| Common in | Linux, Python, APIs, JWT, PostgreSQL | JavaScript, Java, analytics | C/C++, high-frequency trading |
| Convert to date (JS) | new Date(ts * 1000) | new Date(ts) | new Date(ts / 1000) |
| Convert to date (Py) | datetime.utcfromtimestamp(ts) | datetime.utcfromtimestamp(ts/1000) | datetime.utcfromtimestamp(ts/1e6) |
Negative Timestamps — Dates Before 1970
Unix timestamps are not constrained to positive numbers. A negative timestamp represents a moment that occurred before January 1, 1970, 00:00:00 UTC — before the Epoch. The further back in history, the more negative the value. This is mathematically consistent and is supported by most modern programming languages and databases.
-1 is one second before the Epoch: December 31, 1969, 23:59:59 UTC. -86400 is one full day before the Epoch (86,400 = 60 × 60 × 24): December 31, 1969, 00:00:00 UTC. -2208988800 corresponds to January 1, 1900, 00:00:00 UTC — the start of the 20th century.
JavaScript's new Date(-86400 * 1000) correctly returns December 31, 1969. Python's datetime.utcfromtimestamp(-86400) works correctly on most platforms. PostgreSQL handles negative epoch values in TO_TIMESTAMP() without issues. Problems arise in older C libraries, 32-bit unsigned integer storage, and certain embedded database drivers.
Negative timestamps have practical uses in archival systems, financial history databases, and genealogy software. If your application handles historical dates, test your full stack — language runtime, ORM, and database — with the value -86400 before committing to negative timestamp storage in production.
Famous Unix Timestamp Milestones
Several specific Unix timestamps have become culturally or technically significant in the developer community.
Timestamp 0 — The Unix Epoch (January 1, 1970, 00:00:00 UTC). The starting point of all Unix time. This is a valid timestamp — not a null or sentinel value. Any system that treats timestamp 0 as "missing" has a logic error that will eventually cause incidents when records are created at that exact UTC time in certain timezones.
946684800 — Y2K (January 1, 2000, 00:00:00 UTC). The famous Y2K concern was about two-digit year storage in COBOL and legacy systems, not Unix timestamps. Unix time handled Y2K perfectly: 946684800 is simply the next integer after 946684799. This is one of the strengths of Unix timestamps — no representation gap at round year boundaries.
1234567890 — The Sequential Milestone (February 13, 2009, 23:31:30 UTC). This timestamp became culturally significant because of its recognizable sequential pattern. Developers around the world organised gatherings and countdown timers to watch their system clocks tick past this number.
2147483647 — The Y2K38 Problem (January 19, 2038, 03:14:07 UTC). This is the most serious milestone. 2,147,483,647 is the maximum value of a 32-bit signed integer (2³¹ − 1). Systems storing Unix timestamps in signed 32-bit integers will overflow at this moment, wrapping to -2,147,483,648 and jumping to December 13, 1901. Affected systems include legacy embedded firmware, older Linux kernels, some IoT devices, and legacy database schemas using INT columns. Modern 64-bit systems are safe — they can hold timestamps until the year 292,277,026,596.
Converting Timestamps in Code
The conversion functions you need depend on your language and the direction of conversion. Here are the canonical patterns for the most common environments.
JavaScript (browser and Node.js): Current timestamp in milliseconds: Date.now(). In seconds: Math.floor(Date.now() / 1000). Seconds timestamp to Date: new Date(ts * 1000). To ISO 8601: new Date(ts * 1000).toISOString(). To IST locale string: new Date(ts * 1000).toLocaleString("en-IN", { timeZone: "Asia/Kolkata" } ).
Python: Current timestamp in seconds: import time; int(time.time()). Timestamp to datetime: from datetime import datetime, timezone; datetime.fromtimestamp(ts, tz=timezone.utc). Always use timezone.utc — naive datetimes from utcfromtimestamp() are deprecated in Python 3.12+.
PostgreSQL: Current epoch: SELECT EXTRACT(EPOCH FROM NOW())::INTEGER. Integer to timestamp: SELECT TO_TIMESTAMP(1716278400). TIMESTAMPTZ to epoch: SELECT EXTRACT(EPOCH FROM created_at)::INTEGER FROM orders.
Bash / Linux: Current timestamp: date +%s. Convert to date (GNU/Linux): date -d @1716278400. Convert (macOS): date -r 1716278400.
Unix Timestamps in JWT Tokens
JSON Web Tokens use Unix timestamps in seconds for three standard claims: iat (issued at), exp (expiration), and nbf (not before), as specified in RFC 7519. These are always seconds, never milliseconds.
A typical JWT payload: { "sub": "user_abc123", "iat": 1716192000, "exp": 1716278400 }. To check expiry in JavaScript: const isExpired = exp < Math.floor(Date.now() / 1000). The division by 1000 is critical — Date.now() returns milliseconds, exp is seconds. This off-by-1000 error is one of the most common JWT bugs.
A common security practice is adding a clock skew buffer: exp + 300 < now (5 minutes). This prevents rejecting valid tokens due to minor clock drift between distributed servers — especially important in microservice architectures.
JWT headers and payloads are base64url-encoded, not encrypted — you can decode and read the exp timestamp without the secret key. This allows the frontend to proactively refresh tokens before they expire, rather than waiting for a 401 response from the API.
How to Decide Which Format You Need
Start with your use case, not the technology. Apply the following logic:
You see a 10-digit number in an API response or database column
→ Treat as seconds. Multiply by 1000 before passing to JavaScript Date().
You see a 13-digit number from Date.now() or a JS library
→ Treat as milliseconds. Pass directly to new Date() in JS. Divide by 1000 for Python.
You need to store a timestamp in a database for a modern application
→ Use a TIMESTAMP or TIMESTAMPTZ column, not an integer. Let the database handle it.
You need to check whether a JWT token has expired
→ Read the exp claim. Compare to Math.floor(Date.now() / 1000) in JS or time.time() in Python.
Your application handles dates before 1970 (historical data)
→ Use negative timestamps. Verify your language runtime and database support them before relying on them.
When Should You Use Each?
Consider these scenarios with the correct approach for each:
| Scenario | Correct Approach |
|---|---|
| Storing a user registration date in PostgreSQL | TIMESTAMPTZ column (let the database handle it) |
| Parsing a GitHub API response with created_at | ISO 8601 string — use new Date(isoString) directly |
| Decoding a JWT token exp field | Unix seconds — compare to Math.floor(Date.now() / 1000) |
| Reading a server log timestamp | Unix seconds (10 digits) — multiply by 1000 for JS |
| Measuring front-end performance with Date.now() | Unix milliseconds (13 digits) — use directly in JS |
| Representing a historical date (pre-1970) | Negative Unix timestamp or ISO 8601 string |
Frequently Asked Questions
What is the difference between Unix time and UTC?
Unix time is a counting system — it counts seconds from the Epoch. UTC is a time standard — it defines what time it is at offset 0. Unix timestamps are always expressed in UTC, meaning the same number means the same moment everywhere regardless of local timezone. They are complementary: UTC tells you what time zone, Unix tells you the exact count.
Why does Unix time start in 1970?
January 1, 1970 was chosen when Unix was being developed at Bell Labs in the late 1960s and early 1970s. It was a convenient round date close to the time of development that fit within the 32-bit integer storage available. There is no deeper significance — it became the global standard purely by adoption of Unix across the industry.
How do I convert a Unix timestamp to a date in JavaScript?
If your timestamp is in seconds (10 digits): new Date(timestamp * 1000). If it is in milliseconds (13 digits): new Date(timestamp). To get ISO format: new Date(timestamp * 1000).toISOString(). The most common mistake is forgetting to multiply by 1000, which gives a date in January 1970.
How do I convert a Unix timestamp in Python?
From seconds: from datetime import datetime, timezone; datetime.fromtimestamp(ts, tz=timezone.utc). From milliseconds: datetime.fromtimestamp(ts / 1000, tz=timezone.utc). Always use timezone.utc to get a timezone-aware datetime rather than a naive one.
What is the Y2K38 problem?
Systems that store Unix timestamps as 32-bit signed integers will overflow at 2,147,483,647, which corresponds to January 19, 2038, 03:14:07 UTC. One second later, the counter rolls to -2,147,483,648, representing December 13, 1901. Modern 64-bit systems are not affected. The problem remains real for embedded systems, legacy firmware, and IoT devices.
Can Unix timestamps be negative?
Yes. A negative timestamp means the moment is before January 1, 1970. -1 is December 31, 1969, 23:59:59 UTC. -86400 is December 31, 1969, 00:00:00 UTC. Most modern languages and databases handle negative timestamps correctly.
Is a Unix timestamp always in seconds?
No — this is the source of constant confusion. The original Unix standard uses seconds, and most server-side systems follow this. However, JavaScript's Date API uses milliseconds, and many frontend libraries also use milliseconds. Always check the digit count: 10 digits = seconds, 13 digits = milliseconds.
What is the maximum Unix timestamp supported by modern systems?
For 32-bit signed integers: 2,147,483,647 (January 19, 2038). For 64-bit signed integers used by modern systems: 9,223,372,036,854,775,807 seconds — corresponding to the year 292,277,026,596. JavaScript's Date object supports timestamps up to approximately ±275,760 years from 1970.
Related Concepts
Related Tools
Timestamp to Date Converter
Convert any Unix timestamp to a human-readable date instantly. Auto-detects seconds vs milliseconds.
Date to Timestamp Converter
Convert any date and time back to a Unix epoch timestamp in seconds or milliseconds.
JWT Decoder
Decode any JWT token and inspect the exp, iat, and all other claims.