Unix Timestamps Explained: Seconds vs Milliseconds, Timezones and 2038
By Tahsin Abrar · Updated
A Unix timestamp — also called epoch time or POSIX time — is the number of seconds that have elapsed since 00:00:00 UTC on 1 January 1970, ignoring leap seconds. It is a single integer with no timezone attached, which is exactly why APIs, databases, logs and JWTs love it.
This guide covers how to read timestamps, the seconds-versus-milliseconds trap, conversions in common languages, and the edge cases that cause real bugs. The Unix Timestamp Converter converts in both directions and shows the current epoch time live.
Try itOpen the Unix Timestamp Converter — runs in your browserSeconds, milliseconds, or microseconds?
Different systems count in different units, and mixing them up gives dates in 1970 or tens of thousands of years in the future. The number of digits is the quickest tell for present-day dates:
| Digits | Unit | Example | Used by |
|---|---|---|---|
| 10 | Seconds | 1791500000 | Unix tools, PHP time(), Python time.time() (as float), JWT exp/iat, most APIs |
| 13 | Milliseconds | 1791500000000 | JavaScript Date.now(), Java System.currentTimeMillis(), many JSON APIs |
| 16 | Microseconds | 1791500000000000 | PostgreSQL internals, some tracing systems |
| 19 | Nanoseconds | 1791500000000000000 | Go time.UnixNano(), Prometheus, InfluxDB |
Classic bug: passing a seconds value to JavaScript's new Date(), which expects milliseconds. new Date(1791500000) is 21 January 1970. Multiply by 1000 first.
Converting in code
JavaScript
Math.floor(Date.now() / 1000); // now, in seconds
new Date(1791500000 * 1000).toISOString(); // seconds → ISO 8601 (UTC)
Date.parse("2026-10-09T12:00:00Z") / 1000; // ISO → secondsPython
import time, datetime as dt
int(time.time()) # now
dt.datetime.fromtimestamp(1791500000, tz=dt.timezone.utc) # → aware datetime
int(dt.datetime(2026, 10, 9, tzinfo=dt.timezone.utc).timestamp())SQL
-- PostgreSQL
SELECT to_timestamp(1791500000); -- timestamptz
SELECT extract(epoch FROM now())::bigint;
-- MySQL
SELECT FROM_UNIXTIME(1791500000); -- in the session time zone
SELECT UNIX_TIMESTAMP();Shell
date +%s # now
date -u -d @1791500000 # GNU date (Linux)
date -u -r 1791500000 # BSD date (macOS)Timezones: the timestamp has none
A Unix timestamp identifies an instant, the same everywhere on Earth. Timezones only matter when you display it or parse a human-written date. Most bugs come from parsing a date string without a zone:
new Date("2026-10-09")(date only) is parsed as UTC midnight, butnew Date("2026-10-09T00:00")(date and time, no offset) is parsed as local time. Always includeZor an offset like+06:00.- Python's
datetime.fromtimestamp(ts)withouttz=returns a naive datetime in the server's local zone;datetime.utcfromtimestamp()is deprecated since Python 3.12. - MySQL's
FROM_UNIXTIME()andTIMESTAMPcolumns use the session time zone;DATETIMEcolumns store no zone at all.
A good rule: store and transmit instants as UTC (epoch numbers or ISO 8601 with Z), and convert to a user's zone only at the edge, in the UI. The timezone tab of the time tools helps check what a moment looks like across zones.
Leap seconds and the Year 2038 problem
Unix time pretends every day has exactly 86,400 seconds, so leap seconds are not counted; during one, clocks repeat or smear a second. For application code this almost never matters, and leap seconds are scheduled to be abolished by 2035.
The Year 2038 problem is more concrete. A signed 32-bit integer can hold at most 2,147,483,647, which as a Unix timestamp is 03:14:07 UTC on 19 January 2038. One second later it overflows to a negative number — December 1901. Modern 64-bit operating systems and languages are fine, but watch for:
- Database columns declared
INTto hold timestamps — useBIGINT. - MySQL's
TIMESTAMPtype, whose range ends in 2038;DATETIMEgoes to year 9999. - Embedded devices, old file formats and binary protocols with 32-bit time fields.
- Expiry dates already more than 12 years out, such as long-lived certificates or long-term schedules, which already cross the limit today.
Timestamps you will meet in JWTs
JSON Web Tokens use NumericDate values — seconds, not milliseconds — for exp (expiry), iat (issued at) and nbf (not before). A token whose exp looks like 1791500000 is fine; a 13-digit value means the issuer used milliseconds and most libraries will treat the token as valid for tens of thousands of years. The JWT Decoder shows these claims as readable dates.
FAQ
- Is a Unix timestamp always in UTC?
- Yes. It counts seconds since 1970-01-01T00:00:00Z, so it has no timezone of its own. Only the human-readable form you convert it to depends on a timezone.
- How do I know if a timestamp is in seconds or milliseconds?
- For current dates, 10 digits means seconds and 13 digits means milliseconds. If converting gives a date in January 1970, you probably passed seconds to something that expects milliseconds.
- What happens in 2038?
- Systems that store Unix time in a signed 32-bit integer overflow at 03:14:07 UTC on 19 January 2038. 64-bit systems are unaffected, but old database columns, file formats and embedded devices need checking.
More guides
- How JWT Signatures Work (HS256 vs RS256, Explained)What the three parts of a JSON Web Token are, how the signature is computed, why decoding is not verifying, and the mistakes that lead to JWT vulnerabilities.
- Cron Syntax Cheatsheet: Fields, Operators and 25 Ready-Made SchedulesA practical reference to the five-field cron format, the special characters, common schedules you can copy, and the gotchas in crontab, GitHub Actions and Kubernetes.
- Fixing "Unexpected token" and Other JSON Parse ErrorsWhat JSON.parse errors like "Unexpected token < in JSON", "Unexpected end of JSON input" and "Expected double-quoted property name" really mean, and how to fix each one.