Skip to main content
{/}codefunc
Web & APIJuly 11, 2026 · 6 min read

Unix Timestamps and ISO-8601 Dates: Seconds, Milliseconds, and Safe Conversion

How to convert Unix epoch seconds and milliseconds, read JWT exp claims, format ISO-8601 correctly, and avoid DST and timezone bugs in APIs and frontends.

By codefunc

timestampsiso-8601jwttimezoneapi

Almost every API and auth system eventually reduces time to a number: seconds or milliseconds since 1970-01-01T00:00:00Z. That number is easy to store and compare — and easy to misread. Mixing seconds with milliseconds, ignoring timezone offsets, or trusting local clock display during DST transitions produces "random" 401s, scheduling bugs, and off-by-a-factor-of-1000 errors that look mysterious until you inspect the raw value.

Bottom line: Know whether your epoch is seconds or milliseconds before you convert. Store and exchange UTC (or explicit offsets). Treat JWT exp as Unix seconds. Convert with tools that show both human and machine forms so you catch unit mistakes early.

Who this guide is for

Backend and frontend engineers debugging expired tokens, webhook timestamps, cron schedules, analytics events, and database timestamptz values. If you have ever multiplied or divided by 1000 "just in case," this article replaces guesswork with a checklist.

Unix time in one paragraph

Unix time (epoch time) counts seconds since the Unix epoch in UTC. It ignores leap seconds in most practical systems (POSIX time). JavaScript's Date.now() returns milliseconds. Many databases and JWT libraries use seconds. That single mismatch causes most timestamp bugs.

Source Typical unit Example (approx. mid-2026)
JWT exp, iat, nbf seconds 1784000000
Python time.time() seconds (float) 1784000000.123
Date.now() / Date#getTime() milliseconds 1784000000123
Unix date +%s seconds 1784000000
Some Java APIs / MongoDB milliseconds 1784000000123

Rule: 10-digit values near "now" are usually seconds; 13-digit values are usually milliseconds. Confirm with the Unix timestamp converter instead of guessing.

Seconds vs milliseconds: conversion rules

// Seconds → Date
const fromSeconds = new Date(epochSeconds * 1000);

// Milliseconds → Date
const fromMillis = new Date(epochMillis);

// Date → seconds (JWT-style)
const exp = Math.floor(Date.now() / 1000);
Mistake Symptom Fix
Treat ms as seconds Date in year ~50,000+ Divide by 1000
Treat seconds as ms Date in 1970 Multiply by 1000
Truncate instead of floor Off-by-one second near boundaries Use Math.floor for exp
Float seconds to int badly Early expiry Be explicit about rounding policy

JWT exp, iat, and nbf

Per RFC 7519, registered time claims are NumericDate values: seconds since epoch.

{
  "sub": "usr_8f2a",
  "iat": 1784000000,
  "nbf": 1784000000,
  "exp": 1784003600
}

Debugging workflow:

  1. Copy the token and open the JWT decoder (client-side).
  2. Read exp as Unix seconds.
  3. Convert with the Unix timestamp tool to a UTC wall time.
  4. Compare against "now" in UTC — not only your laptop's local clock display.
  5. Check clock skew between auth server and API (see below).

A token that "just issued" but already fails exp checks often means the verifier used milliseconds, or one machine's clock is wrong by minutes.

ISO-8601: the human-exchange format

ISO-8601 (and the Internet profile in RFC 3339) is what APIs should put in JSON for humans and logs:

2026-07-11T14:30:00Z
2026-07-11T14:30:00.123Z
2026-07-11T10:30:00-04:00
Form Meaning
Z UTC (Zulu)
±HH:MM Offset from UTC
Fractional seconds Precision beyond whole seconds
Date-only 2026-07-11 Calendar date — not a timestamp (timezone ambiguous)

Parsing pitfalls in JavaScript

// Prefer explicit UTC
Date.parse("2026-07-11T14:30:00Z"); // fine

