Guide · Units

Why Your Unix Timestamp Is Off by Hours (or 55 Years)

Updated 2026-08-29 · 11 min read

The dashboard says January 1970. The standup is five and a half hours late. Excel prints 1904. Search calls it unix timestamp wrong timezone. Half the time it is not a timezone at all. Unix time is an instant: a count of seconds (or milliseconds) since 1970-01-01 00:00:00 UTC. It does not contain Kolkata, Chicago, or DST. The human date is that instant printed in some calendar and some offset.

How to convert Unix timestamps to dates is the happy path: count the digits, convert in the browser, label the result with UTC. This article is the incident - why the number you already have is lying, which language APIs disagree on units, and why you should not upload a production log dump to finish the conversion.

Unix Timestamp Converter on DevOkk.com does the integer in the browser. No account. Copy one field. Leave the rest of the incident JSON on disk.

Hours versus 55 years

Two failure clusters look similar in a ticket and are not the same math.

Off by hours. You treated a UTC instant as local wall time, or the reverse. IST is UTC+5:30. PDT is UTC−7 in winter and UTC−8 is not the same as “Pacific” in July. DST is a civil-time rule. It is not stored in the integer. Timezone Converter is the display step after you already have the instant. The zone primer is How to convert time across time zones.

Off by about 55 years. You treated seconds as milliseconds or milliseconds as seconds. In this decade, a Unix second is ten digits (1700000000). A Unix millisecond is thirteen (1700000000000). Feed ten digits to new Date(n) in JavaScript and you get 1970-01-20, because JavaScript’s Date constructor counts milliseconds. That is not “timezone wrong.” That is 1.7 million seconds into 1970. 2023 minus 1970 is the 53-year-shaped screenshot people round to “55 years” in Slack.

The far-future twin is the reverse: Date.now() is already milliseconds. Store it in a column that FROM_UNIXTIME reads as seconds and MySQL will try to print a year in the 50,000s, or overflow, depending on the type. Same mix-up, opposite direction.

Fix units before you “correct” the zone.

Count digits before you blame IST

In the 2020s the cheap check is length:

  • 10 digits - seconds until the year 2286. Example: 1700000000
  • 13 digits - milliseconds. Example: 1700000000000
  • 16 digits - microseconds (some databases, not typical JS)
  • 19 digits - nanoseconds (Go UnixNano, some tracers)

Floats confuse the count. Python time.time() returns seconds as a float (1700000000.123). That is still seconds. JavaScript Date.now() returns an integer millisecond count. Java Instant.now() is a seconds-and-nanos pair. APIs that ship "createdAt": 1700000000 with no unit are doing you no favours; createdAtMs in the field name is documentation.

Rule of thumb: year 1970 on a current log → you treated seconds as milliseconds (or divided by 1000 too many times). Year 50,000+ → you treated milliseconds as seconds (or multiplied when you should have divided). Year 2038 on a device that has been up too long → look at signed 32-bit overflow before NTP.

Paste the integer into Unix Timestamp Converter. Do not paste the surrounding payload until you have redacted it. If you only need to extract the field from broken JSON, JSON Formatter is the local syntax pass - How to fix invalid JSON if commas and trailing junk are the blocker.

Worked example: 1700000000 vs 1700000000000

1700000000 seconds after the epoch is 2023-11-14 22:13:20 UTC.

Convert 1700000000000 as milliseconds. You should land on the same instant. That pair is the fixture. If both conversions do not agree, the tool or your unit assumption is wrong.

Convert 1700000000 as milliseconds. You land on 1970-01-20 11:33:20 UTC. That is the screenshot. The log is not from the Nixon administration. The parser used the JavaScript epoch unit on a POSIX second.

Convert 1700000000000 as seconds. You are looking at a date tens of millennia out. That is the “year 55869” row in a warehouse report.

0 is the epoch itself. 2147483647 is the last second a signed 32-bit Unix clock can represent: 2038-01-19 03:14:07 UTC. Embedded devices still store time that way. If a sensor reports 1901 or 1970 after a long uptime, check 32-bit wrap before you check “timezone.”

Copy results with the zone: 2023-11-14 22:13:20 UTC, not 2023-11-14. The date alone is wrong for anyone west of UTC who is still on the 14th evening.

JavaScript Date.now() is milliseconds

Date.now() and +new Date() and Date.parse results you feed back into new Date(ms) are milliseconds. new Date(1700000000) is the 1970 screenshot. new Date(1700000000000) is November 2023.

getTime() is also milliseconds. getSeconds() is the 0–59 clock field, not Unix seconds. Mixing those names in a review is a classic bug.

ISO strings from toISOString() are UTC with a Z. toString() is local. Logging new Date() without toISOString() is how two teammates in two offsets argue about the same instant. The integer was never the fight.

