Unix timestamp converter

Convert a Unix timestamp to a date in any time zone and back: seconds or milliseconds are detected for you, with the ISO 8601 form and how long ago it was.

Current Unix time
Detecting…
Input

What a Unix timestamp is

A Unix timestamp — also called epoch time or POSIX time — is a single number counting how many seconds have passed since 00:00:00 UTC on 1 January 1970, a moment known as the Unix epoch. Because it is one integer with no time zone, no calendar and no formatting, it is the format computers reach for whenever a moment in time has to be stored, sorted, compared or sent across a network. It is what sits behind the "created_at" column in your database, the "iat" and "exp" claims in a JWT, the mtime on a file, and the timestamps in almost every log file you will ever read.

The trade-off is that a bare number means nothing to a human. 1716197600 is a perfectly good instant, but you cannot tell at a glance whether it is last Tuesday or three years ago. That translation, in both directions, is what this tool does.

Seconds or milliseconds — the ambiguity that bites

The original Unix convention counts whole seconds, and that is what you get from date +%s in a shell, from PHP's time(), and from Python's time.time() once truncated. JavaScript, Java and many others count milliseconds instead: Date.now() returns a number a thousand times larger. Both are called "timestamps", and mixing them up is one of the most common date bugs there is.

The failure is quiet rather than loud. Read a milliseconds value as seconds and your date lands tens of thousands of years in the future; read a seconds value as milliseconds and everything collapses into January 1970. Neither throws an error — you simply get a wrong date that looks like a real one.

This tool guesses from the magnitude of the number and then tells you what it guessed, right next to the result. Today's epoch seconds are ten digits and today's epoch milliseconds are thirteen, so the guess is almost always right — but "almost" is not good enough for a value you are about to paste into a bug report, which is why the reading is always visible and always one click away from being switched.

Time zones, UTC and why "local" is slippery

A Unix timestamp has no time zone. It identifies an instant: 1716197600 is 2024-05-20T09:33:20Z, and that same instant is simultaneously 10:33 in London, 12:33 in Jerusalem and 18:33 in Tokyo — London and Jerusalem on summer time that day, Japan keeping none at all. A time zone is not part of the value; it is a lens you look at the value through.

That is why this tool shows several lenses at once. UTC is the neutral reference every server and log agrees on. Your local time is what your own device reports, shown with its zone name (such as Europe/Berlin) so you always know which lens produced it — the detection happens in your browser, so a visitor in Berlin sees Berlin time and a visitor in Tokyo sees Tokyo time. The third row is any zone you pick from the full IANA time zone database, which is the row you want when you are reading a log from a server that lives somewhere else.

  • UTC — the reference every system agrees on, and the right thing to store and log.
  • Local — the same instant as your device sees it, labelled with the detected zone.
  • A zone of your choice — for reading logs and traces from machines elsewhere.
  • ISO 8601 — the interchange text format, e.g. 2024-05-20T09:33:20.000Z.
  • Relative — "3 hours ago", for a quick sense of how recent something is.

Offsets are shown alongside each time (+03:00, -04:00) because they are not fixed: most zones shift by an hour for daylight saving, so the same zone can produce different offsets at different times of year. A date near a transition is exactly where hand arithmetic goes wrong.

Working with epoch time in practice

Every language and shell has its own way to produce and read epoch values. These are the ones worth remembering:

date +%s                         # shell: current time in seconds
date -d @1716197600              # shell (GNU): seconds back to a date
Date.now()                       # JavaScript: current time in MILLIseconds
new Date(1716197600 * 1000)      # JavaScript: seconds to a Date
time.time()                      # Python: seconds, as a float
datetime.fromtimestamp(1716197600, tz=timezone.utc)
SELECT EXTRACT(EPOCH FROM now()) -- PostgreSQL: seconds

A rule of thumb that avoids most date pain: store and transmit instants as UTC — either an epoch integer or an ISO 8601 string — and convert to a local zone only at the last moment, when you actually display it to someone. Formatting early is how a time zone bug gets baked into your data instead of staying in your view layer.

One more thing worth knowing: a signed 32-bit seconds counter runs out on 19 January 2038, the "Year 2038 problem". Modern systems use 64-bit values and are fine, but old embedded devices and legacy database columns are exactly where you might still meet it.

What an epoch is, and why not every timestamp starts in 1970

An epoch, in this sense, is simply a chosen zero — the instant a counter is defined to start from. The Unix epoch is 00:00:00 UTC on 1 January 1970, and it was chosen because it was a convenient round date rather than derived from anything: nothing in the format requires that particular day. Early Unix counted sixtieths of a second from a much nearer zero, and the third edition manual said plainly why that could not last — thirty-two bits of sixtieths guarantees a crisis every 2.26 years, which is about 828 days. Whole seconds in the same thirty-two bits reach a hundred and thirty-six years, or sixty-eight if the counter is signed, which is the 2038 date above.

