A Unix timestamp, also called epoch time or Unix time, is the number of seconds that have passed since 00:00:00 UTC on January 1, 1970, not counting leap seconds. For example, 1735689600 is exactly 2025-01-01 00:00:00 UTC, and that same number means the same instant everywhere on Earth.
Because it's a single integer with no time zone attached, Unix time is what sits underneath most created_at columns, JWT expiry claims and log lines. This guide covers the seconds-vs-milliseconds trap, how to convert timestamps in JavaScript, Python, SQL and Excel, how leap seconds are handled, negative timestamps, and the exact moment the Year 2038 problem hits. To convert a value right now, paste it into the Timestamp Converter.
What a Unix timestamp represents
The starting point, 1970-01-01 00:00:00 UTC, is called the Unix epoch. A timestamp is a count of seconds from that moment: 0 is the epoch itself, 86400 is one day later, and 1000000000 is 2001-09-09 01:46:40 UTC. The number carries no time zone. Time zones only enter when you turn the number into a calendar date and clock time for display, so 1735689600 shows as midnight on New Year's Day in London but as 7:00 p.m. on December 31 in New York, which is five hours behind UTC in winter. It's still one moment.
That's why the terms are used interchangeably. "Epoch time" emphasizes the starting point, "Unix time" the system it came from, and "POSIX time" the standard that defines it. All three mean the same count.
Why the epoch is January 1, 1970
Nothing significant happened on that date. It's a round starting point close to when Unix was being developed at Bell Labs in the early 1970s. Choosing a recent date kept the numbers small for the hardware of the time. Once the convention was set it spread far beyond Unix, and today languages, databases and operating systems on every platform count from the same moment, which is what makes a timestamp portable between them.
Seconds vs milliseconds: 10 digits or 13
Standard Unix time is in seconds, but JavaScript's Date.now(), Java's System.currentTimeMillis() and many web APIs use milliseconds since the same epoch. The digit count tells you which one you have:
| Digits | Unit | Example for 2025-01-01 00:00:00 UTC |
|---|---|---|
| 10 | Seconds | 1735689600 |
| 13 | Milliseconds | 1735689600000 |
| 16 | Microseconds | 1735689600000000 |
| 19 | Nanoseconds | 1735689600000000000 |
Every 10-digit seconds value falls between 2001-09-09 01:46:40 UTC (1000000000) and 2286-11-20 17:46:39 UTC (9999999999), and 13-digit millisecond values cover the same span. So for any date in that window, the digit count is a reliable test.
Mixing the units fails loudly in one direction and quietly in the other. Read a 2026 millisecond value as seconds and you land around the year 57,971. Go the other way, as in new Date(1735689600) in JavaScript, which expects milliseconds, and you get 1970-01-21T02:08:09.600Z: a plausible-looking date that's simply wrong.
How to convert a Unix timestamp
Each snippet below was run against the timestamp 1735689600 and returned 2025-01-01 00:00:00 UTC, or turned that date back into 1735689600.
JavaScript
- Current time in seconds:
Math.floor(Date.now() / 1000) - Timestamp to date:
new Date(1735689600 * 1000).toISOString()returns2025-01-01T00:00:00.000Z. - Date to timestamp:
Date.parse("2025-01-01T00:00:00Z") / 1000returns1735689600.
Python
- Current time:
int(time.time()) - Timestamp to date:
datetime.fromtimestamp(1735689600, tz=timezone.utc)returns2025-01-01 00:00:00+00:00. - Date to timestamp:
datetime(2025, 1, 1, tzinfo=timezone.utc).timestamp()returns1735689600.0.
Always pass a time zone. Without tz=, fromtimestamp() returns the server's local time, and a naive datetime passed to .timestamp() is assumed to be local time too, so the same code gives different answers on machines set to different zones.
SQL
- PostgreSQL:
to_timestamp(1735689600)converts to atimestamptz, andextract(epoch from now())goes the other way. - MySQL:
FROM_UNIXTIME(1735689600)andUNIX_TIMESTAMP(). The MySQL manual notes that results are in the session time zone, so settime_zone = '+00:00'if you want UTC. - SQLite:
datetime(1735689600, 'unixepoch')returns2025-01-01 00:00:00, andunixepoch('2025-01-01 00:00:00')(SQLite 3.38 and later) orstrftime('%s', ...)converts back. - SQL Server:
DATEADD(second, 1735689600, '1970-01-01'). In versions where the number argument is anint, values past 2,147,483,647 overflow, which is the 2038 limit described below.
Excel
Excel stores dates as a count of days, so divide seconds by 86,400 and add the epoch:
=A2/86400 + DATE(1970,1,1) seconds to date
=A2/86400000 + DATE(1970,1,1) milliseconds to date
=(A2 - DATE(1970,1,1)) * 86400 date to seconds
Format the result cell as a date and time, for example yyyy-mm-dd hh:mm:ss, or you'll see a plain number like 45658. The result is in UTC, because Excel dates carry no time zone. To show US Eastern Standard Time, subtract 5/24. Using DATE(1970,1,1) rather than the hard-coded serial number 25569 keeps the formula correct in workbooks that use the 1904 date system. We tested these formulas in LibreOffice Calc, which uses the same date-serial approach.
Command line
date +%s prints the current timestamp. To convert one, GNU/Linux uses date -u -d @1735689600 and macOS/BSD uses date -u -r 1735689600.
Leap seconds: Unix time ignores them
POSIX, the standard that defines Unix time, says that "each and every day shall be accounted for by exactly 86400 seconds." Inserted leap seconds are simply not counted. The Linux time(2) manual page puts it this way: Linux systems normally follow the POSIX requirement that the value ignore leap seconds, so it "is not the same as the actual number of seconds" since the epoch.
In practice, when a leap second occurs, systems either repeat a timestamp or "smear" the extra second by running the clock slightly slow for several hours. The payoff is simple math: dividing any timestamp by 86,400 gives whole days since 1970, and every day boundary is a multiple of 86,400. The cost is that subtracting two timestamps across a leap second can be off by one second from the true elapsed time.
The gap is small and has stopped growing for now. According to IERS Bulletin C (republished here), UTC has been 37 seconds behind International Atomic Time (TAI) since January 1, 2017. In 2022 the General Conference on Weights and Measures decided that the maximum allowed difference between UT1 and UTC will be increased in, or before, 2035, which is expected to end leap seconds as they work today.
The Year 2038 problem
Older systems store Unix time as a signed 32-bit integer. The largest value it can hold is 231 − 1 = 2,147,483,647, and this Python snippet shows exactly when that runs out:
from datetime import datetime, timedelta, timezone
epoch = datetime(1970, 1, 1, tzinfo=timezone.utc)
print(epoch + timedelta(seconds=2**31 - 1)) # 2038-01-19 03:14:07+00:00
print(epoch + timedelta(seconds=-2**31)) # 1901-12-13 20:45:52+00:00
So the last representable moment is 03:14:07 UTC on Tuesday, January 19, 2038. One second later, a signed 32-bit counter wraps around to −2,147,483,648, which reads as 20:45:52 UTC on December 13, 1901. In US Eastern time the overflow moment is 10:14:07 p.m. on Monday, January 18, 2038.
- Unsigned 32-bit storage lasts until 2106-02-07 06:28:15 UTC but can't represent dates before 1970.
- Signed 64-bit storage, the norm on modern systems, reaches about 292 billion years in each direction.
- Databases have their own ceilings. MySQL's documentation gives
FROM_UNIXTIMEa maximum of 2038-01-19 03:14:07 UTC on 32-bit platforms and 3001-01-18 23:59:59 UTC on 64-bit platforms.
The risk sits mostly in embedded devices, old file formats and database columns declared as 32-bit integers, not in modern languages. JavaScript, for example, handles 2147483648 seconds (2038-01-19 03:14:08 UTC) without trouble.
Negative timestamps: dates before 1970
A negative timestamp counts backward from the epoch. -1 is 1969-12-31 23:59:59 UTC and -86400 is 1969-12-31 00:00:00 UTC. PostgreSQL, SQLite, Python and JavaScript all accept negative values; we checked each one.
Watch the rounding when you convert negative milliseconds to seconds. −1,500 ms is 1969-12-31 23:59:58.500 UTC, so the whole-second value is −2. Math.floor(-1500 / 1000) gives −2, which is correct, but Math.trunc() or Python's int(-1.5) gives −1, half a second in the future. Python's floor division, -1500 // 1000, gives −2.
Auto-detects seconds vs. milliseconds and shows both UTC and your local time.
Timestamp Converter →Notable timestamps
| Timestamp | UTC date and time | Why it's notable |
|---|---|---|
| 0 | 1970-01-01 00:00:00 | The epoch itself |
| 1000000000 | 2001-09-09 01:46:40 | First 10-digit timestamp |
| 1234567890 | 2009-02-13 23:31:30 | A popular developer milestone |
| 1735689600 | 2025-01-01 00:00:00 | Start of 2025 |
| 1767225600 | 2026-01-01 00:00:00 | Start of 2026 |
| 2000000000 | 2033-05-18 03:33:20 | 2 billion seconds |
| 2147483647 | 2038-01-19 03:14:07 | Largest signed 32-bit value |
| -2147483648 | 1901-12-13 20:45:52 | Where a 32-bit value wraps to after overflow |
Timestamps vs ISO 8601 strings
APIs often send dates as strings like 2025-01-01T00:00:00Z, a format from ISO 8601 that RFC 3339 profiles for internet use. The trailing Z means UTC, and an offset like -05:00 means five hours behind UTC. Strings are easier to read in logs. Integers are smaller, sort correctly as numbers, and can't carry an ambiguous time zone. Many systems store integers and display strings.
Where you'll see Unix time
- JWTs: the
exp,iatandnbfclaims are NumericDate values in seconds, as defined in RFC 7519. Inspect them with the JWT Decoder. - Cron and schedulers: jobs are written in local clock terms, but many schedulers log run times as epoch values. See the Cron Expression Generator.
- Cookies: the
Max-Ageattribute is a number of seconds, whileExpiresis a date string. - Time zone work: converting a timestamp to local time is where UTC vs local confusion starts; see UTC vs GMT.
Frequently asked questions
What is epoch time?
Epoch time is another name for Unix time: the number of seconds since the Unix epoch, 00:00:00 UTC on January 1, 1970, not counting leap seconds. For example, 1735689600 is 2025-01-01 00:00:00 UTC.
Is a Unix timestamp always in UTC?
Yes. The number counts seconds from a fixed UTC moment and has no time zone of its own. A time zone is applied only when the timestamp is shown as a local date and time, so the same value displays differently in New York and Tokyo while meaning the same instant.
How can I tell if a timestamp is in seconds or milliseconds?
Count the digits. For dates between 2001 and 2286, a seconds timestamp has 10 digits and a milliseconds timestamp has 13. JavaScript's Date.now() returns milliseconds, so divide by 1,000 to get standard Unix seconds.
What happens on January 19, 2038?
At 03:14:07 UTC on January 19, 2038, a signed 32-bit timestamp reaches its maximum of 2,147,483,647. One second later it wraps to -2,147,483,648, which reads as December 13, 1901. Systems that store time as 64-bit integers are not affected.
Does Unix time count leap seconds?
No. POSIX defines every day as exactly 86,400 seconds, so inserted leap seconds are not counted. This keeps date arithmetic simple, at the cost of a timestamp difference being off by one second across a leap second.
Can a Unix timestamp be negative?
Yes. A negative timestamp is a moment before 1970. For example, -86400 is 1969-12-31 00:00:00 UTC. When converting negative milliseconds to seconds, round down with floor rather than truncating, or the result will be off by one second.
How do I get the current Unix timestamp in JavaScript?
Use Math.floor(Date.now() / 1000). Date.now() returns milliseconds since the epoch, so dividing by 1,000 and rounding down gives the standard seconds-based Unix timestamp.