Unix Timestamp Conversion: Seconds, Milliseconds and 2038

2026-10-146 min readDeveloper Tools5 tools covered

A timestamp is just a number, and the number alone does not tell you its unit. Use the wrong unit and a 2026 date renders as 1970 - or as the year 50,000. Getting digit count, timezone and precision straight prevents nearly all of these bugs.

Key takeaways
  • The digit count defines the unit - 10 digits is seconds, 13 is milliseconds, 16 is microseconds and 19 is nanoseconds, with each step a factor of 1000
  • A timestamp is always UTC and carries no timezone; timezones only matter when a timestamp is rendered as a human-readable date
  • Parsing a 10-digit value as milliseconds yields a date near 1970-01-01, while parsing a 13-digit value as seconds lands tens of thousands of years in the future
  • A signed 32-bit integer overflows at 2147483647, which is 2038-01-19 03:14:07 UTC - the Year 2038 problem
  • Store timestamps in UTC and convert only at display time to avoid off-by-one-hour bugs around DST transitions

#The digit count tells you the unit

A Unix timestamp is an offset from 1970-01-01 00:00:00 UTC, and its length tells you the unit. Keep this table handy; it resolves most "the date is wrong" reports on sight.

DigitsUnitTypical sourceValid until
10secondsPHP time(), Linux date +%sYear 2286
13millisecondsJavaScript Date.now(), Java System.currentTimeMillis()~280,000 years
16microsecondsPython time.time_ns() // 1000-
19nanosecondsGo time.Now().UnixNano()-

The rule is simple: count the digits, then decide whether to divide by 1000. Adjacent magnitudes always differ by exactly 1000, which is why a unit mistake throws the date off by a factor of a thousand.

Warning

These two failures are the easiest to diagnose. Parsing a 13-digit value as seconds produces a date around 1970-01-01. Parsing a 10-digit value as milliseconds produces a date tens of thousands of years ahead. If your date lands in January 1970, milliseconds were almost certainly read as seconds.

#Why timezones are irrelevant to a timestamp

A timestamp identifies an absolute instant, and that instant does not change based on where the observer stands. Noon in Beijing on 2026-10-14 and 04:00 UTC on the same day are the same moment, so they share one timestamp.

A timezone only enters the picture at the last step, when that instant is rendered as a readable date string:

Timestamp (s)UTCBeijing (UTC+8)
01970-01-01 00:001970-01-01 08:00
17919504002026-10-14 04:002026-10-14 12:00

So the correct pattern is: store UTC timestamps everywhere in the database and API, and convert to the viewer's timezone only for display. Store local time as a string and cross-timezone debugging plus DST transitions will keep producing bugs.

Note

China does not observe daylight saving time, so teams serving only domestic users rarely hit this. If your service serves Europe or North America, expect a one-hour drift on DST switchover days - the usual root cause is storing a local-time string where a timestamp belongs.

#Three conversion scenarios

#Scenario 1: date to timestamp

Defaults differ per language and database, which is where cross-team bugs originate:

EnvironmentUnit returnedNote
JavaScriptmillisecondsDate.now() and new Date().getTime() are 13 digits
PHPsecondstime() and strtotime() are 10 digits
Pythonseconds (float)time.time() includes fractional part; use //1000 for ms
Gosecondstime.Now().Unix() for seconds, UnixMilli() for ms
MySQLsecondsUNIX_TIMESTAMP(); divide by 1000 to handle milliseconds

When values cross a language boundary, put the unit in the field name. created_at_ms prevents every future reader from having to guess.

If you also need a unique identifier per record, consider a time-ordered scheme - the v7 variant from UUID / GUID Generator increases with time, satisfying uniqueness and sortability at once.

#Scenario 2: timestamp to date

Paste the value into the Unix Timestamp tool - it infers the unit from the digit count and lets you override. On the command line:

Shell
# seconds to local time
date -d @1791950400

# milliseconds to seconds to local time
date -d @$((1791950400000/1000))

#Scenario 3: measuring an interval

Subtract values in the same unit. Durations are stated in seconds or milliseconds, and mixing them produces a result that is off by 1000x:

JSON
{ "created_at_ms": 1791950400000, "ttl_sec": 3600 }

Field names like these eliminate most misuse, because the unit travels with the value.

To confirm two datasets migrated consistently, put both timestamp columns side by side with Text Diff. And when inspecting a timestamp field in an API response, run it through JSON Formatter first so the field names are visible at a glance.

#The Year 2038 problem: when it still matters

The largest value a signed 32-bit integer holds is 2147483647, which corresponds to 2038-01-19 03:14:07 UTC. Past that instant, programs storing seconds in 32 bits overflow into negative numbers and report dates around 1901.

Do you still need to care? It depends on the environment:

EnvironmentAffectedWhy
64-bit Linux / macOS / WindowsNo64-bit timestamps last for billions of years
Modern runtimes (Go, Rust, Node, Java 8+)No64-bit internally
Embedded devices, legacy C, 32-bit systemsYesStill running in production everywhere
Database columns declared INT instead of BIGINTYesCommon in schemas designed a decade ago
Tip

Declare timestamp columns as BIGINT and store milliseconds in any new schema. The cost is four extra bytes per row, and the benefit is never having to migrate before 2038.

#Frequently asked questions

#What date is timestamp 0?

Timestamp 0 is 1970-01-01 00:00:00 UTC, which is 1970-01-01 08:00 in Beijing. It frequently serves as an "unset" default, so a table full of 1970 dates usually means the column was never written rather than that the event happened then. Negative values are legal and represent instants before 1970.

#Why is the time in my frontend off by 8 hours from the backend?

Two likely causes. Either the backend returned a local-time string and the frontend parsed it as UTC, or the backend treated local time as a timestamp. Diagnosis: divide the returned value by 3600 - if the difference is exactly the local UTC offset, the bug is in timezone handling, not in the timestamp format.

#Can I truncate a millisecond timestamp to seconds?

Yes, but understand the cost: truncation reduces precision to whole seconds, so records created within the same second can no longer be ordered. That is fine for counting how many events happened on a given day. Keep milliseconds if you sort by the value.

#Do timestamps account for leap seconds?

No. Unix time flattens leap seconds by defining a day as exactly 86400 seconds, so the system clock repeats or skips an instant when a leap second occurs. The resulting drift against UTC is negligible for almost all software; only astronomy and satellite systems handle it explicitly.

#How can I sanity-check a conversion?

Use two known anchors: 0 must render as 1970-01-01 00:00 UTC, and 1000000000 must render as 2001-09-09 01:46:40 UTC. If both are correct, the tool and timezone settings are fine and you can inspect the specific value. To reason about recurring intervals instead, use Cron Generator.

Tools used in this guide

Related guides