← Back to Museum

Milliseconds vs Seconds Mix-up

Medium SeverityOngoing IssueEpoch Error

🚨 The Crime

One of the most insidious bugs in software development: the confusion between Unix timestamps in seconds and JavaScript timestamps in milliseconds. This seemingly simple unit mismatch has caused data corruption, broken integrations, and temporal chaos across countless systems. When your API expects seconds but receives milliseconds (or vice versa), time literally breaks.

💥 Real-World Impact

🐦 Twitter's 2010 Bug

Twitter's API returned timestamps in seconds, but their JavaScript client expected milliseconds. Result: tweets appeared to be from 1970 or the far future.

📊 Analytics Disasters

Google Analytics integrations frequently break when event timestamps are sent in the wrong unit, causing data to appear decades in the past.

💳 Payment Processing

E-commerce platforms have lost millions when payment timestamps were misinterpreted, causing transactions to appear expired or invalid.

🔐 JWT Token Chaos

Authentication systems break when JWT expiration times are calculated with the wrong timestamp unit, causing immediate logouts or eternal sessions.

🤔 The Confusion Explained

Unix Timestamp (Seconds)

Traditional Unix timestamp: seconds since January 1, 1970 UTC

1692134400 // August 15, 2023 22:00:00 UTC

JavaScript Timestamp (Milliseconds)

JavaScript Date.now(): milliseconds since January 1, 1970 UTC

1692134400000 // Same time, but in milliseconds

The Disaster

When you mix them up, you get dates that are off by a factor of 1,000:

1692134400 // Treated as milliseconds = January 20, 1970
1692134400000 // Treated as seconds = Year 55,605

💻 Code Examples

❌ Problematic: Naive Timestamp Handling

// Dangerous: Assuming timestamp units
function processEvent(timestamp, data) {
  // Is this seconds or milliseconds? Who knows!
  const eventDate = new Date(timestamp);
  
  if (eventDate.getFullYear() < 2000) {
    // Oops, probably got seconds instead of milliseconds
    console.log("Event from 1970? That can't be right...");
  }
  
  return {
    date: eventDate,
    data: data
  };
}

// API response - what unit is this?
const response = {
  created_at: 1692134400,  // Seconds? Milliseconds? 🤷‍♂️
  event_data: {...}
};

✅ Safe: Explicit Unit Handling

// Safe: Explicit timestamp handling
class TimestampUtils {
  static ensureMilliseconds(timestamp) {
    // If it looks like seconds (10 digits), convert to milliseconds
    if (timestamp.toString().length === 10) {
      return timestamp * 1000;
    }
    
    // If it looks like milliseconds (13 digits), use as-is
    if (timestamp.toString().length === 13) {
      return timestamp;
    }
    
    throw new Error(`Invalid timestamp: ${timestamp}`);
  }
  
  static ensureSeconds(timestamp) {
    // If it looks like milliseconds, convert to seconds
    if (timestamp.toString().length === 13) {
      return Math.floor(timestamp / 1000);
    }
    
    // If it looks like seconds, use as-is
    if (timestamp.toString().length === 10) {
      return timestamp;
    }
    
    throw new Error(`Invalid timestamp: ${timestamp}`);
  }
  
  static validateTimestamp(timestamp) {
    const date = new Date(this.ensureMilliseconds(timestamp));
    const year = date.getFullYear();
    
    // Reasonable range: 1990-2100
    if (year < 1990 || year > 2100) {
      throw new Error(`Timestamp out of reasonable range: ${date.toISOString()}`);
    }
    
    return date;
  }
}

// Usage
const eventDate = TimestampUtils.validateTimestamp(apiResponse.timestamp);

✅ API Design: Clear Timestamp Formats

{
  "event": {
    // Explicit units in field names
    "created_at_seconds": 1692134400,
    "created_at_ms": 1692134400000,
    
    // Even better: use ISO 8601 strings
    "created_at": "2023-08-15T22:00:00.000Z",
    
    // Multiple formats for compatibility
    "timestamps": {
      "iso": "2023-08-15T22:00:00.000Z",
      "unix_seconds": 1692134400,
      "unix_milliseconds": 1692134400000
    }
  }
}

✅ Python: Robust Timestamp Conversion

import time
from datetime import datetime
from typing import Union

class TimestampConverter:
    @staticmethod
    def to_datetime(timestamp: Union[int, float, str]) -> datetime:
        """Convert various timestamp formats to datetime object"""
        
        if isinstance(timestamp, str):
            # Assume ISO 8601 string
            return datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
        
        # Numeric timestamp - detect unit by magnitude
        if timestamp > 1e12:  # Looks like milliseconds
            return datetime.fromtimestamp(timestamp / 1000)
        elif timestamp > 1e9:  # Looks like seconds
            return datetime.fromtimestamp(timestamp)
        else:
            raise ValueError(f"Timestamp {timestamp} doesn't look valid")
    
    @staticmethod
    def ensure_seconds(timestamp: Union[int, float]) -> int:
        """Ensure timestamp is in seconds"""
        if timestamp > 1e12:  # Milliseconds
            return int(timestamp / 1000)
        elif timestamp > 1e9:  # Already seconds
            return int(timestamp)
        else:
            raise ValueError(f"Invalid timestamp: {timestamp}")
    
    @staticmethod
    def ensure_milliseconds(timestamp: Union[int, float]) -> int:
        """Ensure timestamp is in milliseconds"""
        if timestamp > 1e12:  # Already milliseconds
            return int(timestamp)
        elif timestamp > 1e9:  # Seconds
            return int(timestamp * 1000)
        else:
            raise ValueError(f"Invalid timestamp: {timestamp}")

# Usage examples
converter = TimestampConverter()

# These all work regardless of input unit
dt1 = converter.to_datetime(1692134400)      # Seconds
dt2 = converter.to_datetime(1692134400000)   # Milliseconds
dt3 = converter.to_datetime("2023-08-15T22:00:00Z")  # ISO string

🔍 Detection Strategies

1. Sanity Check Ranges

Validate that timestamps fall within reasonable ranges for your application.

// Reasonable range: 2000-2100
if (timestamp < 946684800 || timestamp > 4102444800)

2. Digit Count Validation

Unix seconds: ~10 digits, JavaScript milliseconds: ~13 digits.

// Check digit count
const isMilliseconds = timestamp.toString().length === 13

3. Date Reasonableness

Convert to human-readable date and check if it makes sense.

// Should be recent
const date = new Date(timestamp * 1000)

4. API Documentation

Always explicitly document timestamp units in your API specs.

// timestamp_seconds: Unix timestamp in seconds
// timestamp_ms: Unix timestamp in milliseconds

🛡️ Prevention Strategies

1. Use ISO 8601 Strings

Avoid numeric timestamps entirely. Use standardized date strings.

"2023-08-15T22:00:00.000Z"

2. Explicit Unit Naming

Include the unit in field names: timestamp_seconds, timestamp_ms.

created_at_seconds: 1692134400

3. Validation Functions

Create utility functions that validate and convert timestamps safely.

function ensureMilliseconds(timestamp)

4. Type Systems

Use TypeScript or other type systems to enforce timestamp types.

type UnixSeconds = number

📚 Lessons Learned

Units matter: A 1000x difference in timestamp interpretation can break everything.

Be explicit: Always document and validate timestamp units in APIs and databases.

Prefer standards: ISO 8601 strings eliminate unit confusion entirely.

Test edge cases: Include timestamp validation in your test suites.

💬 Share Your Experience

Have you been bitten by the milliseconds vs seconds bug? Share your story and help others avoid this common pitfall. The more we document these issues, the fewer developers will fall into the same trap.