Core answer: A Unix timestamp counts seconds since 1970-01-01 00:00:00 UTC (the epoch): 1,735,689,600 = 2025-01-01 00:00:00 UTC = 2025-01-01 08:00:00 Beijing (UTC+8). Millisecond versions multiply by 1,000 (JS's Date.now()). The 32-bit signed limit overflows on 2038-01-19 — the Y2K38 problem. Timestamps have NO time zone; zones only appear when formatting for humans.
Seconds vs milliseconds (the #1 confusion)
| Value | Unit | Meaning |
|---|---|---|
| 1735689600 | seconds | 2025-01-01 00:00 UTC |
| 1735689600000 | milliseconds | same instant |
| 1735689600000000 | microseconds | databases, logs |
| 1735689600000000000 | nanoseconds | Go's time.Now().UnixNano() |
Quick check: a current seconds timestamp is ~1.7 billion (10 digits); milliseconds ~1.7 trillion (13 digits). Passing seconds where ms are expected lands you in January 1970 + a few weeks — the classic bug.
Converting by hand (sanity checks)
- 1 day = 86,400 s; 1 year ≈ 31,556,952 s.
- Beijing time = UTC + 8 h = timestamp + 28,800 s when displaying.
- 2025-01-01 = 1735689600 → +86400/day: 1735776000 = Jan 2 00:00 UTC.
Worked examples
Example 1 — Log analysis. Server log shows 1735682400: subtract 1735689600 (midnight) → −7,200 s = 2 h before midnight UTC = 2024-12-31 22:00 UTC = 2025-01-01 06:00 Beijing.
Example 2 — Token expiry. JWT exp: 1735689600 means "invalid AT/AFTER this instant" — compare with the current seconds (not ms!) or tokens live 1,000× longer than intended.
Example 3 — Cache TTL. "Cache for 1 h" = now + 3600. Storing Date.now() + 3600 in JS gives ms + seconds = garbage; use Date.now() + 3_600_000.
Example 4 — The 2038 audit. Embedded systems using 32-bit time_t overflow 2038-01-19 03:14:07 UTC; banks' 30-year mortgages and infrastructure firmware already audit this.
Common mistakes and myths
- "Timestamps are in UTC" — they're zone-less; UTC is just how we describe the epoch. The same instant is one number worldwide.
- ms/s unit bugs — the single most common timestamp error; name variables createdAtMs vs createdAtSec.
- Storing local wall time — "2025-03-09 02:30" doesn't exist in US DST spring-forward and exists twice in fall; store instants (timestamps), render locally.
- Assuming monotonicity — NTP sync can move system clock backward; for measuring durations use monotonic clocks (performance.now(), Instant in Java), never wall time.
- Database defaults drift — MySQL TIMESTAMP (range to 2038) vs DATETIME (to 9999, no TZ conversion): choose deliberately, migrate before 2038.