A user of one of my Laravel SaaS apps once emailed to say his subscription renewal date looked "a bit generous." The billing page told him his plan would renew on April 25 of the year 57123.
The bug took me embarrassingly long to find because every individual piece was correct. The React frontend sent Date.now() β which returns milliseconds β and the PHP backend did date('Y-m-d', $timestamp), which expects seconds. Feed 1740470400000 into a function expecting 1740470400 and you land roughly 55,000 years in the future. No exception, no warning, no failed test. Just a customer politely asking if his subscription really lasted until the heat death of civilization.
Timestamps look like the most boring topic in software. They're actually one of the most reliable bug factories we have: seconds vs milliseconds, UTC vs local, DST transitions, the 2038 rollover. The Timestamp Converter on toolz.dev exists because I got tired of doing new Date(x * 1000) in a browser console forty times a day. This guide covers what I check now, in the order I check it.
TL;DR: A Unix timestamp counts seconds since 1970-01-01T00:00:00 UTC. 10 digits = seconds, 13 digits = milliseconds β mixing them up puts your dates 55,000 years off. Store UTC, convert only for display, use IANA zone names like
Asia/Dhakainstead of abbreviations. Paste any timestamp into the Timestamp Converter to get ISO 8601, RFC 2822, local and UTC forms β it runs client-side, so timestamps out of JWTs and production logs never leave your browser.
What Is a Unix Timestamp, Exactly?
A Unix timestamp (epoch time, POSIX time) is the number of seconds elapsed since January 1, 1970, 00:00:00 UTC β the "Unix epoch." It's a single integer, it has no timezone (it's always UTC by definition), and effectively every OS, language, and database understands it. That last property is why it survived five decades: it is the one time format nobody argues about.
Why 1970? No deep reason β it was a convenient round date near when Unix was being built at Bell Labs, and early Unix counted time in a 32-bit integer. The arbitrary choice fossilized into a universal standard, which is very Unix.
Some reference points worth recognizing on sight:
| Timestamp | UTC date | Why you'd see it |
|---|---|---|
0 |
1970-01-01 00:00:00 | The epoch. Also what you get from null/0 bugs β a date in 1970 on screen almost always means an uninitialized value, not time travel |
946684800 |
2000-01-01 00:00:00 | Y2K |
1234567890 |
2009-02-13 23:31:30 | Developers actually threw parties for this one |
1740470400 |
2025-02-25 08:00:00 | An ordinary 10-digit modern timestamp |
2147483647 |
2038-01-19 03:14:07 | The 32-bit signed maximum β see Y2038 below |
That fourth row is my favorite example for a subtle reason: a lot of tutorial pages list 1740470400 as "Feb 25, 2025, 12:00:00." It's actually 08:00 UTC β someone converted it in their local timezone once and the wrong value has been copy-pasted around ever since. Verify timestamps with a tool, not with a blog post. Including this one.
Seconds or Milliseconds β How Do You Tell?
Count the digits. For any date in the current era:
- 10 digits (
1740470400) β seconds. Unix convention, most APIs, PHP'stime(), Python'stime.time()(as a float), Stripe's API. - 13 digits (
1740470400000) β milliseconds. JavaScript'sDate.now(), Java'sSystem.currentTimeMillis(), MongoDB dates.
This is the exact distinction that produced my year-57123 renewal date, so I'll spell out the failure modes:
- ms interpreted as seconds β dates ~55,000 years in the future
- seconds interpreted as ms β dates in January 1970 (everything collapses to within ~3 weeks of the epoch)
If you see either signature β ancient dates or absurd far-future dates β you know the bug before reading a line of code. The Timestamp Converter detects digit count and labels both interpretations, which settles the "is this s or ms?" argument in two seconds.
Which Date Formats Do You Actually Need to Know?
Three cover almost everything a working developer touches.
ISO 8601 β the international standard, and what you should emit in APIs and logs:
2026-07-13T09:30:45Z UTC ("Z" = Zulu)
2026-07-13T15:30:45+06:00 with timezone offset
2026-07-13T09:30:45.123Z with milliseconds
The killer feature nobody mentions: ISO 8601 strings sort lexicographically in chronological order. sort on a log file just works. 02/25/2026-style formats can't do that β and worse, US MM/DD and European DD/MM are indistinguishable for twelve days of every month.
RFC 3339 (spec) β the internet-protocol profile of ISO 8601. Slightly stricter; if your API emits 2026-07-13T09:30:45Z you satisfy both. This is the format to standardize on.
RFC 2822 (Sun, 13 Jul 2026 09:30:45 +0000) β email and HTTP headers, RSS feeds. You read it more often than you write it.
Database formats are close cousins: MySQL DATETIME is 2026-07-13 09:30:45 (ISO with a space), PostgreSQL timestamptz renders 2026-07-13 09:30:45+00.
How Do the Languages I Use Handle Timestamps?
The three from my own stack β and the quirk in each that has personally cost me time.
JavaScript (the ms one):
Math.floor(Date.now() / 1000) // current Unix seconds β Date.now() is ms!
new Date(1740470400 * 1000) // seconds β Date: multiply by 1000
date.toISOString() // "2025-02-25T08:00:00.000Z"
Date.parse('2026-07-13T09:30:45Z') / 1000 // ISO string β Unix seconds
Quirk: everything is milliseconds, and new Date(1740470400) silently gives you January 21, 1970 instead of February 2025. No error. This asymmetry is the single most common timestamp bug in web development.
PHP (the seconds one):
time(); // current Unix seconds
date('Y-m-d H:i:s', 1740470400); // "2025-02-25 08:00:00" (server TZ!)
strtotime('2026-07-13 09:30:45'); // string β timestamp
(new DateTime('@1740470400'))
->setTimezone(new DateTimeZone('Asia/Dhaka'))
->format(DateTime::ATOM); // "2025-02-25T14:00:00+06:00"
Quirk: date() formats in the server's default timezone, so the same code prints different dates on your machine and in production. Also, new DateTime('@1740470400') ignores any timezone you pass to the constructor β the @ form is always UTC; you must call setTimezone() after. WordPress adds its own layer: current_time('timestamp') returns a fake "local" timestamp offset from real Unix time, which is exactly as dangerous as it sounds.
Python:
import time, datetime
int(time.time()) # current Unix seconds
datetime.datetime.fromtimestamp(1740470400,
tz=datetime.timezone.utc) # β aware datetime
dt.isoformat() # "2025-02-25T08:00:00+00:00"
Quirk: fromtimestamp() without tz= returns a naive datetime in local time. Naive datetimes are the Python time bug: they compare and subtract happily against each other until the day one of them crossed a DST boundary. Always pass tz=; use zoneinfo (stdlib since 3.9) for named zones.
How Should You Handle Timezones Without Losing Your Mind?
Four rules, all learned the annoying way:
- Store UTC. Always. Unix timestamps or
timestamptzin the database. Timezone becomes a display concern only. - Convert at the presentation layer. The user in Dhaka sees
+06:00, the user in Berlin sees+02:00, the database sees neither. - Use IANA names, not abbreviations.
Asia/Dhaka,America/New_York,Europe/Berlin. Abbreviations are ambiguous βCSTmeans Central US, China Standard Time, or Cuba Standard Time depending on who's reading β and abbreviations don't encode DST rules. IANA names do. - Never hand-roll DST logic. DST dates differ by country, change by legislation, and some places (Arizona, Bangladesh, Japan) don't observe DST at all. The IANA tz database exists because this is genuinely hard; use the library that wraps it.
The corollary of rule 1: when two systems disagree about an event time, convert both values to UTC Unix timestamps and compare the integers. Arguments about "but it says 3 PM here" dissolve instantly.
What Is the Y2038 Problem, and Should You Care?
A 32-bit signed integer maxes out at 2,147,483,647. As a Unix timestamp, that's January 19, 2038, 03:14:07 UTC. One second later, the value wraps negative β to December 13, 1901.
Sounds far away; it isn't, for two reasons. First, it's about 11.5 years out as I write this β well inside the lifespan of embedded systems, industrial controllers, and that one legacy service nobody wants to touch. Second, future dates hit the wall early: a system that computes a 15-year mortgage schedule or a 20-year certificate expiry crosses 2038 today. MySQL's TIMESTAMP column type is the classic trap β it's 32-bit-bounded and can't store dates past 2038-01-19, while DATETIME in the same database is fine.
You're safe on 64-bit time_t (any modern OS), JavaScript (float64 ms), Python (arbitrary precision), and PostgreSQL. You're at risk on 32-bit embedded systems, old TIMESTAMP columns, and C code that hardcoded int32_t for time. The test is simple: push 2147483648 (one past the limit) through your pipeline and see what comes out. The Timestamp Converter will happily generate you post-2038 test values.
Where Do Timestamps Show Up in Real Debugging?
JWT expiry. Tokens carry iat and exp claims as Unix seconds:
{ "sub": "user_8241", "iat": 1783915890, "exp": 1783919490 }
"Why is this user logged out?" is answered by converting exp. Decode the token in the JWT Decoder and convert the claim β both run client-side, which matters because a pasted token is a live credential (the data privacy guide covers why I refuse to put tokens in server-side tools).
Log correlation. One incident, three services, three formats: nginx logs [13/Jul/2026:09:30:45 +0000], the app logs ISO 8601, a queue worker logs raw epoch seconds. Converting everything to one format is step zero of building a timeline.
API integration. Stripe sends "created": 1740470400 (seconds). A JavaScript-built API sends 1740470400000 (ms). Google APIs send RFC 3339 strings. If you consume all three, conversion isn't occasional β it's constant. Format the payloads in the JSON Formatter and convert the interesting fields.
Date-range queries. WHERE created_at >= 1752364800 AND created_at < 1752451200 β is that the right day? Convert both bounds and check, in UTC, before running the delete. Related: the Date Difference Calculator for "how many days between these two?", the Timezone Converter for meeting-time math, and the Cron Parser for "when does this schedule actually fire?".
Frequently Asked Questions
What is a Unix timestamp?
The number of seconds elapsed since January 1, 1970, 00:00:00 UTC (the Unix epoch), stored as a single integer. It's timezone-independent by definition β the same instant is the same number everywhere on Earth β which is why it's the standard interchange format across operating systems, languages, and databases.
Why do some timestamps have 10 digits and others 13?
10 digits is seconds (standard Unix convention, PHP, most APIs); 13 digits is milliseconds (JavaScript's Date.now(), Java). Divide by 1,000 to go from ms to seconds. Confusing the two shifts dates either ~55,000 years into the future or back to January 1970.
Can Unix timestamps represent dates before 1970?
Yes β negative values count backwards from the epoch. -86400 is December 31, 1969. A 32-bit signed timestamp reaches back to December 13, 1901. Some systems and APIs reject negative timestamps, though, so test before relying on them.
What is the Y2038 problem?
32-bit signed timestamps overflow at 2,147,483,647 β January 19, 2038, 03:14:07 UTC β wrapping to December 1901. Modern 64-bit systems are unaffected, but 32-bit embedded devices, legacy C code, and MySQL TIMESTAMP columns are exposed. Systems computing far-future dates (mortgages, certificates) hit the bug years before 2038 arrives.
Why does my date show January 1970?
A zero or near-zero timestamp reached your formatting code β usually an uninitialized value, a failed parse returning 0, or seconds passed where milliseconds were expected. A 1970 date on screen is almost never a data point; it's a null wearing a costume.
Should I store timestamps or datetime strings in my database?
Store UTC either way β the type matters less than the timezone discipline. Unix integers are compact, sort trivially, and dodge parsing entirely; timestamptz/DATETIME columns are human-readable in query results and support date arithmetic in SQL. What you must not do is store local times without offsets β that's data loss you only discover at the next DST transition.
Is the epoch affected by leap seconds?
Practically, no. Unix time pretends leap seconds don't exist β each day is exactly 86,400 seconds, and systems typically smear or step the clock when a leap second occurs. For application code this is a non-issue; it only matters in scientific timing contexts, where TAI or GPS time is used instead.
Is it safe to paste production log timestamps into an online converter?
A raw timestamp alone reveals little, but timestamps usually travel with context β user IDs, token claims, log lines. The Timestamp Converter on toolz.dev converts entirely in your browser with no data transmitted, so pasting values straight from production logs or JWTs doesn't expose anything.
How do I convert a Unix timestamp to a readable date?
Paste the number into a converter and read the UTC and local results, or do it in code: new Date(ts * 1000).toISOString() in JavaScript, datetime.fromtimestamp(ts, tz=timezone.utc) in Python, date -u -d @ts on Linux. The one thing to get right first is whether your value is in seconds or milliseconds β everything else follows from that.
How do I get the current Unix timestamp?
date +%s in a shell, Math.floor(Date.now() / 1000) in JavaScript, int(time.time()) in Python, SELECT EXTRACT(EPOCH FROM NOW()) in PostgreSQL. Note that JavaScript is the odd one out: Date.now() returns milliseconds, so the division is not optional.

