About epochr

The timestamp converter that tells you what you're looking at

Paste a timestamp, get a human-readable date. Pick a date, get a timestamp. No accounts, no tracking, no friction.

A 13-digit timestamp arrives in a webhook payload, someone feeds it to new Date(value * 1000) because they copied an older converter's recipe, and your "user signed up at" column claims the event happened in the year 49,995. Most converters say nothing: they emit a date and move on. The bad bug isn't when the math fails. It's when the math works and the answer is nonsense.

Epochr runs on one rule: count the digits before you convert.

Unit detection has to happen before parsing, not after. This page catalogs what goes wrong when you reverse that order.

The 1970 date bug: when a 13-digit timestamp is read as seconds

The most common failure we kept hitting during build was the inverse of the year-49,995 case. A logging pipeline emits 13-digit millisecond timestamps. A downstream parser, written against an older API contract, reads the value as seconds. The result isn't a year-49,995 garbage date. It's a plausible date 1,000 times in the future, which gets quietly truncated by a database column that won't accept it. The row lands with a NULL or, worse, with the maximum timestamp the column supports, often December 31, 9999.

The reverse case is louder. Strip a 13-digit value down to 10 digits (sloppy substring, a regex that captured too little, a CSV cell that hit a column width limit) and feed it as seconds. The first 10 digits of 1711987200000 give 1711987200, March 2024. Benign. But 1700000 (a truncated value pasted out of a broken log entry) reads as January 20, 1970 at 14:13:20 UTC. Now every event in your system happened during the moon-shot era.

How do you tell which side of this bug you're on?

Look at the year. If your date is in the future or past 2286, the input was milliseconds parsed as seconds. If your date sits in 1970, the input was a stripped-down value that should have been longer. In our build we chose to make the detected unit visible as a badge above the result so the user catches the mismatch in the first second, not after a deploy.

The year 49,995 problem: the silent overflow nobody notices

This failure mode hides in test fixtures. A unit test pins a "future date" with a millisecond timestamp because that's what the app emits. A separate service reads the fixture as seconds. The test passes because both sides are wrong in the same direction. Production breaks the moment a real user record routes through one service but not the other.

Epochr's defense sits in the decision table below. We tested every digit count from 9 through 20 against actual outputs from Node 22, Python 3.13, Go 1.24, and Postgres 17 to build it. The table is the contract: count, identify, then parse.

DigitsUnitExampleRead as seconds givesWhere it comes from
10seconds1711987200March 2024 (correct)Unix date +%s, cron, HTTP Date header
13milliseconds1711987200000year 56,235JavaScript Date.now(), most web APIs
16microseconds1711987200000000year 50,706,996PostgreSQL timestamptz, high-res event logs
19nanoseconds1711987200000000000overflow / InfinityGo time.UnixNano(), Linux kernel tracing

The "read as seconds gives" column shows the lie a naive converter tells. Epochr refuses to compute that column at all unless the user explicitly overrides the detected unit. The override exists for one case: a 10-digit microsecond value emitted by a system that pre-divides by a million. We have seen it once, in a Kafka topic from a payments processor we won't name.

Microseconds in PostgreSQL: why timestamptz columns produce 16-digit values

This pattern shows up across older Postgres apps that want sub-millisecond resolution without timestamptz directly:

SELECT extract(epoch FROM now()) * 1000000;
-- returns: 1711987200123456

The output is a 16-digit integer. Newer apps store timestamptz natively and convert at query time, which is correct but produces output strings rather than integers, so the 16-digit form survives in logs, message queues, and analytics pipelines.

The Date object loses precision on conversion. JavaScript's Date works at millisecond resolution; nothing past three decimal places after the comma survives. Epochr reports the full 16-digit input in the badge but renders the date at millisecond resolution, with the dropped microseconds shown in a smaller annotation. The failure mode we kept hitting was rounding: dividing by 1,000 before constructing a Date introduces a half-millisecond bias on average. We round to nearest, not toward zero, which matches what Postgres's own to_timestamp(microseconds / 1000000.0) does.

Nanoseconds in Go: time.UnixNano() and the precision you lose on conversion

Go's time.UnixNano() returns an int64 of nanoseconds since the epoch:

t := time.Now().UnixNano()
// e.g. 1711987200123456789

The value runs 19 digits for any timestamp between 2001 and 2262. After 2262 the int64 overflows: Go's documented "year 2262 problem," analogous to the year 2038 problem for 32-bit second timestamps.

JavaScript can hold the full 19-digit value in a BigInt but cannot hand it to Date, which is double-precision float and tops out at the year 275,760. Epochr takes the only sensible path: divide by a million to reach milliseconds, store the lost six digits separately, and report them in the output. Those dropped digits aren't noise. In a distributed tracing context they're the difference between two spans landing in the right order and showing up as simultaneous.

DST and the one-hour ambiguity that breaks fall-back timestamps

On the morning the clock falls back, every local-time string between 1:00 AM and 2:00 AM happens twice. A timestamp like "2025-11-02T01:30:00 America/New_York" is truly ambiguous: it could be EDT (UTC-4) or EST (UTC-5). The two readings sit an hour apart in Unix-epoch terms.

