Timestamp Bugs: Seconds vs Milliseconds
You're debugging an API integration and the dates are showing up as 1970 or 2053. Or maybe your timestamps work fine in local development but break in production. These are classic symptoms of timestamp format confusion: mixing seconds and milliseconds, mishandling timezones, or using the wrong Unix epoch format. Understanding how timestamps work across different systems is critical for reliable date handling in APIs, databases, and logs.
The Problem: Unix Timestamp Ambiguity
Unix timestamps represent time as the number of units elapsed since January 1, 1970 00:00:00 UTC (the Unix epoch). But there's a critical ambiguity: which unit?
- Seconds:
1704985200(10 digits, standard Unix timestamp) - Milliseconds:
1704985200000(13 digits, JavaScript standard) - Microseconds:
1704985200000000(16 digits, used in some databases) - Nanoseconds:
1704985200000000000(19 digits, high-precision systems)
The problem? There's no universal standard. Different languages, APIs, and systems use different units, and timestamps don't carry metadata about their format.
Classic Bug: Date Shows as 1970 or 2053
You receive a timestamp from an API and convert it to a date. The result?
// API returns timestamp in seconds
const apiTimestamp = 1704985200;
// JavaScript expects milliseconds
const date = new Date(apiTimestamp);
console.log(date.toISOString());
// Output: 1970-01-20T18:03:05.200Z ❌ Wrong!The date is interpreted as January 20, 1970 instead of January 11, 2026. Why? JavaScript's Date() constructor expects milliseconds, but the API sent seconds.
The Fix: Convert Seconds to Milliseconds
// Multiply by 1000 to convert seconds to milliseconds
const date = new Date(apiTimestamp * 1000);
console.log(date.toISOString());
// Output: 2026-01-11T10:00:00.000Z ✅ Correct!Conversely, if you're sending timestamps to an API that expects seconds but you provide milliseconds, dates will appear in the distant future (year 2053+).
How to Detect Timestamp Format
When working with unknown timestamp formats, use the digit count as a detection heuristic:
function detectTimestampUnit(timestamp: number): string {
const digits = timestamp.toString().length;
if (digits === 10) return 'seconds';
if (digits === 13) return 'milliseconds';
if (digits === 16) return 'microseconds';
if (digits === 19) return 'nanoseconds';
return 'unknown';
}
// Examples
console.log(detectTimestampUnit(1704985200)); // 'seconds'
console.log(detectTimestampUnit(1704985200000)); // 'milliseconds'This heuristic works until around the year 2286 (when Unix timestamps in seconds reach 10 billion, becoming 11 digits). For practical purposes, it's reliable for decades.
Language-Specific Timestamp Formats
Different programming languages have different conventions:
JavaScript / TypeScript
Date.now()returns millisecondsnew Date(timestamp)expects millisecondsdate.getTime()returns milliseconds
const now = Date.now(); // 1704985200000 (milliseconds)
const date = new Date(now);
console.log(date.toISOString()); // '2026-01-11T10:00:00.000Z'Python
time.time()returns seconds (with decimal for sub-second precision)datetime.timestamp()returns seconds
import time
from datetime import datetime
now = time.time() # 1704985200.123456 (seconds)
date = datetime.fromtimestamp(now)
print(date.isoformat()) # '2026-01-11T10:00:00.123456'Java
System.currentTimeMillis()returns millisecondsInstant.now().toEpochMilli()returns millisecondsInstant.now().getEpochSecond()returns seconds
long millis = System.currentTimeMillis(); // 1704985200000
long seconds = millis / 1000; // 1704985200Go
time.Now().Unix()returns secondstime.Now().UnixMilli()returns millisecondstime.Now().UnixNano()returns nanoseconds
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println(now.Unix()) // 1704985200 (seconds)
fmt.Println(now.UnixMilli()) // 1704985200000 (milliseconds)
}Database Timestamp Storage
Databases also have varying conventions:
- PostgreSQL:
TIMESTAMPstores datetime values, not numeric timestamps. UseEXTRACT(EPOCH FROM timestamp)to get Unix seconds. - MySQL:
TIMESTAMPstores datetime values. UseUNIX_TIMESTAMP()to get seconds. - MongoDB:
Datetype stores milliseconds since Unix epoch (compatible with JavaScript). - Redis: Stores timestamps as strings or numbers. Common practice is to use seconds for TTL, milliseconds for event timestamps.
PostgreSQL Example
-- Convert timestamp to Unix seconds
SELECT EXTRACT(EPOCH FROM NOW()); -- 1704985200.123456
-- Convert Unix seconds to timestamp
SELECT TO_TIMESTAMP(1704985200); -- '2026-01-11 10:00:00+00'Timezone Pitfalls
Unix timestamps are always in UTC, but displaying them requires timezone context. Common mistakes:
Mistake #1: Displaying UTC as Local Time
const timestamp = 1704985200; // 2026-01-11 10:00:00 UTC
const date = new Date(timestamp * 1000);
// ❌ Wrong: Displays in browser's local timezone
console.log(date.toString());
// In New York: 'Sat Jan 11 2026 05:00:00 GMT-0500'
// In Tokyo: 'Sat Jan 11 2026 19:00:00 GMT+0900'
// ✅ Correct: Display explicitly in UTC
console.log(date.toISOString());
// '2026-01-11T10:00:00.000Z' (always UTC)Mistake #2: Creating Timestamps from Local Time
// ❌ Wrong: Assumes local timezone
const date = new Date('2026-01-11 10:00:00');
console.log(date.getTime() / 1000);
// Result varies by user's timezone!
// ✅ Correct: Specify UTC explicitly
const utcDate = new Date('2026-01-11T10:00:00Z');
console.log(utcDate.getTime() / 1000); // 1704985200 (consistent)Solution: Always Use ISO 8601 with Timezone
When working with human-readable dates, always use ISO 8601 format with explicit timezone:
- UTC:
2026-01-11T10:00:00Zor2026-01-11T10:00:00+00:00 - New York (EST):
2026-01-11T05:00:00-05:00 - Tokyo (JST):
2026-01-11T19:00:00+09:00
Real-World Debugging Scenario
You're integrating with an external API and timestamps look wrong. Here's how to debug:
Step 1: Inspect the Raw Timestamp
const apiResponse = {
"created_at": 1704985200,
"updated_at": 1704985200000
};
console.log('created_at digits:', apiResponse.created_at.toString().length);
// Output: 10 (likely seconds)
console.log('updated_at digits:', apiResponse.updated_at.toString().length);
// Output: 13 (likely milliseconds)Step 2: Test Conversion with Known Date
Convert the timestamp and check if it matches a known date. For example, if the API documentation says created_at is January 11, 2026 10:00:00 UTC:
// Test as seconds
const dateSeconds = new Date(1704985200 * 1000);
console.log(dateSeconds.toISOString());
// '2026-01-11T10:00:00.000Z' ✅ Matches!
// Test as milliseconds (wrong)
const dateMillis = new Date(1704985200);
console.log(dateMillis.toISOString());
// '1970-01-20T18:03:05.200Z' ❌ Wrong yearStep 3: Verify Timezone Handling
// Check if the displayed time matches expectations
const date = new Date(1704985200 * 1000);
console.log('ISO (UTC):', date.toISOString());
// '2026-01-11T10:00:00.000Z'
console.log('Local string:', date.toString());
// 'Sat Jan 11 2026 05:00:00 GMT-0500 (Eastern Standard Time)'
console.log('UTC string:', date.toUTCString());
// 'Sat, 11 Jan 2026 10:00:00 GMT'Common Timestamp Conversion Errors
Error: Date 50 Years in the Past or Future
Cause: Mixing seconds and milliseconds
Fix: Multiply by 1000 if the timestamp is in seconds, divide by 1000 if it's in milliseconds
Error: Date Off by Hours
Cause: Timezone confusion (displaying UTC as local or vice versa)
Fix: Always use toISOString() or toUTCString() for consistent UTC display
Error: Timestamps Work Locally but Fail in Production
Cause: Local dev machine has a different timezone than production servers
Fix: Set server timezone to UTC, always use UTC for timestamps internally, convert to local timezones only for display
Error: Invalid Date When Parsing Timestamps
Cause: Timestamp format not recognized by new Date()
Fix: Use numeric timestamps or ISO 8601 strings, avoid locale-specific date formats
Best Practices for Timestamp Handling
- Store timestamps as Unix seconds or milliseconds in UTC — never store local time without timezone
- Use ISO 8601 format for human-readable dates — always include timezone (
Zor±HH:MM) - Document timestamp units in API contracts — specify seconds, milliseconds, or ISO format
- Detect format using digit count — 10 digits = seconds, 13 = milliseconds
- Use date libraries for complex operations — e.g., date-fns, Luxon, Day.js (instead of raw
Date) - Test with edge cases — year boundaries, leap seconds, daylight saving transitions
- Set server timezone to UTC — avoid timezone drift between environments
Using Timestamp Conversion Tools
When debugging timestamp issues, use a timestamp converter to:
- Convert between seconds, milliseconds, and human-readable dates
- Test timestamp values with known dates
- Verify timezone handling
- Generate timestamps for testing
- Check current Unix time for reference
The date converter on DevToys Pro automatically detects timestamp format, handles multiple timezones, and provides instant conversion between Unix timestamps, ISO 8601, and human-readable formats.
Validation Checklist
Before deploying timestamp handling code, verify:
- ✅ Timestamps converted to correct unit (seconds ↔ milliseconds)
- ✅ UTC used for storage and API communication
- ✅ Timezone explicitly specified when displaying dates
- ✅ Edge cases tested (year boundaries, leap years)
- ✅ API documentation specifies timestamp format
- ✅ Server timezone set to UTC
- ✅ Date libraries used for complex timezone logic
Key Takeaways
- Unix timestamps can be in seconds (10 digits) or milliseconds (13 digits)
- JavaScript expects milliseconds; many APIs send seconds — convert accordingly
- Always store and communicate timestamps in UTC, convert to local only for display
- Use ISO 8601 format with explicit timezone for human-readable dates
- Detect timestamp format using digit count as a heuristic
- Test timestamp conversions with known dates to verify correctness
- Document timestamp units in API contracts to prevent integration bugs
Related Tools:
- Unix Timestamp Converter — Convert between timestamps, dates, and timezones
- JSON Formatter — Format API responses with timestamps
- JSONPath Tester — Extract timestamp fields from API responses