That history is the reason to check a long number before trusting it. Other systems count from other zeros at other resolutions, and reading one of them as Unix seconds does not leave you a few hours out — it leaves you centuries out. These are the ones you are most likely to be handed:

  • Windows FILETIME — hundred-nanosecond ticks since 1 January 1601 UTC. Divide by ten million and subtract 11644473600 for Unix seconds; the instant 1716197600 used throughout this guide is 133606712000000000 in that scheme.
  • .NET DateTime.Ticks — the same hundred-nanosecond tick, counted from the start of year 1. The Unix epoch itself sits at 621355968000000000 ticks, so that is the number to subtract before dividing.
  • Apple's reference date — seconds since 1 January 2001 UTC, which Cocoa and Core Data use. Add 978307200 for Unix seconds: 1716197600 is 737890400 there.
  • Excel serial numbers — days rather than seconds, and counted from 30 December 1899, because Excel treats 1900 as a leap year and it was not one.
  • GPS time — seconds since 6 January 1980, counted without ever skipping a leap second, so GPS time and UTC no longer agree.

That last one points at something true of the Unix value itself: it is not a stopwatch reading. POSIX defines it as a value that approximates the seconds elapsed since the epoch, and requires that every single day be accounted for by exactly 86400 seconds — so the leap seconds inserted into UTC are not counted at all. The difference between two Unix timestamps is therefore the number of calendar seconds between them rather than the number that actually passed, and the value is best understood as an encoding of a UTC calendar reading. It is also why no timestamp ever names a leap second: the encoding has no slot for one.

Turning a date into an epoch value: the shell, JavaScript and Python

The block above turns an epoch value into something readable. The other direction — a date you already have, as text or as a set of numbers, turned into an epoch value — is where the time zone bugs live, because every one of these will quietly assume a zone when the text does not name one. Here is the same job three ways, on this guide's instant 2024-05-20T09:33:20Z. The shell lines are GNU date; the BSD and macOS date takes different flags.

date -u -d '2024-05-20 09:33:20' +%s     # 1716197600 — -u reads the text as UTC
date -d '2024-05-20 09:33:20 UTC' +%s    # the same, with the zone named in the text
date -d '2024-05-20 09:33:20' +%s        # neither: your machine's zone, another instant
Date.parse('2024-05-20T09:33:20Z') / 1000    // 1716197600 — the Z is what makes it UTC
new Date('2024-05-20T09:33:20Z').getTime()   // the same instant in milliseconds
Date.parse('2024-05-20 09:33:20')            // no Z: your machine's zone, another instant
from datetime import datetime, timezone
int(datetime(2024, 5, 20, 9, 33, 20, tzinfo=timezone.utc).timestamp())   # 1716197600 — an aware datetime
int(datetime.fromisoformat('2024-05-20T09:33:20+00:00').timestamp())     # the same, parsed from a string
int(datetime(2024, 5, 20, 9, 33, 20).timestamp())                        # naive: your machine's zone

The pattern is identical in all three: name the zone, or you inherit whatever the machine is set to. A number that is right on your laptop and hours out on a colleague's is almost always this, and it is why the tool above shows UTC and your local reading side by side instead of picking one for you. If you have the number already and want to check it by eye, paste it into the field at the top of this page.

Frequently asked questions

How does it know whether my number is seconds or milliseconds?
From its magnitude: values below roughly 1e11 are read as seconds, larger ones as milliseconds. Today that cleanly separates ten-digit seconds from thirteen-digit milliseconds. The reading is shown next to the result and you can switch it with one click, so the guess is never hidden from you.
Which time zone counts as "local"?
The zone your own device reports, detected in your browser and shown by name so there is no doubt. It is your zone, not the site's — a visitor in Berlin sees Berlin time and a visitor in Tokyo sees Tokyo time.
Can I convert a date back into a timestamp?
Yes. The same field accepts both directions: type a number and you get a date, type a date such as 2024-05-20 or 2024-05-20T09:33:20Z and you get the epoch value back. There is no mode to switch.
Does it handle dates before 1970?
Yes. Timestamps before the epoch are simply negative — -86400 is 31 December 1969 — and negative values are accepted and converted normally.
Why does my timestamp show a different date than I expected?
Almost always one of two reasons: the seconds/milliseconds reading is not what you assumed (check the label and switch it), or you are comparing a UTC value against a local-time expectation. The tool shows both rows side by side so you can see which one it is.
What is the Year 2038 problem?
Systems that store epoch seconds in a signed 32-bit integer overflow on 19 January 2038 and wrap to a negative number, giving dates in 1901. Anything using 64-bit values — which is nearly everything modern — is unaffected, but legacy embedded systems and old database columns can still be at risk.
Why is my timestamp eighteen digits long?
Because it is probably not a Unix timestamp. Unix seconds run to ten digits today and Unix milliseconds to thirteen; eighteen is what a hundred-nanosecond tick looks like, which is what Windows FILETIME counts from 1601 and what .NET counts from the start of year 1. Sixteen digits is the other common case — the same instant in microseconds. The section on epochs above gives the constant to subtract for each.
How do I convert a date to a Unix timestamp in Python?
Build an aware datetime — one that carries a tzinfo — and call its timestamp method, then truncate it to an integer. The code block above has the line, and the same section shows the same job in the shell and in JavaScript. The whole of the difficulty is the tzinfo: leave it out and Python reads your numbers as the machine's local time and hands back a different value without telling you.
Is the timestamp I enter sent anywhere?
No. Parsing, conversion and formatting all run in your browser using the platform's own date and time zone support. Nothing you type ever leaves your device.

Related tools