Real Epoch Converter logo realepochconverter
guide

7 Timestamp Bugs That Bite Every Developer

Some bugs only bite once a year, when daylight saving ends. Others wait until 3 a.m. on a Saturday to corrupt a whole table. These are the seven timestamp bugs that show up again and again in production — and the fix for each one.

Bug 1 — Confusing seconds with milliseconds (or microseconds)

Symptom: dates in the 1970s appear in brand-new logs, or events sort wildly out of order.

Cause: JavaScript and Java write 13-digit milliseconds; PHP, C, and most databases write 10-digit seconds; Go offers both plus microseconds and nanoseconds. A value written by one system and read by another is routinely misinterpreted — 13 digits read as seconds lands you in the year 5138, and 10 digits read as milliseconds lands you in 1970.

Fix: decide the unit at the system boundary and document it in the schema or API contract. When decoding an unfamiliar value, count the digits (10 = seconds, 13 = milliseconds, 16 = microseconds) and confirm with the unit-detection guide. The converter auto-detects the unit and shows all three readings so a mismatch is obvious.

Bug 2 — Storing local time without an offset

Symptom: events recorded at the same moment on different servers are hours apart, and comparisons across servers are wrong.

Cause: a server writes 2026-01-15 09:00:00 — its local time — and nothing records that the machine was on UTC+2. When another system reads the string, it assumes its own zone and shifts the moment.

Fix: store instants, not wall clocks: a Unix timestamp or an RFC 3339 string carrying Z or an explicit offset. Convert to a display timezone only at the UI layer. If you must store local time (scheduling is the legit case), store the IANA zone name beside it, as in 2026-01-15 09:00 America/New_York.

Bug 3 — Lexically sorting ISO strings that are not all UTC

Symptom: a list of timestamps appears sorted, but some rows are out of order; the bug only appears when data crosses timezones.

Cause: string comparison is chronological only when every value is normalized to the same offset and format. 2026-08-07T00:00:00Z and 2026-08-07T05:30:00+05:30 are the same instant, but the second string sorts "later".

Fix: normalize to UTC (always emit Z) before sorting, or sort on a parsed instant. In databases, use a real timestamp column type instead of a text column and let the engine compare correctly.

Bug 4 — Losing precision on 64-bit values

Symptom: snowflake IDs, .NET ticks, or Active Directory values come back with different trailing digits than the ones stored, and lookups occasionally match the wrong row.

Cause: numbers above 9,007,199,254,740,991 (253 − 1) cannot be represented exactly as JavaScript numbers, and JSON parsers in many languages round them silently. Current .NET ticks, FILETIME values, and snowflake IDs are all above that line.

Fix: treat these as strings or arbitrary-precision types end to end — BigInt in JavaScript, decimal / long handling where appropriate, never float or number. Every converter on this site that touches such values (snowflake, .NET ticks, LDAP) uses BigInt.

Bug 5 — Forgetting that a day is not always 86,400 seconds

Symptom: date arithmetic that adds 24 hours drifts by an hour around daylight-saving transitions; logs skip or duplicate a second at leap-second events.

Cause: two different "days". Civil days are 23, 24, or 25 hours long across DST boundaries, and UTC occasionally inserts an extra leap second. Adding 86_400 * 1000 to a local midnight does not always land on the next midnight.

Fix: when you mean "same wall-clock time tomorrow", add one day through a date library operating in the zone, not a fixed number of seconds. When you mean "exactly 24 hours from now", a timestamp in UTC is the safe tool. The leap seconds guide covers the civil-vs-atomic split in detail.

Bug 6 — The 32-bit overflow (and its cousins)

Symptom: dates suddenly read as 1901; GPS receivers report dates from 1999; NTP-era confusion produces 1968 timestamps.

Cause: a fixed-width counter wrapped. Signed 32-bit seconds reach their limit on January 19, 2038; GPS week numbers are broadcast in 10 bits and rolled over in April 2019; NTP's 32-bit seconds field wraps in 2036.

Fix: audit integer widths in any system that stores or transmits timestamps — embedded devices, file formats, and protocols are the usual laggards. For new code, 64-bit seconds are safe for the next 292 billion years; if a fixed-width counter is unavoidable, define the rollover behavior explicitly and test it.

Bug 7 — Misreading "now" across machines

Symptom: event timestamps disagree with the moment they were logged; clock skew between services produces negative durations and "event before creation" rows.

Cause: timestamps are only as trustworthy as the clocks that mint them. Virtual machines drift, containers inherit host clocks, and a machine that jumped forward produces timestamps from the future.

Fix: run NTP (or a cloud time service) everywhere and monitor clock skew on your fleet. When a single ordering authority is needed, timestamp at a well-synchronized service rather than on every client, and log your own clock's offset when you record an event so skew is visible later. Remember that the site's "now" values come from your own device clock — the methodology page says so explicitly.

The one habit that prevents most of these

Almost every bug above is a boundary bug: it happens where a value moves between systems, units, or timezones. The fix is to make the boundary explicit — document the unit in the schema, carry the offset on every string, convert once at the edge — and to decode unfamiliar values with a tool that shows you what it assumed. When a date looks wrong, suspect the unit first, the timezone second, and the clock third; in that order, you will find almost every timestamp bug on this list within minutes.

First published · Last reviewed · Maintained and developed by the Real Epoch Converter team · [email protected] · Contact · Methodology

Copied