Unix Timestamps: Seconds vs Milliseconds
Distinguish Unix timestamps in seconds and milliseconds, avoid accidental 1970 dates, and convert each unit safely across programming languages.
Many developers encounter this date bug: an event timestamp reaches a frontend and displays January 20, 1970 or causes a backend range error. The usual cause is a mismatch between seconds and milliseconds.
These errors stem from a single, pervasive point of confusion in modern software engineering: the mismatch between Unix timestamps in seconds versus milliseconds.
Both formats measure from the Unix epoch, but APIs choose different units. POSIX-oriented functions often use seconds, while JavaScript Date and Java's System.currentTimeMillis() use milliseconds. Passing one unit to an API expecting the other introduces a factor-of-1,000 error.
You can inspect, convert, and format timestamps across both scales instantly using ToolPorta's free Timestamp Converter. This guide explains how the Unix epoch works, breaks down the mathematical difference between 10-digit and 13-digit timestamps, diagnoses the root causes of common timestamp bugs, provides an automatic heuristic detection algorithm, and outlines best practices for designing time-safe APIs.
What is a Unix timestamp?#
A Unix timestamp (also known as POSIX time or Epoch time) is an integer representing the total amount of time that has elapsed since the Unix Epoch:
Leap seconds are ignored by convention; every day in Unix time is treated as having exactly 86,400 seconds.
Unix time is fundamentally timezone-agnostic. A timestamp represents a single, universal instant in time across the globe. Whether a user inspects a timestamp in Tokyo, Paris, London, or New York, the underlying numeric timestamp is identical. Timezones, Daylight Saving Time (DST) shifts, and calendar formatting are purely presentation-layer concerns applied when rendering the timestamp for human eyes.
The core difference: 10 digits vs 13 digits#
The fundamental division between timestamps comes down to the unit of measurement:
| Attribute | Unix Seconds (s) | Unix Milliseconds (ms) |
|---|---|---|
| Unit of Measurement | Seconds | Milliseconds (1/1,000 second) |
| Typical String Length | 10 digits (contemporary era) | 13 digits (contemporary era) |
| Example for 2026-10-01 00:00:00 UTC | 1790812800 | 1790812800000 |
| Order of Magnitude in 2026 | About 1.8 billion | About 1.8 trillion |
| Multiplier to Convert | Multiply by 1,000 to get ms | Divide by 1,000 to get seconds |
| Precision | One-second resolution | Sub-second (millisecond) resolution |
| Dominant Ecosystems | Linux, C/POSIX, Python time.time(), MySQL, PHP | JavaScript/Node.js, Java, Android, MongoDB |
The 10-digit scale: Unix seconds#
For over five decades, standard Unix operating systems, C libraries (time_t), and POSIX standards counted time strictly in seconds. In the contemporary 2020s, any Unix timestamp in seconds has exactly 10 decimal digits, beginning with 1 (for example, 1704067200 represents January 1, 2024 at 00:00:00 UTC).
Timestamps in seconds will remain 10 digits long until November 20, 2286, when the timestamp reaches 10000000000 (11 digits).
The 13-digit scale: Unix milliseconds#
With the rise of personal computing, graphical user interfaces, network latency tracing, and modern runtimes like Java (1995) and JavaScript (1995), second-level granularity proved insufficient. Animation frames, user click events, and database transaction timestamps required sub-second precision.
Java's System.currentTimeMillis() and JavaScript's Date.now() return milliseconds. For whole-second values, converting seconds to milliseconds multiplies by 1,000; real millisecond timestamps may use the final three digits for sub-second precision rather than simply ending in zeroes.
In the contemporary era, all millisecond timestamps are 13 decimal digits long (for example, 1704067200000).
Beyond milliseconds: Microseconds and nanoseconds#
High-performance computing and distributed tracing systems sometimes use even finer granularities:
- Microseconds (16 digits in the contemporary era): One-millionth of a second. Some APIs and database representations use Unix microseconds; in Python,
time.time_ns() // 1_000produces an integer microsecond value. - Nanoseconds (19 digits in the contemporary era): One-billionth of a second. Go's
time.Now().UnixNano()and Python'stime.time_ns()return Unix-based nanosecond values. A monotonic clock such asCLOCK_MONOTONICuses nanoseconds or another fine resolution but is not anchored to the Unix epoch.
Two infamous production bugs caused by unit mismatch#
When distributed systems exchange timestamps without explicit documentation of their scale, catastrophic bugs occur.
Bug 1: The "January 1970" frontend rendering bug#
This is the single most common date bug in web development.
A backend API (written in Python, Go, or PHP) returns a creation timestamp in seconds:
A frontend developer takes this timestamp and passes it directly into JavaScript's Date constructor:
Why does this happen?
The developer intended to render January 1, 2024. However, because new Date(value) expects milliseconds, it interpreted 1704067200 as 1,704,067,200 milliseconds after the epoch.
Dividing 1,704,067,200 ms by 1,000 gives 1,704,067.2 seconds, or approximately 19.72 days after the Unix epoch. That lands on January 20, 1970.
An unexpected date in January 1970 is therefore a strong sign that a seconds value was interpreted as milliseconds.
Bug 2: Backend range and overflow errors#
The inverse error occurs when a frontend application or Java service sends a 13-digit millisecond timestamp to a backend service or database expecting seconds.
Suppose a client sends 1704067200000 to a Python backend:
Likewise, inserting 1704067200000 into a signed 32-bit SQL INT column is out of range because the maximum is 2,147,483,647. Exact exception text depends on the database and SQL mode.
Ecosystem breakdown: Who uses what?#
Different programming languages, frameworks, and storage layers have hardened preferences. Knowing these defaults prevents cross-service integration failures:
| Ecosystem / Tool | Native Unit | Function / Method | Example Output (2026) |
|---|---|---|---|
| JavaScript / TypeScript | Milliseconds | Date.now() / new Date().getTime() | 1790812800000 |
| Browser elapsed-time API | Milliseconds since a monotonic origin, not Unix time | performance.now() | 1423.8 |
| Java | Milliseconds | System.currentTimeMillis() | 1790812800000 |
| Python | Seconds (Float) | time.time() | 1790812800.41295 |
| Go | Seconds / Nanos | time.Now().Unix() / UnixNano() | 1790812800 |
| PHP | Seconds | time() | 1790812800 |
| C / C++ | Seconds | time(NULL) | 1790812800 |
| Ruby | Seconds (Float) | Time.now.to_f | 1790812800.125 |
| MySQL / MariaDB | Seconds | UNIX_TIMESTAMP() | 1790812800 |
| PostgreSQL | Seconds (Float) | EXTRACT(EPOCH FROM NOW()) | 1790812800.681 |
| MongoDB ObjectId | Timestamp field stored with second precision | ObjectId.getTimestamp() returns a date object | Date corresponding to creation time |
| Stripe API | Seconds | created attribute | 1790812800 |
Notice the pattern: systems closer to the operating system, C runtime, or POSIX specifications use seconds. Systems designed for web browsers, user interfaces, or JVM runtimes use milliseconds.
Defensive heuristic detection for contemporary timestamps#
When your application ingests timestamps from third-party webhooks, arbitrary user CSV files, or legacy microservices, you may not know upfront whether an integer is in seconds, milliseconds, microseconds, or nanoseconds.
Magnitude can help with contemporary positive timestamps, but it cannot identify a unit universally. The safest API contract carries an explicit unit. If legacy input is unavoidable, reject values outside a documented date window instead of guessing every integer.
Defensive JavaScript and TypeScript example#
Defensive Python example#
Best practices for API and database design#
To protect your software against timestamp corruption, adopt these architectural standards:
- Be explicit in field names: When naming JSON properties or database columns, include the unit in the identifier. Name your fields
created_at_msorexpires_at_secinstead of ambiguous names liketimestamportime. - Standardize on ISO 8601 for public APIs: For external REST and GraphQL APIs, use standardized ISO 8601 strings (e.g.,
2026-10-01T12:00:00.000Z) rather than raw numbers. String dates are self-describing, human-readable in network logs, and eliminate all unit ambiguity. - Use 64-bit integers for storage: Never store timestamps in 32-bit integer columns (
INT). Always useBIGINT(8 bytes) to ensure compatibility with milliseconds and complete immunity to the Year 2038 Problem. - Enforce UTC on the server: Always calculate and persist timestamps in UTC. Never apply local timezone offsets or daylight saving shifts to stored epoch values.
Frequently asked questions#
How do I know if my timestamp is in seconds or milliseconds?#
For contemporary positive dates, 10 digits usually indicates seconds and 13 digits usually indicates milliseconds. Treat that as a heuristic, not a universal rule: old, negative, or far-future timestamps need an explicit unit. A 1970 result from a modern-looking value passed to JavaScript's new Date() is a strong clue that seconds were supplied where milliseconds were expected.
Why does JavaScript default to milliseconds while Unix defaults to seconds?#
Unix was designed in 1969–1971 for operating systems and file systems where 1-second granularity was more than sufficient and memory was severely constrained (32-bit registers). JavaScript was designed in 1995 for interactive browser interfaces, user events, and animations, where sub-second millisecond timing is essential.
Can a Unix timestamp be negative?#
Yes. Negative timestamps represent dates prior to the Unix Epoch (January 1, 1970). For example, -315619200 corresponds to January 1, 1960 at 00:00:00 UTC. Standard 64-bit signed integer implementations safely support negative timestamps going back hundreds of millions of years.
Do Unix timestamps account for leap seconds?#
No. The POSIX standard explicitly mandates that every day consists of exactly 86,400 seconds. When an official leap second is inserted by the International Earth Rotation and Reference Systems Service (IERS), Unix clocks repeat a second or use "leap second smearing" (gradually slowing the system clock over a 24-hour window) to maintain synchronization with atomic time without adding a 61st second to the integer counter.
What happens when converting between float and integer timestamps?#
Python and PostgreSQL often represent timestamps as floating-point numbers (e.g., 1704067200.500), where the integer part represents seconds and the decimal represents fractional milliseconds or microseconds. When converting floats to integer milliseconds, always multiply first and then round or floor: Math.floor(floatSeconds * 1000). Converting float directly to int truncates the fractional precision.
Try the related ToolPorta tools#
Debug, test, and convert your timestamps and identifiers directly in your browser:
- Use the Timestamp Converter to transform Unix seconds, milliseconds, ISO 8601 strings, and human dates with zero server requests.
- Use the UUID Generator to create time-ordered UUIDv7 identifiers containing embedded 48-bit millisecond Unix timestamps.
- Use the JSON Formatter to validate and inspect API responses containing timestamp properties.
- Read our JavaScript Timestamp Guide and Python Timestamp Guide for code snippets to integrate into your production codebase.