Skip to main content
ZEKILO Dev
Convert

Unix Time: Seconds vs Milliseconds and the 2038 Problem

Updated 2026.10.03 · 5 min read

Unix time counts seconds since 1970 in UTC. Learn to tell seconds from milliseconds by digit count, handle time zones, and see what overflows in 2038.

Open Unix Timestamp Converter

Unix time is the number of seconds since 1970-01-01T00:00:00Z, and it has no time zone. For present-day dates, a 10-digit value is in seconds and a 13-digit value is in milliseconds, and mixing the two up is the most common timestamp bug. The year 2038 problem affects only places that keep the seconds in a signed 32-bit integer, which runs out after 2038-01-19T03:14:07Z. Paste any value into the Unix Timestamp Converter to see which unit it was read as and the date in UTC and in your own time zone.

Seconds, milliseconds, microseconds, nanoseconds

The same instant, 2026-01-01T00:00:00Z, written in four units:

Unit Value Digits Where you meet it
Seconds 1767225600 10 POSIX time(), the exp and iat claims of a JWT
Milliseconds 1767225600000 13 JavaScript Date.now()
Microseconds 1767225600000000 16 Logs and databases with microsecond precision
Nanoseconds 1767225600000000000 19 Tracing and high-resolution clocks

Counting digits works because of where we are on the timeline: a seconds value has 10 digits from 2001-09-09T01:46:40Z until 2286-11-20T17:46:39Z. The converter uses the same idea when the unit is set to automatic. A number whose absolute value is below 10^11 is read as seconds, below 10^14 as milliseconds, below 10^17 as microseconds, and anything larger as nanoseconds, and a badge shows which unit was chosen. 1767225600123 is therefore read as milliseconds and becomes 2026-01-01T00:00:00.123Z.

Mixing units gives results that are easy to recognize:

What happened Result
1767225600 (seconds) read as milliseconds 1970-01-21T10:53:45.600Z
1767225600000 (milliseconds) read as seconds A date in the year 57971

A date in January 1970 almost always means that seconds were passed to something that expects milliseconds. In JavaScript, new Date(1767225600) is that January 1970 date, and new Date(1767225600 * 1000) is the intended one. Going the other way, RFC 7519 defines the JWT claims exp and iat as seconds, so compare them with Math.floor(Date.now() / 1000) and not with Date.now() itself. The JWT Decoder shows those claims as dates.

Nanosecond values need one more precaution in JavaScript. A Number represents every integer exactly only up to 9,007,199,254,740,991 (Number.MAX_SAFE_INTEGER), and the nanosecond value above is larger than that: Number('1767225600000000001') returns 1767225600000000000, losing the last digit. Keep such values as a string or a BigInt. The microsecond value still fits.

Unix time has no time zone

POSIX defines the Epoch as 1970-01-01 00:00:00 UTC, so a Unix timestamp names one instant that is the same number everywhere. A time zone only enters when the number is turned into a calendar date for people to read.

1767225600  =  2026-01-01T00:00:00Z          UTC
            =  2026-01-01 09:00:00           Asia/Seoul, Asia/Tokyo (UTC+9)

In the other direction, the local time 2026-01-01 09:00:00 with the time zone Asia/Seoul converts back to 1767225600.

The usual pitfall is a date string without an offset. ECMA-262 states that when the UTC offset is absent, date-only forms are interpreted as UTC and date-time forms as local time. On a machine set to Asia/Seoul:

Date.parse('2026-01-01')             →  1767225600000   read as UTC
Date.parse('2026-01-01T00:00:00')    →  1767193200000   read as local time, 9 hours earlier
Date.parse('2026-01-01T00:00:00Z')   →  1767225600000   explicit UTC

The same code gives a different number on a server set to UTC. Write the Z or an offset such as +09:00 every time; the RFC 3339 timestamp format requires one.

One more property follows from the definition. POSIX says every day is accounted for by exactly 86,400 seconds and leaves the relationship to actual UTC unspecified, so Unix time does not count leap seconds, and ECMAScript uses the same model.

The year 2038 problem

A signed 32-bit integer holds values up to 2,147,483,647. Read as seconds, that is:

 2147483647  →  2038-01-19T03:14:07Z   (12:14:07 in Seoul and Tokyo)
-2147483648  →  1901-12-13T20:45:52Z   where the counter wraps one second later

Software that still stores seconds in a signed 32-bit field can jump back to 1901 or fail at that moment. Whether you are affected depends only on how the value is stored:

Storage Last representable time
Signed 32-bit seconds 2038-01-19T03:14:07Z
Unsigned 32-bit seconds 2106-02-07T06:28:15Z
Signed 64-bit seconds About 292 billion years from 1970
JavaScript Date (milliseconds) 275760-09-13T00:00:00Z (±8,640,000,000,000,000 ms)
MySQL TIMESTAMP column (MySQL 8.4 manual) ‘2038-01-19 03:14:07’ UTC

The problem does not wait for 2038. Any calculation that reaches past the limit already hits it: adding 15 years of 365 days to 1767225600 gives 2240265600 (2040-12-28T00:00:00Z), which no longer fits in a signed 32-bit field. Expiry dates, subscriptions and certificates are the typical cases.

Things worth checking:

  • Integer columns that hold seconds. A signed 32-bit INT has the same limit of 2,147,483,647. Use a 64-bit integer type.
  • MySQL TIMESTAMP. The manual gives its range as ‘1970-01-01 00:00:01’ UTC to ‘2038-01-19 03:14:07’ UTC, while DATETIME runs from ‘1000-01-01 00:00:00’ to ‘9999-12-31 23:59:59’. MySQL converts TIMESTAMP values to UTC for storage and does not do so for DATETIME, so changing the type also changes time zone behavior.
  • 32-bit builds and binary formats. Old 32-bit time_t, file formats and network protocols with a fixed 32-bit time field.

Practical rules

  • Store instants in UTC, either as a 64-bit integer or as an RFC 3339 string with Z, and convert to local time only for display.
  • Put the unit in the name: expires_at_ms is harder to misuse than expires.
  • Do not send nanosecond integers through JSON to JavaScript as plain numbers.
  • Never parse a date string that has no offset on the assumption that “the server is in UTC”.

Converting with ZEKILO Dev

  1. Open the Unix Timestamp Converter and paste a number. The unit is detected automatically, or you can set seconds, milliseconds, microseconds or nanoseconds yourself.
  2. Read the result as ISO 8601 in UTC, as RFC 2822, in a written-out English date format and as relative time. The browser’s time zone, UTC, KST and JST are shown together, and other zones can be chosen by IANA name.
  3. To go from a date to a timestamp, enter ISO 8601, YYYY-MM-DD HH:mm:ss with a selected time zone, or RFC 2822.

For 2147483647 and later values, the tool adds a note about the 32-bit time_t limit. Everything is computed in your browser.

Summary

  • Unix time is seconds since 1970-01-01T00:00:00Z. It has no time zone and does not count leap seconds.
  • For current dates, 10 digits are seconds, 13 milliseconds, 16 microseconds and 19 nanoseconds.
  • A date in January 1970 usually means seconds were read as milliseconds.
  • Signed 32-bit seconds end at 2038-01-19T03:14:07Z. Use 64-bit storage, and check INT and MySQL TIMESTAMP columns.

Tools for this guide

Sources

More guides