If you must store seconds in JS, divide by 1000 and document it: Math.floor(Date.now() / 1000). Do not silently truncate in one service and not another.

The converter will not fix a clock that was set to local time and then stored as if it were UTC (subtracting the offset twice). It will not parse 11/14/23 without a format.

Python time.time() is seconds

time.time() returns seconds as a float. datetime.now(timezone.utc).timestamp() is also seconds. datetime.utcfromtimestamp is the old footgun: naive datetime that looks like UTC. Prefer timezone-aware UTC, then convert.

datetime.fromtimestamp(n) without a tz uses local. That is an hours-off bug waiting for a laptop in IST. datetime.fromtimestamp(n, tz=timezone.utc) is the instant. Then format for a zone if you need a wall clock.

Pandas and NumPy sometimes take nanoseconds or milliseconds depending on the dtype. A datetime64[ns] integer is not a Unix second. Check the unit on the array before you paste a 19-digit value into a seconds converter and panic.

Do not open() a production .jsonl into a website to “just convert column three.” Copy one number.

MySQL FROM_UNIXTIME wants seconds

FROM_UNIXTIME(1700000000) is 2023-11-14 22:13:20 in the session time zone unless you are careful. That last clause is the hours-off trap. The function interprets seconds since the epoch, then displays in time_zone. If the session is SYSTEM and the host is IST, you will swear Unix time “includes India.” It does not. The integer was UTC. The display was not.

Pass milliseconds to FROM_UNIXTIME and you get nonsense or a range error. Divide by 1000 in SQL only when you have proven the column is ms. UNIX_TIMESTAMP() returns seconds. Mixing it with a JS millisecond column is the 55-year report again.

TIMESTAMP columns in MySQL convert to UTC for storage. DATETIME does not. Loading a UTC instant into DATETIME as if it were local is a migration scar you will convert forever.

PostgreSQL to_timestamp is seconds (float). to_timestamp(ms / 1000.0) if the source is JS. EXTRACT(EPOCH FROM timestamptz) is seconds.

JWT exp is seconds

RFC 7519 NumericDate is the number of seconds from 1970-01-01T00:00:00Z UTC, excluding leap seconds - the same POSIX-style count most converters use. exp, iat, and nbf are seconds. A 13-digit exp is not “more precision.” It is a bug. Libraries that compare exp * 1000 against Date.now() are compensating for the unit. Libraries that compare exp to Date.now() without multiplying will treat every token as expired in 1970 terms, or never expire, depending on the inequality.

Decoding a JWT is not verifying it. Do not paste a live access token into a converter farm. If you only need to read exp, copy that integer after a local decode, or copy the claim from a decoder you already trust on your machine. The Unix tool on DevOkk converts the number. It does not prove the signature.

ISO strings are not epoch integers

2023-11-14T22:13:20.000Z already did the human-date step. The Z is UTC. 2023-11-14T22:13:20+05:30 is a different spelling of an instant (offset included). 2023-11-14T22:13:20 with no offset is ambiguous. Many parsers treat it as local, which is a different instant for every teammate. That is an hours-off bug that never touches Unix digits.

Convert ISO to a timestamp only if you need to sort or subtract. Do not parse as local, then store the result as Unix seconds - that is a double shift.

If two logs disagree, convert both to UTC seconds and subtract. Do not subtract two locale-formatted strings. Do not subtract Tuesday 14:00 IST from Tuesday 14:00 UTC in your head.

After you have a civil clock and need another city, Timezone Converter. That page is zones and DST, not epoch math.

DST does not live in the integer

Unix time does not spring forward. Civil clocks do. America/New_York is UTC−5 or UTC−4 depending on the calendar date. Storing UTC-5 as if it were a zone loses the next transition. Storing 09:00 with no zone is not an instant.

“The timestamp is wrong around November” is often a display format that used a fixed offset all year. The integer was fine. Confirm UTC first in Unix Timestamp Converter, then apply an IANA name in Timezone Converter.

POSIX time, which almost every web converter implements, pretends leap seconds do not exist. UTC inserts them. For application logs the discrepancy is rare and small. Do not “correct” a Unix converter by adding leap seconds unless you know the timescale (UTC vs TAI vs GPS).

Excel 1899 and 1904 are a third epoch

A cell that shows a date and contains 45244 is not Unix time. Excel’s Windows default serial counts days from 1899-12-30 (the Lotus 1-2-3 compatibility story, including the fake 1900 leap year). Old Mac workbooks use 1904-01-01. CSV exports sometimes dump the serial with no format. Someone pastes 45244 into a Unix converter and gets 1970-plus-half-a-day.

DATEVALUE, Power Query, and Google Sheets have their own serial conventions. Sheets is closer to the 1899 system. None of them are time.time().

If the sheet was supposed to hold Unix seconds, look at the column format. A format that paints 10-digit numbers as dates will look “off by decades” because Excel is dividing by the wrong epoch. Keep Unix integers as integers, convert with the browser tool, write back an ISO string with a Z if humans must read the sheet.

