Timestamp Conversion Errors Across Timezones
Fix timestamp bugs: milliseconds vs seconds confusion, timezone offset errors, DST transitions, Y2038 problem, and ISO 8601 parsing pitfalls. Complete guide.
The Problem
Timestamp bugs are intermittent: they appear at 2 AM on DST transition days, or when a server in US-East processes data from a server in UTC, or when JavaScript milliseconds are treated as Unix seconds. These bugs are nearly impossible to reproduce without knowing exactly what to look for.
A timestamp looks like a simple number, but it hides an entire world of complexity: timezones, daylight saving transitions, milliseconds vs seconds precision, and platform-specific quirks. Timestamp bugs are especially insidious because they work 99% of the time and fail only during specific hours, on specific dates, or for users in specific timezones. This guide covers the 5 most common timestamp conversion errors.
Common errors covered
Milliseconds treated as seconds (or vice versa)
Date shows year 2540 (ms treated as seconds)
or date shows January 20, 1970 (seconds treated as ms)
JavaScript Date.now() returns milliseconds (13 digits). Unix timestamps are traditionally seconds (10 digits). Mixing them produces dates thousands of years in the future or stuck in January 1970.
Step-by-step fix
- 1 Check the digit count: 10 digits = seconds, 13 digits = milliseconds.
- 2 Use the Timestamp Converter to test both interpretations.
- 3 Divide millisecond timestamps by 1000 for seconds-based APIs.
-
4
Multiply second timestamps by 1000 for JavaScript
new Date().
// JavaScript ms timestamp used as Unix seconds: const ts = Date.now(); // 1713600000000 (13 digits) // API expects seconds: sends 1713600000000 -> year 56287! // Unix seconds used in JavaScript Date: new Date(1713600000); // January 20, 1970 - 20 days after epoch
// Convert correctly: const msTimestamp = Date.now(); // 1713600000000 const unixSeconds = Math.floor(msTimestamp / 1000); // 1713600000 new Date(unixSeconds * 1000); // April 20, 2024
Local time used instead of UTC
Timestamps are off by 1-12 hours depending on user timezone
Scheduled events fire at wrong times for some users
JavaScript's Date methods like getHours(), getDate() return local time (user's timezone). Using these to construct timestamps or compare dates introduces timezone-dependent bugs.
Step-by-step fix
- 1 Use the Timestamp Converter to see both UTC and local interpretations.
-
2
Always store timestamps in UTC:
Date.now()returns UTC milliseconds. -
3
Use UTC methods:
getUTCHours(),toISOString(). - 4 Convert to local time only for display, never for storage or comparison.
// Using local time for timestamp - timezone-dependent: const date = new Date(); const ts = new Date( date.getFullYear(), date.getMonth(), date.getDate() ).getTime(); // This depends on user's timezone!
// Always use UTC: const now = new Date(); const todayUTC = Date.UTC( now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() ); // Same result regardless of timezone
Date arithmetic breaks on DST transitions
Adding 24 hours doesn't always give 'tomorrow'
Cron job runs twice or skips on DST change day
On DST spring-forward, a day is 23 hours. On fall-back, it is 25 hours. Adding 24 hours to midnight on these days gives the wrong date. Adding 1 day to a date that doesn't exist (e.g., Feb 29 in non-leap year) overflows to the next month.
Step-by-step fix
- 1 Do all date arithmetic in UTC where there are no DST transitions.
- 2 Use the Timestamp Converter to verify dates around DST transitions.
- 3 Use date libraries (date-fns, Luxon, Temporal) that handle DST correctly.
- 4 For cron jobs, use UTC-based schedules to avoid DST surprises.
// Adding 24 hours - breaks on DST days: const tomorrow = new Date(today); tomorrow.setHours(today.getHours() + 24); // On DST spring-forward: jumps to day-after-tomorrow!
// Use UTC for reliable arithmetic: const tomorrowMs = today.getTime() + 86400000; // 24h in ms const tomorrow = new Date(tomorrowMs);
ISO 8601 date strings parsed differently across browsers
new Date('2024-04-20') gives April 19 in some timezones
Same date string produces different dates on different machines
The string '2024-04-20' (date-only, no time or timezone) is parsed as UTC by ES5 spec but as local time in some older implementations. Adding a time component with explicit timezone fixes this.
Step-by-step fix
-
1
Always include timezone in date strings:
'2024-04-20T00:00:00Z'. - 2 Use the Timestamp Converter to verify parsed results.
- 3 Use the Diff Checker to compare outputs across environments.
- 4 Prefer Unix timestamps over date strings for cross-platform reliability.
// Ambiguous - UTC or local?
new Date('2024-04-20'); // UTC in some browsers, local in others
new Date('04/20/2024'); // US format - breaks in non-US locales
// Explicit timezone - always unambiguous:
new Date('2024-04-20T00:00:00Z'); // UTC
new Date('2024-04-20T00:00:00+05:30'); // IST
// Or use Unix timestamp:
new Date(1713571200000); // Always UTC, no ambiguity
32-bit timestamp overflow (Year 2038 problem)
Date shows December 13, 1901 or January 19, 2038
Database timestamp column stores incorrect dates for future events
32-bit signed integers overflow on January 19, 2038, 03:14:07 UTC. Systems using 32-bit time_t, MySQL TIMESTAMP columns, or 32-bit APIs cannot represent dates beyond 2038.
Step-by-step fix
- 1 Verify with the Timestamp Converter - enter dates after 2038.
-
2
In MySQL, use
BIGINTorDATETIMEinstead ofTIMESTAMP. - 3 In code, use 64-bit integers for timestamp storage.
- 4 Audit your database schemas for 32-bit timestamp columns.
-- MySQL TIMESTAMP has 2038 limit: CREATE TABLE events ( event_date TIMESTAMP -- max: 2038-01-19 03:14:07 );
-- Use DATETIME or BIGINT: CREATE TABLE events ( event_date DATETIME -- max: 9999-12-31 23:59:59 );
Debugging Approach
- 1 Paste the timestamp into the Timestamp Converter to see what date it represents.
- 2 Check the digit count - is it seconds, milliseconds, or something else?
- 3 Compare the same timestamp in UTC and your local timezone.
- 4 Test around DST transition dates: second Sunday of March, first Sunday of November (US).
- 5 Verify the timestamp on a different machine/timezone to catch locale-specific bugs.
Prevention Checklist
- Check timestamp digit count: 10 = seconds, 13 = milliseconds, 16+ = microseconds.
- Store and transmit all timestamps in UTC - convert to local time only for display.
-
Always include timezone in date strings:
'2024-04-20T00:00:00Z'. - Do date arithmetic in UTC to avoid DST transition bugs.
-
Use 64-bit integers and
DATETIMEcolumns instead of 32-bit timestamps. - Test with dates across DST transitions and in different timezones.
Frequently Asked Questions
Why does JavaScript's Date.now() return milliseconds but Unix uses seconds?
Historical reasons. Unix chose seconds in 1970 when sub-second precision was unnecessary. JavaScript chose milliseconds in 1995 for animation and timing precision. Most modern APIs document which format they use - always check the docs.
How do I store timestamps in a database?
Store as UTC always. Use BIGINT for Unix timestamps (future-proof, fast comparisons) or TIMESTAMPTZ in PostgreSQL / DATETIME in MySQL for human-readable storage. Avoid TIMESTAMP WITHOUT TIME ZONE - it loses timezone information.
What is the best format for timestamps in APIs?
ISO 8601 with timezone is the most interoperable: 2024-04-20T15:30:00Z. Unix timestamps (seconds or milliseconds) are compact and unambiguous. Avoid locale-specific formats like 'MM/DD/YYYY' which are ambiguous internationally.
Related Debug Guides
Related Tools
Still stuck? Try our free tools
All tools run in your browser, no signup required.