A timestamp moved eight hours: a reproducible debugging case

Work through six checked timestamp conversions to separate timezone offsets, seconds, milliseconds, and the 2038 integer boundary.

This case starts with a common debugging report: an event saved as2026-01-01T08:00:00+08:00 appears as midnight in a UTC log. The record has not moved. The two clock readings describe the same instant. The next problem is deciding whether the API expects Unix seconds or JavaScript milliseconds.

Check the offset before the number

The +08:00 suffix means the displayed clock is eight hours ahead of UTC. Subtracting eight hours gives 2026-01-01T00:00:00Z. Both values therefore map to 1767225600 Unix seconds. By contrast, midnight at UTC+08 maps to2025-12-31T16:00:00Z, eight hours before UTC midnight. That is why converting only the clock digits while discarding the offset can even change the displayed date.

Six cases to reproduce

These cases were generated with Python's timezone-aware datetime and independently checked the millisecond results with JavaScript Date.parse. The files are synthetic examples, not measurements of any third-party converter. All inputs specify an explicit offset, so the answer does not depend on your computer's local timezone. You can download the CSVto inspect or rerun them.

CaseInputUnix secondsUTC result
UTC midnight2026-01-01T00:00:00+00:0017672256002026-01-01T00:00:00Z
Same instant at UTC+082026-01-01T08:00:00+08:0017672256002026-01-01T00:00:00Z
Local midnight at UTC+082026-01-01T00:00:00+08:0017671968002025-12-31T16:00:00Z
One second before epoch1969-12-31T23:59:59+00:00-11969-12-31T23:59:59Z
32-bit signed maximum2038-01-19T03:14:07+00:0021474836472038-01-19T03:14:07Z
One second later2038-01-19T03:14:08+00:0021474836482038-01-19T03:14:08Z

Test the API unit

The second column of the CSV stores JavaScript milliseconds as well. For the UTC+08 morning case it is 1767225600000, exactly 1,000 times the seconds value. JavaScript Date takes milliseconds, so passing the seconds number directly makes the event look near January 1970. Do not rely only on digit count: old dates, negative timestamps, and future values can defeat that shortcut. Read the API field contract, record its declared unit, and convert explicitly.

const source = "2026-01-01T08:00:00+08:00";
const milliseconds = Date.parse(source);
console.assert(milliseconds === 1767225600000);
console.log(new Date(milliseconds).toISOString());
// 2026-01-01T00:00:00.000Z

In Python, datetime.fromisoformat(source).timestamp() gives seconds when the input includes the offset. For this case it returns 1767225600. Convert back using datetime.fromtimestamp(value, timezone.utc) if you want a UTC display. Never strip the +08:00 suffix and then assume the remaining clock was UTC.

Check the 2038 boundary only where it applies

The final two rows move from 2147483647 to 2147483648seconds. The latter does not fit in a signed 32-bit integer. That can matter when an older database field, binary protocol, or device uses that storage type. It does not imply that every modern date library fails in 2038. Check the receiving system's type before describing an overflow.

Carry the evidence into a bug report

Preserve four things together: the original value, its declared unit, the source offset or timezone, and the expected UTC result. Add the actual output from the affected system. That lets another person distinguish a display preference from a unit conversion bug. If you need a broader introduction to Unix time, see our Unix-time guide.

Calculation checked 20 October 2026. Reference definitions: Python datetime and ECMAScript Date time values.