A teammate pasted a number, 1752345678, and asked me what date it was. I glanced at it and said about 2025-07-12. A few seconds later he followed up — wait, was it the 13-digit 1752345678901 or the 10-digit 1752345678? That is the everyday state most people are in with UNIX timestamps: familiar on sight, unreadable in practice.
This guide is so that next time you see a string of 10, 13, 16, or 19 digits, you can immediately say which day, hour, and second it points to — and understand why the epoch happens to be 1970-01-01.
30-second overview
- 1970-01-01 00:00:00 UTC was picked as the UNIX epoch for three reasons: UNIX itself was being born around then; a 32-bit signed integer fits time most conveniently starting from there; and choosing a fixed past moment makes cross-system alignment trivial.
- A UNIX timestamp equals the number of seconds since that moment (fractional allowed for sub-second precision). In practice you meet four common precisions: seconds, milliseconds, microseconds, nanoseconds.
- The digit count itself signals precision: 10 digits is roughly seconds, 13 is milliseconds, 16 is microseconds, 19 is nanoseconds. That rule alone gets you 90% of the way there.
- To switch between seconds, milliseconds, microseconds, nanoseconds, and human-readable dates on demand, bookmark our Unix Timestamp Converter.
5 real scenarios: where timestamps show up
Scenario 1: A 13-digit number in a server log
You see a line like 1752345678901 INFO request completed in a backend log. That number is almost certainly milliseconds, not seconds.
Why: mainstream backend languages default to Date.now() (JavaScript), System.currentTimeMillis() (Java), or time.time() * 1000 (Python), all of which return 13-digit milliseconds.
Fix: paste 1752345678901 into our Unix Timestamp Converter. It detects the precision automatically and renders both UTC and local time. Without the tool, remember 13 digits divided by 1000 is roughly 10 digits, and 1752345678 corresponds to about 2025-07-12.
Scenario 2: A 10-digit number in an API response
A REST API returns JSON like {"created_at": 1752345678, "expires_at": 1752346400}. That is almost certainly seconds.
Why: some legacy APIs (especially PHP-era code and certain Unix tools) still use seconds, and a number of public time APIs emit seconds directly.
Fix: read the field name. If it is unix_timestamp or epoch_seconds, it is seconds. If it is created_time_ms or timestamp_ms, it is milliseconds. Our Unix Timestamp Converter outputs all four precisions at once, so you can cross-check by eye.
Scenario 3: iat and exp claims in a JWT
A JWT payload often shows iat: 1752345678 and exp: 1752349278. Both are seconds, not milliseconds.
Why: RFC 7519 (the JWT spec) defines NumericDate as the number of seconds since the epoch (fractional allowed). All compliant JWT libraries use seconds.
Fix: decode the token with our JWT Decoder. iat and exp are rendered as seconds by default. If you see a 13-digit iat, that is a red flag — either a library misfilled milliseconds, or a non-standard implementation is at work.
Scenario 4: ISO 8601 and epoch mixed in a database
A common legacy trap: one column stores an ISO 8601 string (2025-07-12T10:30:00Z), another stores an epoch integer (1752345678). JOINs that forget to unify units become slow, and date comparisons that ignore timezones return wrong rows.
Why: this is a decade-old residue from ORM and migration scripts. Different teams and eras of code lead to both formats living in the same schema.
Fix: new tables store exactly one absolute-time format. Prefer epoch milliseconds (13 digits) or timezone-aware ISO 8601 (2025-07-12T10:30:00+08:00). Do not mix.
Scenario 5: Three engineers in three timezones
Someone posts a time in chat as 2025-07-12 10:30:00. One teammate reads it as Beijing time, another as UTC, a third as US Pacific. An eight-hour meeting slips and someone joins at midnight.
Why: a naive local time string with no timezone is a landmine in cross-timezone collaboration.
Fix: in cross-timezone work, use only timezone-aware ISO 8601 (2025-07-12T10:30:00+08:00) or epoch (10 or 13 digits). Both are absolute and do not depend on the reader’s local zone. As a team standard, prefer epoch ms — one number, one format, no ambiguity for anyone pasting it.
4 common pitfalls
Pitfall 1: Seconds vs milliseconds (the Y2K story: 2000-01-01 = 946684800 seconds, not 946684800000)
The classic trap: on 2000-01-01, some code treated epoch as milliseconds and computed 946684800000. Converting that back to a date overflowed 32-bit signed integers, crashing parts of some systems and pushing other dates back to 1970.
Fix: digit count is your first signal. 10 digits is roughly seconds; 13 digits is milliseconds. If your codebase mixes units, define constants like SECOND = 1 and MILLISECOND = 1000 and route every conversion through them. No raw numbers.
Pitfall 2: Comparing two epochs across timezones is safe; comparing two naive local strings is not
Two epochs subtracted always give you a real seconds difference, independent of timezone. Two naive local strings like 2025-07-12 10:30:00 subtracted give you a literal subtraction, not a real time delta, when readers sit in different zones.
Fix: cross-timezone persistence and comparison use only epoch or timezone-aware ISO 8601. Naive local strings live in the UI layer only — never for storage, never for comparison.
Pitfall 3: Precision loss — 16-digit microseconds and 19-digit nanoseconds are lossy in JavaScript
JavaScript Number is IEEE 754 double precision. The safe integer ceiling is Number.MAX_SAFE_INTEGER = 2^53 - 1 ≈ 9.007 × 10^15. A 16-digit microsecond (up to ~10^16) and a 19-digit nanosecond (up to ~10^19) both exceed that range, so storing them in a JS number drops precision.
Fix: backend languages like Go, Rust, and Python int preserve precision natively. The frontend can display, but when persisting back to the server, pass them as strings ("1752345678901234567"). Subtraction and comparison should happen in the backend with bigint or a date library.
Pitfall 4: 32-bit overflow — past 2038-01-19 03:14:07 UTC, 32-bit signed int rolls over
A 32-bit signed integer tops out at 2^31 - 1 = 2147483647 seconds, which is exactly 2038-01-19 03:14:07 UTC. Beyond that, a 32-bit time_t wraps to negative, and the system date jumps to 1901. That is Y2K38.
Fix: modern languages (Go, Rust, Python 3, Node.js, Java) all use 64-bit integers or higher precision and are not affected. But embedded systems, legacy COBOL mainframes, certain IoT devices, and some database drivers still use 32-bit time_t. Audit those paths before 2038 or they will repeat the Y2K drama in their own way.
Recommended practices
- In your code, use timezone-aware ISO 8601 strings for absolute time — for example
2025-07-12T10:30:00+08:00. Humans can read it, machines can parse it, and there is no timezone ambiguity. - In transit and storage, use epoch milliseconds (13 digits) for absolute time. One number, one format, unambiguous across languages. Works in JSON payloads, logs, database columns, and URL parameters.
- In front of users, convert to local time strings. Use
Intl.DateTimeFormator a similar library to localize absolute time — never make users do timezone math by hand. - When debugging and verifying, bookmark our Unix Timestamp Converter. Paste a number, see UTC, local time, ISO 8601, RFC 2822, and four epoch precisions in one second. Pair it with our JWT Decoder for iat/exp inspection and our Cron Expression Generator for next-run epoch readouts — that closes the time-handling loop.
- When timestamps appear URL-encoded or Base64-encoded (OAuth state, signed URLs, time-stamped tokens), decode them with our Text Encoder first, then paste the result into the Unix Timestamp Converter for human time.
The difficulty with timestamps is not the arithmetic — it is the mixing of precisions and timezones. Once your team settles on the epoch ms + ISO 8601 pair, 90% of timezone incidents stop happening.
Should you still worry about 2038?
It depends where you look.
Desktops, servers, mobile devices, cloud functions — 64-bit time_t is the default everywhere, and Y2K38 is not a problem on those platforms. Linux, macOS, Windows, Go, Rust, Python 3, Node.js, Java are all 64-bit. On 2038-01-19, nothing visible will happen.
The real risk lives in embedded systems, legacy COBOL mainframes, certain IoT devices, and some database drivers or filesystems that still use 32-bit signed integers for time_t. Those devices ship with slow firmware update cycles — security cameras, routers, industrial PLCs, automotive ECUs are the high-risk categories.
Fix: if your business depends on third-party devices or libraries, audit your dependency chain now. Look for time_t, int32_t storing seconds, old MySQL schemas, embedded C code. Upgrade what you can, write workarounds where you cannot, and replace the rest before 2038.
Just like the few years before Y2K, the engineering world will batch-fix in a window. Y2K38 has its own window — shorter than Y2K because 32-bit devices are now scattered across countless vendors — and the work starts today.
Open the Unix Timestamp Converter, paste a 13-digit millisecond value, and watch the tool auto-detect the precision and output all four precisions plus UTC, local time, ISO 8601, and RFC 2822 in one shot. Bookmarks add one click; answers land in one second. Next time someone pastes 1752345678, you will have an answer.