Time

Unix Timestamp Converter Workflows for Logs, APIs, and JWTs

Timestamps appear everywhere. This guide explains how to read them faster when you are debugging real systems.

Timestamps appear everywhere. This guide explains how to read them faster when you are debugging real systems.

This article focuses on the practical side of the workflow, including where developers usually get stuck, what to verify before trusting the result, and how the topic shows up in day-to-day implementation work.

What Epoch Time Actually Means

A Unix timestamp is the number of seconds that have elapsed since midnight UTC on January 1, 1970, a moment known as the epoch. It has no concept of timezone, daylight saving, or locale baked into it; it is just a count of seconds ticking forward. That simplicity is exactly why it shows up everywhere from database columns to API payloads to the internals of your operating system.

Take the value 1717411200. On its own it looks meaningless, but feed it through any converter and you get 2024-06-03 12:00:00 UTC. Because the number is timezone-agnostic, the same integer represents the same instant for a user in Tokyo and a server in Frankfurt. The display differs, the underlying moment does not. This is the property that makes epoch time the lingua franca of distributed systems.

The catch is that humans cannot read it at a glance, and the moment you render it for a person you have to reintroduce a timezone. Most production bugs around time live in that translation layer, not in the raw number itself.

Seconds Versus Milliseconds: The 13-Digit Tell

The single most common mistake when working with timestamps is mixing up seconds and milliseconds. Unix tooling, Postgres, and most languages default to seconds, but JavaScript's Date.now() and Java's System.currentTimeMillis() return milliseconds. Pass one where the other is expected and your date silently jumps decades.

There is a quick sanity check: count the digits. A seconds timestamp for any date near now is 10 digits (1717411200). A millisecond timestamp is 13 digits (1717411200000). If you ever see a date in the year 56,000-something, you almost certainly fed milliseconds into a seconds parser. If you see a date in early 1970, you probably divided when you should not have, or treated seconds as milliseconds.

When you are unsure, multiply or divide by 1000 and re-check against a known reference. A good habit is to keep one trusted timestamp memorized or pinned, such as 1700000000 which lands on 2023-11-14. Comparing an unknown value to that anchor instantly tells you the order of magnitude you are dealing with before you commit to a conversion.

Reading Timestamps in Server Logs

Logs are where timestamp fluency pays off daily. Many systems log in ISO 8601 (2024-06-03T12:00:00Z), but plenty of high-volume services log raw epoch values to save bytes and avoid formatting overhead. When you are correlating an incident across three services, being able to convert 1717411200 to a wall-clock time in your head, or paste it into a converter instantly, shortens the loop dramatically.

The trap is assuming all your logs share a timezone. A load balancer might log in UTC, an application server in the machine's local time, and a managed database in yet another zone depending on its configuration. When two log lines look one hour apart but the events were simultaneous, suspect a timezone offset before you suspect a real latency problem. Converting everything to epoch first, then to UTC, removes the ambiguity.

On the command line, date -d @1717411200 on GNU systems and date -r 1717411200 on macOS and BSD both decode a timestamp. Learning the flavor your shell uses saves you from copying values into a browser tab during an outage.

JWT exp and iat Claims

JSON Web Tokens lean heavily on epoch seconds. The iat claim records when the token was issued, exp records when it expires, and nbf marks the earliest time it is valid. All three are plain Unix timestamps in seconds, per RFC 7519, and this trips up developers who assume milliseconds because the rest of their JavaScript stack uses them.

A token with exp set to 1717414800 stops being accepted at 2024-06-03 13:00:00 UTC. If you generate that token in Node with Date.now(), which returns milliseconds, and forget to divide by 1000, you create a token that expires roughly 50,000 years from now, which is a real security finding, not a harmless quirk. Conversely, multiplying an already-seconds value by 1000 on the verification side makes every token look expired and you get a flood of 401s.

When debugging auth, decode the JWT payload, take the exp value, convert it, and compare against the current epoch. If exp is less than now, the token is expired regardless of what the client believes. Watch for clock skew too: a few seconds of drift between issuer and verifier can reject freshly minted tokens, which is why many libraries allow a small leeway window.

Client Time Versus Server Time

Never trust a timestamp generated on a client for anything security- or ordering-sensitive. A user's laptop clock can be wrong by minutes or set deliberately. If your expiry checks, rate limits, or audit trails rely on client-supplied time, an attacker or even an honestly misconfigured machine can break your assumptions.

The standard fix is to stamp authoritative events on the server using a monotonic, NTP-synchronized source, and treat client timestamps as advisory hints at best. When you must reconcile the two, capture both and store the difference. That delta is invaluable later when you are trying to explain why a client claims an action happened before the server recorded receiving it.

This matters most in distributed systems where ordering is derived from time. Two servers whose clocks differ by even 200 milliseconds can disagree about which of two near-simultaneous writes came first. Production systems either tighten clock sync aggressively or stop relying on wall-clock ordering altogether, reaching for logical clocks or sequence numbers instead.

Timezones and the UTC Discipline

The cleanest rule in time handling is to store and transmit everything in UTC, and convert to local time only at the moment of display. A timestamp is timezone-neutral by nature, so storing 1717411200 commits you to nothing; the moment you format it as a string with an offset, you make a choice that future readers must understand.

Daylight saving time is where this discipline earns its keep. In zones that observe it, the local clock jumps forward an hour in spring and back in autumn, meaning some local times occur twice and others never occur at all. Scheduling logic written against local time hits these seams every year. Compute against epoch or UTC, apply the timezone purely for presentation, and these edge cases stop being your problem.

A practical sanity check: if a converted time is off by exactly one hour, suspect DST. If it is off by a clean whole number of hours, suspect a static timezone offset. If it is off by a wildly large amount, you are back to the seconds-versus-milliseconds problem rather than a timezone issue.

The Year 2038 Problem

Older systems store Unix time in a signed 32-bit integer, which can count up to 2,147,483,647 seconds. That ceiling is reached at 03:14:07 UTC on January 19, 2038. One second later the counter overflows and wraps to a large negative number, which decodes to December 1901. Any 32-bit time arithmetic past that point silently produces nonsense.

This is not a far-off abstraction. Code that computes future dates, such as a 20-year mortgage schedule or a long-lived certificate expiry, can hit 2038 today. A token issued now with a very distant expiry, or a financial projection reaching into the late 2030s, will already trip the bug on a vulnerable platform, which is why it deserves attention well before the deadline.

The remedy is to use 64-bit time representations everywhere, which most modern languages and 64-bit operating systems already do. When auditing legacy C, embedded firmware, or old database schemas, check the width of the type holding time. A column typed as a 32-bit integer is a latent 2038 bug regardless of how far away the year feels.

A Repeatable Debugging Workflow

When a time-related bug lands on your desk, resist the urge to guess. Start by extracting the raw value and counting its digits to settle the seconds-versus-milliseconds question. Then convert it to UTC, never local time first, so you have a stable reference point that does not shift under you.

Next, compare that UTC value against what you expected and categorize the discrepancy. A clean one-hour gap points to DST, a whole-number-of-hours gap points to a static offset, a factor-of-1000 gap points to a unit mismatch, and a jump to 1901 or the year 50,000 points to overflow or unit errors respectively. Each symptom maps to a specific cause, which turns a vague time bug into a quick diagnosis.

Finally, verify the fix against a known anchor and at a boundary. Test around midnight UTC, around a DST transition, and with both a 10-digit and a 13-digit input to confirm your parser rejects or correctly handles each. Pinning down these cases once, ideally in a small unit test, prevents the same confusion from quietly returning the next time someone touches the code.