Working with Unix Timestamps: Seconds vs Milliseconds, Time Zones and the 2038 Overflow
What a Unix timestamp is, seconds vs milliseconds, converting timestamps in JavaScript, Python and SQL, time zone best practices, and how the Year 2038 problem can still break your software.
Almost every system you build stores time somewhere: log entries, orders, sessions, token expiry. Under the hood, most of them rely on the Unix timestamp. It looks like a simple number, but it causes a surprising number of bugs, from off-by-1000 errors to a looming overflow in January 2038.
What is a Unix timestamp?
A Unix timestamp (also called Unix time, POSIX time or epoch time) is the number of seconds that have elapsed since 00:00:00 UTC on 1 January 1970, the "Unix epoch".
0 → 1970-01-01 00:00:00 UTC
1700000000 → 2023-11-14 22:13:20 UTC
2147483647 → 2038-01-19 03:14:07 UTC
Its advantages are why it is everywhere:
- Time-zone independent. The same instant has the same timestamp in Hanoi and New York.
- Easy to compare and calculate. "Is this token expired?" is just
now > exp. "One day later" is+ 86400. - Compact. A single integer is cheap to store, index and send over the network.
You can convert any timestamp to a human-readable date (and back) with our Unix Timestamp Converter.
Seconds or milliseconds?
The most common timestamp bug is mixing up units:
| Digits | Unit | Typical source |
|---|---|---|
10 (e.g. 1700000000) |
Seconds | Unix/Linux, PHP, Python time.time() (as float), JWT exp/iat, most APIs |
13 (e.g. 1700000000000) |
Milliseconds | JavaScript Date.now(), Java System.currentTimeMillis() |
| 16 / 19 | Micro / nanoseconds | Databases, Go time.UnixNano(), tracing systems |
If a date shows up as January 1970, you probably passed seconds where milliseconds were expected. If it shows a year far in the future like 55,000, you did the opposite.
Converting in popular languages
JavaScript
// current time
const seconds = Math.floor(Date.now() / 1000);
// timestamp → Date (note the * 1000)
const date = new Date(1700000000 * 1000);
date.toISOString(); // "2023-11-14T22:13:20.000Z"
Python
from datetime import datetime, timezone
ts = int(datetime.now(timezone.utc).timestamp())
datetime.fromtimestamp(1700000000, tz=timezone.utc)
# datetime.datetime(2023, 11, 14, 22, 13, 20, tzinfo=datetime.timezone.utc)
SQL
-- PostgreSQL
SELECT to_timestamp(1700000000); -- timestamptz
SELECT extract(epoch FROM now())::bigint;
-- MySQL
SELECT FROM_UNIXTIME(1700000000);
SELECT UNIX_TIMESTAMP();
Shell
date +%s # current timestamp
date -u -d @1700000000 # GNU date: Tue Nov 14 22:13:20 UTC 2023
Time zone best practices
A timestamp has no time zone. Problems appear when you convert it to a local date:
- Store instants in UTC (as a timestamp or a
timestamptzcolumn) and convert to local time only when displaying. - Send ISO 8601 strings with an offset in APIs, e.g.
2023-11-14T22:13:20Zor2023-11-15T05:13:20+07:00, if you do not use numeric timestamps. - Store the user's time zone name (for example
Asia/Ho_Chi_Minh), not just an offset, when you need to schedule future events. Offsets change with daylight saving time and political decisions. - Remember scheduled jobs run in the server's zone. A cron job on a UTC server that should run at 09:00 in Vietnam needs
0 2 * * *. Check your schedule with the Cron Expression Parser.
Unix time also ignores leap seconds: every day is treated as exactly 86,400 seconds. For almost all applications this is exactly what you want.
The Year 2038 problem
Historically, many systems stored Unix time in a signed 32-bit integer (time_t on 32-bit platforms). The largest value such an integer can hold is:
2^31 − 1 = 2,147,483,647 → 2038-01-19 03:14:07 UTC
One second later the value overflows and wraps around to −2,147,483,648, which is interpreted as 13 December 1901, 20:45:52 UTC. Software that is not prepared may suddenly think certificates are not yet valid, reject logins, or compute negative durations.
Is it still a real risk?
Modern 64-bit operating systems use a 64-bit time_t, which lasts for about 292 billion years. Linux has also added 64-bit time support for 32-bit architectures (kernel 5.6 and glibc 2.34). But 2038 can still bite in places such as:
- Embedded devices and IoT running old 32-bit firmware for decades: routers, cars, industrial controllers, medical equipment.
- Database columns: the MySQL
TIMESTAMPcolumn type is still limited to2038-01-19 03:14:07UTC. UseDATETIMEor aBIGINTinstead for dates that may go beyond that. - File formats and protocols with 32-bit time fields.
- Application code that casts timestamps to 32-bit integers, for example
int32fields in Protocol Buffers orINTcolumns.
And 2038 is not that far away. A 15-year mortgage or a long-lived certificate created today already has an end date after it.
How to prepare
- Use 64-bit integers (
BIGINT,int64) for timestamps everywhere. - Avoid MySQL
TIMESTAMPfor future dates. - Test your system with dates after 2038, for example by generating an expiry of
2147483648and checking what happens. - Audit embedded and third-party components for 32-bit time handling.
Conclusion
Unix timestamps are simple and powerful: one integer, one universal instant. To avoid bugs, always know which unit (seconds or milliseconds) you are handling, store in UTC, convert to local time only for display, and make sure nothing in your stack squeezes time into 32 bits.
Need to decode a timestamp from a log or a JWT right now? Our free Unix Timestamp Converter converts seconds and milliseconds to dates in any time zone, directly in your browser.
Written by
Nguyễn Thành Nam
Full-Stack Engineer · Backend & Platform
Full-stack engineer with 9+ years of experience building high-traffic APIs and platforms with Go, NestJS, PHP (Laravel, Phalcon) and Node.js. Specializes in GraphQL API design, real-time systems and performance optimization, and builds NICETOOL.dev for the developer community.