// Date-only forms are interpreted as UTC in ES5+ for ISO, but
// "2026-07-11" vs "07/11/2026" behavior differs across engines — avoid locale forms
Date.parse("07/11/2026"); // DO NOT rely on this

Always include time and offset (or Z) for instants. Use date-only strings only for civil dates (birthdays, fiscal days) with a documented timezone policy.

Timezones and DST

Unix timestamps and UTC instants do not observe DST. DST affects local display and local scheduling rules.

Scenario Risk
Store "09:00 America/New_York" as a bare timestamp without zone Ambiguous on fall-back day (1:30 AM happens twice)
Add 24 hours in local time across DST spring-forward Landing on wrong civil time
Compare server UTC logs to laptop local screenshots Apparent "skew" that is only offset
Cron in container TZ ≠ intended business TZ Jobs fire an hour off after DST change

Conversion workflow:

  1. Keep the source of truth as UTC instant (epoch or ...Z).
  2. Convert for display with an explicit IANA zone (America/New_York, Europe/Berlin) via the Timezone converter.
  3. Never serialize "local time without offset" across systems.
// Display in a zone (modern platforms)
new Intl.DateTimeFormat("en-US", {
  timeZone: "America/New_York",
  dateStyle: "medium",
  timeStyle: "long",
}).format(new Date(1784000000 * 1000));

Clock skew and verification windows

Distributed systems disagree about "now" by tens of milliseconds to several minutes. Auth servers often allow a small leeway (e.g. 60 seconds) when checking exp and nbf.

Check Guidance
API rejects fresh tokens Compare API host NTP vs auth host; inspect iat/exp
Tokens valid "in the future" Client clock ahead; fix NTP or allow nbf leeway carefully
Browser vs server mismatch Do not trust Date on the client for security decisions

Security checks belong on the server with synchronized clocks. Client-side conversion is for debugging and UX.

Safe conversion playbook

When a timestamp looks wrong:

  1. Count digits — 10 ≈ seconds, 13 ≈ ms (for contemporary dates).
  2. Convert both ways in the Unix timestamp tool — if the human date is nonsensical, you have the wrong unit.
  3. Normalize to UTC before comparing across services.
  4. Map to local zones only for UI, using Timezone converter.
  5. For JWTs, decode claims with JWT decoder, then convert exp/iat as seconds.

Quick reference snippets

# Current Unix seconds
date +%s

# Seconds → UTC
date -u -d @1784000000

# Node
node -e "console.log(new Date(1784000000*1000).toISOString())"
-- PostgreSQL: seconds → timestamptz
SELECT to_timestamp(1784000000);

-- milliseconds
SELECT to_timestamp(1784000000123 / 1000.0);

API contract recommendations

  1. Document the unit next to every epoch field: "expiresAt": 1784003600 // Unix seconds.
  2. Prefer ISO-8601 strings in public JSON unless you need compact numeric fields.
  3. Reject ambiguous date-only strings for absolute deadlines.
  4. Include timezone or store UTC-only for events.
  5. In tests, freeze time (jest.useFakeTimers, sinon.useFakeTimers) instead of asserting against Date.now().

Privacy note for online converters

Timestamps themselves are rarely secret, but values pasted alongside tokens, order IDs, or session payloads can be. Prefer client-side converters that do not upload your clipboard. codefunc's Unix timestamp, Timezone converter, and JWT decoder run in the browser — useful when the surrounding payload is sensitive.

Practical takeaway

Decide seconds vs milliseconds first; everything else is display. Use ISO-8601 with Z or an explicit offset for APIs. Treat JWT time claims as Unix seconds and verify them against UTC. Convert zones only at the edges with IANA names, and watch DST when scheduling civil times. When debugging, paste into the Unix timestamp and Timezone converter tools — and decode tokens with the JWT decoder — before changing production expiry settings.

Sources

Try it on codefunc

Related articles

3

Search tools

Find a developer tool