Epoch Converter: Unix Timestamp to Human-Readable Date
The bug that puts dates in year 57,000
// JavaScript developer writes:
const expiresAt = Date.now() + 3600 // Wants: "1 hour from now in seconds"
// Date.now() → 1777334400000 (milliseconds!)
// + 3600 → 1777334403600 (still milliseconds — forgot to /1000)
// Stores in DB, sends to API...
// Python backend reads it as seconds:
from datetime import datetime, timezone
dt = datetime.fromtimestamp(1777334403600, tz=timezone.utc)
# → datetime(57346, 1, 22, ...) — Year 57,346 AD
// Or Node.js reads it back:
new Date(1777334403600)
// → 2026-04-29T01:00:03.600Z — actually correct in JS!
// (JS Date() expects ms, so it worked accidentally)This seconds-vs-milliseconds mismatch is the #1 timestamp bug in production systems. Understanding the epoch system makes it easy to spot and fix.
Key Takeaways
- ✓ Unix epoch = seconds since 1970-01-01T00:00:00Z UTC — 10 digits in 2026 (e.g., 1777334400)
- ✓ JavaScript uses milliseconds — Date.now() = 13 digits. Always ÷1000 for seconds, ×1000 for display.
- ✓ Timestamps are timezone-agnostic — they represent UTC. Convert to local time only at display.
- ✓ Y2038 affects 32-bit systems — MySQL TIMESTAMP, embedded devices, 32-bit Linux kernels.
- ✓ JWT tokens always use epoch seconds — milliseconds in exp/iat gives tokens valid for 55,000 years.
What is Unix epoch time?
Unix epoch time (also called Unix time, POSIX time, or a Unix timestamp) is the number of seconds elapsed since 00:00:00 UTC on Thursday, January 1, 1970, not counting leap seconds. At time of writing, the current epoch is approximately 1,777,334,400.
The choice of January 1, 1970 was pragmatic. Unix was developed at Bell Labs in the late 1960s, and the designers wanted a round date close to the system’s creation. The specific date has no special mathematical significance — it was simply convenient. POSIX.1-2017 standardized this definition, and RFC 3339 (the internet date standard) builds on it.
Unix timestamps are timezone-independent — they represent the same point in time everywhere on Earth. This makes them ideal for storing and comparing timestamps across distributed systems. The timezone complexity is pushed entirely to the display layer. According to the Stack Overflow Developer Survey 2025, date/time handling is consistently rated among the top 5 most confusing programming topics — largely because of timezone conversion errors that wouldn’t exist if timestamps were stored as Unix time.
// Current epoch seconds as of April 29, 2026 (approximate):
1777334400
// This represents the same moment everywhere:
// New York: 2026-04-29 00:00:00 EDT (UTC-4)
// London: 2026-04-29 04:00:00 BST (UTC+1)
// Tokyo: 2026-04-29 13:00:00 JST (UTC+9)
// All of these → 1777334400 seconds since epochTimestamp format reference: seconds vs milliseconds vs more
Different languages, APIs, and databases use different timestamp precisions. Knowing which format you’re working with is the first step to avoiding conversion bugs.
| Format | Example (2026-04-29) | Precision | Notes |
|---|---|---|---|
| Unix seconds | 1777334400 | 1 second | POSIX standard. 32-bit overflows 2038-01-19. |
| Unix milliseconds | 1777334400000 | 1 ms | JavaScript Date.now() default. 3 extra digits vs seconds. |
| Unix microseconds | 1777334400000000 | 1 μs | PostgreSQL default. Many databases and system calls. |
| Unix nanoseconds | 1777334400000000000 | 1 ns | Go time.UnixNano(). Int64 wraps in year 2262. |
| ISO 8601 (UTC) | 2026-04-29T00:00:00Z | Varies | RFC 3339 compliant. Human-readable. Not a number. |
| JWT NumericDate | 1777334400 | 1 second | RFC 7519 §2. iat/exp/nbf claims. Always seconds. |
Digit-count detection rule for 2026: 10 digits = seconds, 13 digits = milliseconds, 16 digits = microseconds, 19 digits = nanoseconds. This heuristic works until year 2286 for seconds (11 digits) and year 2001 for milliseconds (already 13 digits).
Epoch conversion code: JavaScript, Python, Go, SQL
Each language has its own timestamp conventions and gotchas. The examples below all convert to/from the same moment: 1777334400 (2026-04-29T00:00:00Z).
Date/string → epoch
// Current epoch in seconds
const nowSeconds = Math.floor(Date.now() / 1000)
// Current epoch in milliseconds (JS native)
const nowMs = Date.now()
// Date string → epoch seconds
const epoch = Math.floor(new Date('2026-04-29T00:00:00Z').getTime() / 1000)
// → 1777334400
// Safer with explicit UTC
const d = new Date('2026-04-29')
const epochUTC = Math.floor(d.getTime() / 1000)Epoch → human-readable
// Seconds epoch → Date
const d = new Date(1777334400 * 1000) // Must multiply by 1000!
console.log(d.toISOString()) // 2026-04-29T00:00:00.000Z
// Milliseconds epoch → Date
const dMs = new Date(1777334400000)
// Format with Intl.DateTimeFormat
const fmt = new Intl.DateTimeFormat('en-US', {
timeZone: 'America/New_York',
dateStyle: 'full',
timeStyle: 'short',
})
console.log(fmt.format(d)) // Tuesday, April 29, 2026 at 8:00 PMDate/string → epoch
import time
from datetime import datetime, timezone
# Current epoch
now = int(time.time()) # seconds
now_ms = int(time.time() * 1000) # milliseconds
# datetime → epoch (ALWAYS use timezone-aware datetimes)
dt = datetime(2026, 4, 29, tzinfo=timezone.utc)
epoch = int(dt.timestamp()) # 1777334400
# Naive datetime warning: datetime(2026, 4, 29).timestamp()
# uses LOCAL timezone — results differ per machine!Epoch → human-readable
from datetime import datetime, timezone
epoch = 1777334400
# Epoch → UTC datetime
dt = datetime.fromtimestamp(epoch, tz=timezone.utc)
print(dt.isoformat()) # 2026-04-29T00:00:00+00:00
# Epoch → specific timezone
from zoneinfo import ZoneInfo # Python 3.9+
dt_ny = datetime.fromtimestamp(epoch, tz=ZoneInfo('America/New_York'))
print(dt_ny) # 2026-04-28 20:00:00-04:00Date/string → epoch
import "time"
// Current epoch
nowSec := time.Now().Unix() // int64 seconds
nowNano := time.Now().UnixNano() // nanoseconds
// Time → epoch seconds
t := time.Date(2026, 4, 29, 0, 0, 0, 0, time.UTC)
epoch := t.Unix() // 1777334400
// Parse string → epoch
layout := "2006-01-02T15:04:05Z07:00" // Go's reference time
parsed, _ := time.Parse(layout, "2026-04-29T00:00:00Z")
epoch2 := parsed.Unix()Epoch → human-readable
import "time"
epoch := int64(1777334400)
// Epoch → time.Time (UTC)
t := time.Unix(epoch, 0).UTC()
fmt.Println(t.Format(time.RFC3339))
// 2026-04-29T00:00:00Z
// With timezone
loc, _ := time.LoadLocation("America/New_York")
tNY := time.Unix(epoch, 0).In(loc)
fmt.Println(tNY) // 2026-04-28 20:00:00 -0400 EDTDate/string → epoch
-- PostgreSQL: timestamp → epoch
SELECT EXTRACT(EPOCH FROM TIMESTAMP '2026-04-29 00:00:00 UTC')::BIGINT;
-- 1777334400
-- MySQL/MariaDB
SELECT UNIX_TIMESTAMP('2026-04-29 00:00:00');
-- SQLite (no native timestamp type)
SELECT strftime('%s', '2026-04-29T00:00:00Z');Epoch → human-readable
-- PostgreSQL: epoch → timestamp
SELECT to_timestamp(1777334400);
-- 2026-04-29 00:00:00+00
-- MySQL/MariaDB
SELECT FROM_UNIXTIME(1777334400);
-- 2026-04-29 00:00:00
-- SQLite
SELECT datetime(1777334400, 'unixepoch');
-- 2026-04-29 00:00:005 timestamp bugs you will encounter in production
Bug 1: Milliseconds passed to seconds-expecting API
// WRONG: sending ms to an API that expects seconds
fetch('/api/events', { body: JSON.stringify({ timestamp: Date.now() }) })
// → API receives 1777334400000, stores date as year 57,346
// RIGHT:
fetch('/api/events', { body: JSON.stringify({ timestamp: Math.floor(Date.now() / 1000) }) })Bug 2: Naive datetime in Python (local timezone assumed)
# WRONG: naive datetime uses local timezone
from datetime import datetime
epoch = datetime(2026, 4, 29).timestamp() # uses LOCAL TZ
# On a UTC server: 1777334400 ✓
# On an EST server: 1777348800 ✗ (4 hours off)
# RIGHT: always use timezone-aware datetimes
from datetime import datetime, timezone
epoch = datetime(2026, 4, 29, tzinfo=timezone.utc).timestamp() # 1777334400 alwaysBug 3: MySQL TIMESTAMP column overflow (Y2038)
-- RISKY: TIMESTAMP max is 2038-01-19 03:14:07 UTC
CREATE TABLE subscriptions (
expires_at TIMESTAMP -- Will REJECT values after 2038!
);
-- SAFE: Use DATETIME (no timezone but no Y2038 limit)
-- or BIGINT for Unix epoch seconds
CREATE TABLE subscriptions (
expires_at BIGINT NOT NULL -- Unix seconds, no Y2038 issue
);Bug 4: Milliseconds in JWT exp/iat claims
// WRONG: exp in milliseconds — token "expires" year 57,346
const token = jwt.sign({ sub: userId, exp: Date.now() + 3600000 }, secret)
// RIGHT: RFC 7519 requires seconds
const token = jwt.sign(
{ sub: userId, exp: Math.floor(Date.now() / 1000) + 3600 },
secret
)
// Or use jose library's expiresIn:
import { SignJWT } from 'jose'
const token = await new SignJWT({ sub: userId })
.setExpirationTime('1h') // jose handles seconds correctly
.sign(secret)Bug 5: parseInt on a float epoch
// Python time.time() returns a float with sub-second precision
import time
now = time.time() # 1777334400.123456
# WRONG: floor vs truncation difference
int(1777334400.9) # → 1777334400 (correct — truncates toward zero)
round(1777334400.9) # → 1777334401 (rounds UP — off by 1 second)
# RIGHT for epoch seconds: always floor (or use int() which truncates)
import math
epoch = math.floor(time.time()) # always rounds downOnline epoch converter: use it directly in your browser
BytePane’s Timestamp Converter handles both directions: paste any Unix timestamp (seconds or milliseconds — auto-detected by digit count) to get a human-readable date, or pick a date to get the epoch. All processing is client-side, no server calls, safe for JWT tokens and production timestamps.
For JWT token inspection including the iat (issued at) and exp (expiration) claims, use the JWT Decoder — it converts epoch claims to human-readable dates automatically.
The Y2038 problem: which systems are at risk
At 03:14:07 UTC on January 19, 2038, a signed 32-bit integer storing Unix seconds overflows from 2,147,483,647 to -2,147,483,648, representing December 13, 1901. The Linux kernel itself fixed this in 2020 for 32-bit architectures (MIPS, ARM) by using 64-bit time_t in kernel 5.6+. However, the application layer is another story.
| System | Y2038 risk | Fix |
|---|---|---|
| MySQL TIMESTAMP columns | ⚠️ Max 2038-01-19 19:14:07 | Use DATETIME or BIGINT |
| MySQL DATETIME columns | ✅ Max 9999-12-31 | No action needed |
| 32-bit embedded firmware (IoT) | ⚠️ Depends on time_t size | Firmware update or replace |
| 64-bit Linux (x86-64, ARM64) | ✅ Safe until year 292B | No action needed |
| 32-bit Linux (MIPS, ARM32) | ✅ Fixed in kernel 5.6+ | Update to kernel 5.6+ |
| JavaScript (64-bit float) | ✅ Safe until ±285M years | No action needed |
| Python (int is arbitrary precision) | ✅ No overflow | No action needed |
| Go int64 | ✅ Safe until year 292B | No action needed |
Frequently asked questions
What is Unix epoch time and why does it start on January 1, 1970?▼
Unix epoch time counts the number of seconds elapsed since 00:00:00 UTC on Thursday, January 1, 1970 (not counting leap seconds). The date was chosen by Unix developers at Bell Labs in the late 1960s as a convenient, round date near Unix's initial development. It was close enough to the present to be practical, yet far back enough not to run into overflow issues quickly with then-current hardware. The formal specification is in POSIX.1-2017 and RFC 3339. Alternative epochs exist: Windows uses January 1, 1601 (start of Gregorian calendar cycle); GPS uses January 6, 1980; Excel (incorrectly) uses January 1, 1900.
Why does my epoch converter show a date in 1970 or year 57000?▼
Two common bugs: (1) YEAR 1970: You're treating a milliseconds timestamp as seconds. JavaScript's Date.now() returns milliseconds (e.g., 1777334400000), but if you pass it to Python's datetime.fromtimestamp(1777334400000), Python treats it as seconds, giving a date far in the future — about year 58,000. Fix: divide by 1000 first. (2) YEAR 57000 or similar: The inverse — you have a seconds epoch but multiplied by 1000 or passed to a milliseconds-based function. JavaScript's new Date() expects milliseconds, so new Date(1777334400) gives 1970-01-21, not 2026-04-29. Fix: multiply seconds by 1000: new Date(1777334400 * 1000). The 13-digit rule: a milliseconds epoch has 13 digits in 2026 (e.g., 1777334400000). A seconds epoch has 10 digits (1777334400).
What is the Y2038 problem and does it affect me?▼
The Year 2038 problem (also called Y2K38 or Unix Millennium bug) occurs when systems store Unix timestamps as signed 32-bit integers. A signed 32-bit int maxes out at 2,147,483,647, which corresponds to 03:14:07 UTC on January 19, 2038. After that second, the value wraps to -2,147,483,648, representing December 13, 1901. Systems affected in 2026: embedded devices (IoT, industrial controllers, automotive), legacy 32-bit Linux kernels (MIPS, ARM), MySQL TIMESTAMP columns (which use 32-bit internally, max 2038-01-19), and some legacy programming languages. Modern 64-bit systems using int64 for timestamps are safe until year 292,277,026,596. Audit your stack: check MySQL TIMESTAMP vs DATETIME columns, embedded system firmware dates, and any 32-bit IoT devices.
How do I handle daylight saving time (DST) with Unix timestamps?▼
Unix timestamps are timezone-agnostic — they always represent UTC. This is their main advantage for storage and comparison. The DST complexity occurs at the display layer when converting to local time. The classic DST bug: during the "fall back" hour, the same wall clock time occurs twice (e.g., 1:30 AM EST and 1:30 AM EDT). If you store only wall clock times, you cannot distinguish them. Unix timestamps unambiguously distinguish them because they count continuous UTC seconds. In JavaScript: new Date(epoch * 1000).toLocaleDateString() uses the browser's local timezone — different developers see different dates for the same timestamp. Always store timestamps as UTC Unix seconds and convert to local time only at the display layer using Intl.DateTimeFormat with an explicit timeZone option.
What is the difference between epoch seconds and epoch milliseconds?▼
Epoch seconds (10 digits in 2026, e.g., 1777334400) is the POSIX/Unix standard: used by Python time.time(), Go time.Unix(), Linux system calls, JWT tokens (RFC 7519), and most databases. Epoch milliseconds (13 digits in 2026, e.g., 1777334400000) is JavaScript's native format: Date.now() and new Date().getTime() return milliseconds. Most JS APIs expect milliseconds. Epoch microseconds (16 digits) is used by PostgreSQL's timestamp internal representation and many C library functions. Epoch nanoseconds (19 digits) is Go's time.UnixNano() and Rust's Duration since UNIX_EPOCH. The detection rule for 2026: 10 digits = seconds, 13 digits = milliseconds, 16 digits = microseconds.
How do JWT tokens use epoch timestamps?▼
JWT tokens (RFC 7519) use NumericDate — defined as the number of seconds from 1970-01-01T00:00:00Z UTC, identical to Unix epoch seconds. Three claims use this format: iat (issued at), exp (expiration time), and nbf (not before). Critical rule: JWT epoch values are always seconds, never milliseconds. A common bug is setting exp = Date.now() (milliseconds) instead of Math.floor(Date.now() / 1000) + 3600 (seconds + 1 hour). With milliseconds, exp would be ~1.7 trillion, making the token valid for 55,000 years. Some JWT libraries silently accept oversized exp values; others reject them. Use our JWT Decoder to inspect any token's iat and exp values and verify they are in the expected range.
How should I store timestamps in a database?▼
Recommended pattern for 2026: store timestamps as BIGINT (int64) Unix seconds in the database, not as TIMESTAMP or DATETIME columns. Reasons: BIGINT is timezone-agnostic, has no Y2038 issue, sorts correctly as a number, and is portable across all databases. Avoid MySQL TIMESTAMP columns — they use 32-bit internally and max out at 2038-01-19 19:14:07 UTC. Use DATETIME or BIGINT instead. In PostgreSQL, TIMESTAMPTZ (timestamp with time zone) stores UTC internally and converts to the session timezone on display — a good alternative to BIGINT. For SQLite, use INTEGER (Unix epoch seconds) or TEXT (ISO 8601) since SQLite has no native date type. Add a created_at BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM NOW())) column instead of created_at TIMESTAMP.
Convert timestamps instantly
Use BytePane’s Timestamp Converter to convert between Unix timestamps and human-readable dates. The tool auto-detects seconds vs milliseconds based on digit count, supports any timezone, and works entirely in your browser — paste a JWT token into the JWT Decoder to inspect its exp timestamp.
Related articles
Unix Timestamp Converter
Convert epoch to date in JavaScript, Python, Go, SQL, and Rust.
JWT Tokens Explained
How iat, exp, and nbf epoch claims work in JSON Web Tokens.
SQL Cheat Sheet
Date functions, EXTRACT, to_timestamp, and timezone handling in SQL.
Python vs JavaScript
Language comparison including date/time library approaches.