Browsers default to "earlier" via the Intl engine, which picks the EDT reading. Postgres defaults to "earlier" too. Python's datetime.replace(tzinfo=...) does whatever you tell it, which means nothing useful by default. We surface the ambiguity in epochr by parsing both readings and showing the gap when the input is a local-time string in a fall-back window. The result: two timestamps with a one-hour delta and a note about which one the runtime picked.

Negative timestamps: Unix times before January 1, 1970

-2208988800 is the Unix timestamp for January 1, 1900. Negative values are legal in POSIX and supported by time.Time in Go and JavaScript's Date. Not every database driver agrees. SQLite stores them fine; older MySQL versions reject them outright; some ORMs silently clamp at zero. The clamp is the dangerous one.

Birthdays before 1970 are the most common real input. Historical event timestamps show up in archival contexts. Epochr accepts negative values across all four units, with the same digit-count rule applied to the absolute value: a 10-digit negative number is still seconds, a 13-digit negative number is still milliseconds.

Inline questions worth a closing pass

Does epochr work offline?

Yes. Every conversion runs in the browser via the Date and Intl APIs. After the first page load the tool works with no network connection. The Service Worker is not aggressive (we kept it minimal to avoid stale-state bugs across timezone updates), but the converter itself is pure JavaScript and works without a server round-trip.

What about the year 2038 problem?

The 2038 problem is specific to 32-bit signed integer seconds. Epochr's internal representation uses JavaScript's double-precision float milliseconds, good through the year 275,760. The 2038 boundary is still visible in input data, though: a 10-digit timestamp from a C program compiled against a 32-bit time_t will wrap to 1901 on January 19, 2038. We flag values close to the boundary in the badge.

FAQ

How does epochr detect whether a timestamp is seconds, milliseconds, microseconds, or nanoseconds?

epochr counts the digit length of the input after stripping non-numeric characters. Ten digits is treated as seconds (valid for any date from 1973 to 2286 in the signed 32-bit range, and well beyond that for our 64-bit handling). Thirteen digits is milliseconds — the default output of JavaScript's Date.now(). Sixteen digits is microseconds, the granularity PostgreSQL uses for timestamptz. Nineteen digits is nanoseconds, used by Go's time.UnixNano() and Linux kernel tracing. You do not pick a unit — paste the value and epochr picks for you, which is the whole point. A small tolerance window handles values that drift a digit around epoch boundaries.

Why do some timestamps have 10 digits and others 13?

10-digit timestamps count seconds since epoch. 13-digit timestamps count milliseconds — 1,000 times more granular. JavaScript's Date.now() returns milliseconds, which is why you see 13-digit values so frequently in web development. epochr detects the difference automatically.

What is the Unix epoch and why January 1, 1970?

The Unix epoch is an arbitrary but historically convenient starting point chosen by early Unix developers at Bell Labs. January 1, 1970 00:00:00 UTC was selected as time zero. It predates Unix's first public release and was chosen because it was a round date in the recent past at the time of the language's design. Every POSIX system, JavaScript runtime, JVM, .NET framework, and mainstream database has standardized on the same epoch, which is what makes Unix time a reliable wire format for timestamps across heterogeneous systems.

Will Unix time break in 2038?

The Year 2038 Problem affects systems that store Unix timestamps as 32-bit signed integers, which overflow on January 19, 2038 at 03:14:07 UTC. Modern systems use 64-bit integers, which extend Unix time to roughly the year 292 billion. epochr uses JavaScript numbers (64-bit IEEE 754 floats), safe for all dates between roughly 271,821 BC and 275,760 AD. If you are auditing legacy C code or embedded firmware, the 2038 bug is still real; for anything using a 64-bit time_t (Linux x86_64, modern mobile OSes, every cloud provider), it is not.

How does epochr handle Daylight Saving Time?

DST is handled automatically by the browser's Intl.DateTimeFormat engine, which uses the IANA timezone database bundled with your browser. When you select America/New_York and convert a summer timestamp, the output correctly shows EDT (UTC-4). A winter timestamp shows EST (UTC-5). The conversion is deterministic because Unix timestamps are always UTC under the hood — only the display formatting depends on the selected zone. You can switch timezones instantly without re-typing the input.

What's the difference between UTC and ISO 8601?

UTC is a timezone standard — output looks like 'Thu Apr 02 2026 00:00:00 GMT+0000'. ISO 8601 is a formatting standard — output looks like '2026-04-02T00:00:00.000Z'. Both represent the same moment. ISO 8601 is preferred for storage and wire formats because it is unambiguous and sortable as a string; the verbose UTC form is easier for humans to scan. epochr emits both plus a relative phrase ('3 hours ago') and the broken-down components so you can copy the format your target system expects.

Can I use epochr offline?

Once the page has loaded, all conversion logic runs in your browser with no network requests. If your browser has cached epochr's assets — which happens automatically after the first visit — it will work offline. This also means timestamps you paste never touch a server. There is no logging, no rate limit, and no analytics tied to the values you convert.

Privacy & Data

epochr stores only your theme preference and timezone in localStorage. No accounts, no cookies, no analytics on your timestamps. Read the full policy

Back to Converter