Timestamp Converter
Convert Unix timestamps in seconds or milliseconds to human-readable dates and back in real time, showing both your local timezone and UTC — ideal for developers and data processing.
What is a Unix Timestamp?
A Unix timestamp counts seconds since 00:00:00 UTC on 1 January 1970, excluding leap seconds. That origin is called the epoch. The value is a plain integer carrying no timezone, no format, and no locale: the same number describes the same absolute instant on every machine in every country.
That property is why logs, databases, cache expiry values, and API fields such as created_at carry timestamps instead of formatted date strings. A string has to be parsed before it can be compared, needs a separate timezone field, and means different things in different locales. An integer sorts directly, differences are meaningful, and it can be indexed.
One consequence is easy to miss: Unix time contains no leap seconds. Every day is exactly 86400 seconds long even when the physical day was 86401 seconds, which is what makes "add one day = add 86400 seconds" exact.
Seconds versus Milliseconds
The most common conversion mistake is unit confusion, not timezones. Second-level values in the 2020s have 10 digits; millisecond-level values have 13. Mixing them puts the result a factor of 1000 off, which shows up either as a date in 1970 or a five-digit year.
| Unit | Typical length | Example value | If misread as the other unit |
|---|---|---|---|
| Seconds (10 digits) | 10 digits | 1790090593 | Read as ms → 1970-01-21 |
| Milliseconds (13 digits) | 13 digits | 1790090593958 | Read as s → year 58695 |
Forcing a unit manually has a second use beyond double-checking: when a system emits non-standard precision — microseconds, or a counter in half-second steps — automatic detection guesses wrong, and switching the unit by hand is how you work out what the producer actually meant.
Why the 10^11 Boundary Exists
The tool treats any value at or above 100000000000 (10^11) as milliseconds and anything below it as seconds. The constant is not arbitrary; it is derived from how long a second-level timestamp would take to reach that size.
10^11 seconds is roughly 3169 years, so a second-level timestamp that large would not appear until the year 5138. But 10^11 milliseconds is only about 1157 days, which lands on 3 March 1973 — a millisecond value that has existed for half a century. For any foreseeable future, everything above the line is milliseconds and everything below is seconds.
| Input | Digits | Classified as | Result (UTC) | Why |
|---|---|---|---|---|
| 99999999999 | 11 | Seconds | 5138-11-16T09:46:39Z | Below the boundary |
| 100000000000 | 12 | Milliseconds | 1973-03-03T09:46:40Z | Equal to the boundary, so ms wins |
| 100000000001 | 12 | Milliseconds | 1973-03-03T09:46:40.001Z | One millisecond past it |
Note the tie-breaking: the boundary value itself is read as milliseconds, because the test is "greater than or equal". And 99999999999, despite having only 11 digits, falls below the line and is read as seconds — producing the year 5138, which looks absurd but demonstrates that the threshold sits comfortably high.
Landmark Timestamps
The values below work as reference points for sanity-checking magnitude and estimating an unfamiliar timestamp by eye.
| Moment | Seconds | Milliseconds | UTC rendering |
|---|---|---|---|
| Unix epoch | 0 | 0 | 1970-01-01T00:00:00Z |
| One second before | -1 | -1000 | 1969-12-31T23:59:59Z |
| Start of 2000 | 946684800 | 946684800000 | 2000-01-01T00:00:00Z |
| Start of 2020 | 1577836800 | 1577836800000 | 2020-01-01T00:00:00Z |
| Start of 2026 | 1767225600 | 1767225600000 | 2026-01-01T00:00:00Z |
| 32-bit signed limit | 2147483647 | Overflows | 2038-01-19T03:14:07Z |
Two constants cover most mental arithmetic: one day is 86400 seconds and a common year is 31536000 seconds. Dividing a timestamp by 86400 gives whole days since the epoch; the remainder is seconds since midnight UTC.
Worked Examples
- Seconds to a readable date: 1600000000 resolves to 2020-09-13T12:26:40Z, which is 2020-09-13 20:26:40 at UTC+8. Dividing by 86400 gives 18518.518 days and the remainder is 44800 seconds — exactly 12:26:40 — matching the tool output.
- Milliseconds to the same instant: 1600000000000 is auto-detected as milliseconds and yields the same 2020-09-13T12:26:40Z. Two values a thousand times apart describing one instant is the clearest demonstration that seconds and milliseconds are two graduations on a single axis.
- What a unit mistake looks like: reading 1600000000 as milliseconds gives 1970-01-19T12:26:40Z — the classic "it jumped back to 1970" report. Reading 1600000000000 as seconds gives the year +057971. Counting the digits before you convert rules out both.
- Date back to timestamp: picking 2026-01-01 00:00:00 UTC yields 1767225600 seconds and 1767225600000 milliseconds. Adding 28800 seconds (the UTC+8 offset) to the same instant yields 2026-01-01 08:00:00 Beijing time.
- Negative values: -1 gives 1969-12-31T23:59:59Z, one second before the epoch. Negative timestamps are not errors; they are legitimate pre-1970 instants and show up when auditing old log files.
- Elapsed-day arithmetic: 2026-10-09T00:00:00Z is 1791504000, and dividing by 86400 divides exactly to 20735 days. To compute "days ago", subtract the target from the current timestamp before dividing, and round down rather than to nearest, otherwise a 23-hour-old gap displays as "1 day ago".
The 2038 Problem
The canonical Unix timestamp type is a signed 32-bit integer, and its maximum value 2147483647 corresponds to 2038-01-19T03:14:07Z. One second later the counter wraps negative and the date falls back into 1970. This is the Year 2038 problem, and the fix is 64-bit storage: a signed 64-bit integer reaches about 9.22 × 10^18 seconds, comfortably past the year 290 million. Modern language and database date types are 64-bit, so the survivors are older embedded devices, legacy file formats, and database columns still typed int32. Migrate those before 2038 rather than after.
Timezones and Offsets
A timestamp is timezone-independent; the offset belongs to the display layer. The same instant rendered in three zones gives 12:26:40 at UTC, 20:26:40 at UTC+8, and 07:26:40 at UTC−5 for 1600000000 — the number never changes, only the wall clock does.
Offsets are whole seconds, so timezone arithmetic is simple addition: UTC+8 adds 28800 seconds, UTC+9 adds 32400, UTC−5 subtracts 18000. Fractional zones follow the same rule — India at UTC+5:30 is 19800 seconds and Nepal at UTC+5:45 is 20700. This is exactly why servers and databases store offsets in minutes rather than hours.
Daylight saving is the complication worth planning for. In regions that observe it, the offset changes twice a year, so "28800 seconds" is stable for China but not for Europe or North America. The safe pattern is to store UTC and convert only for display rather than baking a local offset into stored data.
Where Leap Seconds Went
Leap seconds exist because Earth's rotation is not uniform, and UTC has inserted them repeatedly. Unix time never does: it fixes every day at 86400 seconds even when the real physical day was 86401 seconds long.
The leap seconds are not lost — under POSIX convention they are folded into a neighbouring second, usually the one before or the one after, and each system picks its own placement. So a Unix timestamp is not a count of elapsed SI seconds: the timeline is uniform seconds, and it can sit tens of seconds away from atomic time. For orders, logs, and monitoring that gap is irrelevant; for tidal, clock-discipline, or atomic-clock work it is a real caveat.
Converting Without a Tool
Two constants cover most quick checks: one day is 86400 seconds and a common year is 31536000 seconds. Dividing a timestamp by 86400 gives the number of days since the epoch, and the remainder is the time of day in seconds — so the hour is the remainder divided by 3600. For 1600000000 that yields 18518 days plus 44800 seconds, or 12:26:40 UTC.
Offsets are then simple additions: UTC+8 adds 28800 seconds and UTC−5 subtracts 18000, with half-hour and three-quarter-hour zones following the same rule. That is why most systems store offsets in minutes rather than hours.
| Zone | Offset seconds | Applied to 1600000000 (UTC) |
|---|---|---|
| UTC | 0 | 2020-09-13 12:26:40 |
| UTC+8 (China) | +28800 | 2020-09-13 20:26:40 |
| UTC+9 (Japan) | +32400 | 2020-09-13 21:26:40 |
| UTC+5:30 (India) | +19800 | 2020-09-13 17:56:40 |
| UTC−5 | −18000 | 2020-09-13 07:26:40 |
Common Timestamp Formats
The same instant can be written several ways. Unix time is the storage-friendly form; ISO 8601 is the interchange form used by most APIs; HTTP-date appears in protocol headers. Converting between them does not change the instant, only the notation.
| Format | Value | Typical use |
|---|---|---|
| Unix seconds | 1600000000 | Databases, logs, sorting |
| ISO 8601 / RFC 3339 | 2020-09-13T12:26:40Z | JSON APIs, config files |
| HTTP-date (RFC 7231) | Sun, 13 Sep 2020 12:26:40 GMT | HTTP headers, caching |
| Human (UTC) | 2020-09-13 12:26:40 UTC | Reports, user interfaces |
Where 10 Digits and 13 Digits Come From
Different stacks made different choices. Unix time(), Java's System.currentTimeMillis(), and MySQL's UNIX_TIMESTAMP() all return seconds, so they yield 10-digit values. JavaScript's Date.now(), .NET's DateTimeOffset.ToUnixTimeMilliseconds(), and Go's time.Now().UnixMilli() return milliseconds, yielding 13-digit values. When two systems both store "user registered at" and disagree by a factor of 1000, the API contract simply never specified the unit. Documenting the unit in the field name — created_at_ms — prevents the whole class of bug.
Code-Level Pitfalls
The browser and Node both work in milliseconds internally, so a second-level timestamp must be multiplied before it is handed to the Date constructor: new Date(1700000000) yields 1970-01-20, while new Date(1700000000 * 1000) correctly gives 2023-11-14T22:13:20Z. Go the other way with Math.floor(Date.now() / 1000). Python defaults the other way, which is why cross-language bugs cluster here: datetime.datetime.utcfromtimestamp(1700000000) gives 2023-11-14T22:13:20Z, time.time() is a float in seconds, and int(time.time() * 1000) is the millisecond form.
Database column types are the next trap. MySQL's TIMESTAMP stores seconds with timezone conversion applied on write and read, so it carries UTC semantics; DATETIME stores the literal date and time with no conversion, which suits opening hours or birthdays. Pushing a 13-digit millisecond value into a TIMESTAMP column exceeds its range and is silently truncated or rejected — a frequent cause of "the time is wrong" reports that never mention milliseconds at all. JDBC types such as java.sql.Timestamp add their own millisecond-versus-nanosecond precision differences on top.
The remaining trap is treating a wall-clock time as UTC. A datetime-local input yields a naive local time with no zone attached, so something has to decide which zone it means before it is stored as a timestamp. Skipping that step is what produces a drift of several hours in cross-timezone features. Converting the suspect value back to a readable date and comparing it line by line against the log and the database row is the fastest way to localise the mismatch.
Practical Notes
- Unix time ignores leap seconds, so it is not a count of elapsed SI seconds; it is a count of 86400-second days since the epoch.
- Negative timestamps are valid and denote instants before 1970.
- Store timestamps in UTC and convert only for display; daylight saving rules change far more often than your storage format will.
- When a value looks about a thousand times too large or too small, the unit is almost always the cause.
- A date string without a zone cannot be converted unambiguously, so always record the offset or the Z suffix when storing text.
- Diagnose in order: confirm the magnitude looks right, then confirm the digit count, and only then chase the timezone.
Frequently Asked Questions
Why do some APIs return 10 digits and others 13?
Because they come from different runtimes. Unix time(), Java's System.currentTimeMillis(), and MySQL's UNIX_TIMESTAMP() produce seconds (10 digits), while JavaScript's Date.now(), .NET's ToUnixTimeMilliseconds(), and Go's UnixMilli() produce milliseconds (13 digits). Storing both in one column without recording the unit produces values that differ by 1000×. Name the unit explicitly — created_at_ms — or document it in the API contract.
Where does the 10^11 boundary come from, and how are edge values classified?
The constant is derived by asking how long a second-level timestamp would take to reach 1e11: roughly 3169 years, so the year 5138. Meanwhile 1e11 milliseconds is only about 1157 days — 3 March 1973 — a value that has existed for decades. Hence "≥ 1e11 means milliseconds". At the edge: 99999999999 sits just below and is read as seconds, giving 5138-11-16T09:46:39Z; 100000000000 equals the boundary and is read as milliseconds, giving 1973-03-03T09:46:40Z; 100000000001 is read as milliseconds, giving 1973-03-03T09:46:40.001Z. If your data really does contain a millisecond timestamp below 1e11 (anything before 1973), automatic detection fails and you must force the unit.
Why does Unix time exclude leap seconds, and where did they go?
Unix time's design goal is that a day is always 86400 seconds, which keeps date arithmetic deterministic; inserting a leap second would break that invariant. The seconds are not discarded — POSIX folds each leap second into an adjacent second, usually the preceding or following one, and leaves the exact placement to each implementation. A Unix timestamp therefore counts uniform seconds and can drift tens of seconds from atomic time. That gap is irrelevant for everyday scheduling but matters for timestamping, clock discipline, and any comparison against a reference clock.
What do MySQL's DATETIME and TIMESTAMP actually store?
TIMESTAMP stores seconds since the epoch with automatic timezone conversion on both write and read, spanning roughly 1970 to 2038 — it carries UTC semantics. DATETIME stores the literal date and time with no conversion and reaches back to the year 1000, which makes it the right choice for data with no timezone meaning such as business hours. For precision plus range, use DATETIME(3) or store milliseconds in a BIGINT. Writing a 13-digit millisecond value into a TIMESTAMP column overruns the supported range and is truncated or rejected outright.
Why does new Date(1700000000) need dividing by 1000 in Node?
JavaScript's Date represents instants in milliseconds internally, and the constructor argument is milliseconds too, so 1700000000 is interpreted as a moment in January 1970 rather than 2023. Use new Date(1700000000 * 1000) for 2023-11-14T22:13:20Z, and Math.floor(date.getTime() / 1000) to go back to seconds. Python inverts the convention, which is why this is such a common cross-language bug: time.time() returns float seconds, datetime.utcfromtimestamp() assumes seconds, and only the multiplied form gives milliseconds.
What should I watch for when turning a timestamp into "N days ago"?
Subtract the current timestamp first — dividing the target alone tells you its distance from the epoch, not from now. Then round down: a gap of 23 hours and 59 minutes is not yet a day, and rounding to nearest would display it as "1 day ago". Across a daylight saving transition one day is not 86400 seconds — it is 23 or 25 hours in local time — so fixed-86400 arithmetic drifts by an hour twice a year. And if you anchor to a fixed baseline such as "today at midnight", make sure the baseline uses the same timezone convention as the target, otherwise a morning entry already reads as a full day elapsed.
Related Reading
For day and week arithmetic around a given date, use the Date Calculator; for background on offsets and working-day rules, see our date calculation guide.