The unit converters hub will not help; this is time, not pascals. For the map of the other converters, see the student and engineer roundup.

Do not upload the log dump

Incident JSON contains tokens, hostnames, customer ids, and the one timestamp you actually needed. Cloud “timestamp converters” that ask you to drop a file are an upload. DevOkk’s converter is built to run in the browser on the number you paste. That is still not a reason to paste a redacted-poor dump on a shared machine.

Copy one integer. If the file is JSON and will not parse, fix it locally with JSON Formatter. Redact first. Close the tab. You are not creating an account on DevOkk.com to read 1700000000.

If the source is a screenshot of a 1970 date, type the integer from the raw field, not the formatted caption. Captions already applied a zone and a unit.

Mixed units in the same pipeline

A Node service writes Date.now(). A Python worker writes int(time.time()). A mobile client writes milliseconds. The warehouse concatenates them into one event_ts column. Every dashboard that assumes one unit will be right for a third of the rows.

Name columns. ts_s and ts_ms beat timestamp. If you cannot rename, a length check in the query (LENGTH of the integer, or event_ts > 1e12) is a coarse split for this decade. It will fail in 2286. You will have a better schema by then, or you will not.

Message queues that stringify numbers as JSON may turn milliseconds into floats and lose low bits. That is milliseconds of jitter, not 55 years. Do not “fix” float noise by dividing by 1000.

Clocks that were wrong before they were stored

NTP step, a VM that resumes with a stale CMOS, a container with no UTC, a browser that used local Date in a form field. The integer can be internally consistent and still be the wrong instant. Conversion will faithfully print the wrong instant. The converter is not a source of truth for “when did this happen in the world.” It is a printer for the number you gave it.

Compare two independent clocks (server log vs load balancer vs client) in UTC seconds. If they disagree by hours, look at zone and unit. If they disagree by years, look at unit. If they disagree by a few seconds, look at skew, not IST.

13 digits, 10 digits, and the 1970 screenshot

Keep this next to the laptop when the next ticket says the timestamp is in the wrong timezone.

Ten digits in this decade are seconds. Thirteen are milliseconds. Date.now() is thirteen. time.time() is ten (plus a fraction). MySQL FROM_UNIXTIME and JWT exp want seconds. new Date(n) wants milliseconds. ISO strings with Z are already UTC instants, not an extra epoch. DST is a display rule. Excel serials are 1899 or 1904, not 1970.

1700000000 as seconds is 2023-11-14 22:13:20 UTC. The same digits as milliseconds are 1970-01-20 11:33:20 UTC. That pair is the whole incident. Convert the integer in Unix Timestamp Converter. Shift the wall clock in Timezone Converter only after the instant is honest. Pull the field out of messy JSON with JSON Formatter if you must - still one field, still in the browser, still no account.

The happy-path walkthrough remains How to convert Unix timestamps to dates. This page exists so you stop shipping 1970 as a timezone bug.

Frequently asked questions

Why is my Unix timestamp off by about 55 years?

You mixed seconds and milliseconds. Classic Unix time is seconds since 1970-01-01 00:00:00 UTC. JavaScript Date.now() is milliseconds. A 10-digit value treated as milliseconds lands in January 1970. A 13-digit value treated as seconds lands tens of thousands of years in the future. Count digits first in Unix Timestamp Converter.

Is a Unix timestamp in a timezone?

No. Unix time is an instant, not a timezone. The integer does not know IST or PDT. Off-by-hours bugs are a display step: you printed UTC as local, or local as UTC, or applied DST twice. Convert the integer to UTC, then shift civil time with Timezone Converter if you need a wall clock.

What date is 1700000000 versus 1700000000000?

1700000000 seconds is 2023-11-14 22:13:20 UTC. 1700000000000 milliseconds is the same instant. Feed 1700000000 to a millisecond parser and you get 1970-01-20 11:33:20 UTC. That is the screenshot that looks like the epoch never moved.

Is JWT exp in seconds or milliseconds?

RFC 7519 NumericDate is seconds. A 13-digit exp is already non-compliant. Decode the payload locally if you must read claims; do not paste a live access token into a random site. The converter will not verify a signature.

Why does Excel show a date from 1900 or 1904?

Spreadsheet serials are not Unix time. Windows Excel counts days from a 1899-12-30 epoch. Old Mac Excel uses 1904-01-01. A value like 45244 is days, not seconds. Do not run FROM_UNIXTIME on a cell serial.

Do I need an account? Can I paste a whole log file?

No account. Unix Timestamp Converter runs in the browser. Copy one integer or one ISO field - not a production dump with tokens and customer rows. If the payload will not parse, fix syntax in JSON Formatter first, still without uploading the file.

More reading that links back to the same tools and workflows.