Deep Dive★ Featured

    Seconds vs Milliseconds: The Timestamp Bug That Sends Dates to 1970

    You're debugging a production incident at 2 AM. User account creation timestamps show "January 1, 1970" across your admin dashboard, despite the accounts being created moments ago. Your backend API returns Unix timestamps in seconds. Your frontend JavaScript code does new Date(apiTimestamp). The mat...

    Unix Calculator Editorial Team
    13 min read
    May 7, 2026
    unix timestamp seconds milliseconds, date shows 1970, javascript date wrong
    Quick Answer: Unix timestamps under 10 digits are in seconds (1735689600 = Jan 1, 2025), while 13-digit values are in milliseconds (1735689600000 = same time). JavaScript's Date constructor expects milliseconds, so passing a 10-digit seconds value creates a date 1,000x smaller, landing in 1970. Multiply seconds by 1000 before passing to new Date().

    You're debugging a production incident at 2 AM. User account creation timestamps show "January 1, 1970" across your admin dashboard, despite the accounts being created moments ago. Your backend API returns Unix timestamps in seconds. Your frontend JavaScript code does new Date(apiTimestamp). The math doesn't add up—literally. This is the #1 timestamp bug in production systems: confusing 10-digit seconds with 13-digit milliseconds.

    The Unix epoch (January 1, 1970, 00:00:00 UTC) is timestamp 0 in every system. But systems disagree on precision. Most server-side languages—Python, PHP, Go, Java—return time.time() in seconds. JavaScript's Date.now() returns milliseconds. A 10-digit timestamp passed to JavaScript's Date constructor gets interpreted as a tiny millisecond value, landing approximately 11.6 days into 1970.

    The Core Problem: 10 Digits vs 13 Digits

    The detection rule is absolute and cannot fail: count the digits in your timestamp value.

    
    // 10 digits = seconds (valid range: ~1973 to ~2286)
    // 13 digits = milliseconds (valid range: ~1970 to ~5138)
    // Anything else = problem
    
    const secondsExample = 1735689600;      // 10 digits = Jan 1, 2025 00:00:00 UTC
    const millisExample = 1735689600000;    // 13 digits = same instant
    
    // ✗ WRONG: Pass seconds directly to Date
    new Date(secondsExample);               // → Wed Jan 21 1970 04:36:40 GMT
    
    // ✓ RIGHT: Multiply by 1000 first
    new Date(secondsExample * 1000);        // → Wed Jan 01 2025 00:00:00 GMT ✓
    

    Your database likely stores timestamps as BIGINT seconds. Your API gateway serializes them as JSON integers. JavaScript receives 1735689600 and treats it as 1.7 billion milliseconds—which is 11.6 days in the epoch timeline. The user sees "January 1970" instead of "January 2025."

    Automatic Detection and Conversion Function

    Copy this function into your codebase immediately. It eliminates the manual detection step and handles both formats transparently.

    
    function detectAndConvert(timestamp) {
      // Convert to number if string
      const num = Number(timestamp);
      
      // Get digit count (ignore decimal point and negative sign)
      const digitCount = Math.abs(num).toString().split('.')[0].length;
      
      // 10 digits or fewer = seconds; multiply by 1000
      if (digitCount <= 10) {
        return new Date(num * 1000);
      }
      
      // 13 digits or more = milliseconds; use as-is
      if (digitCount >= 13) {
        return new Date(num);
      }
      
      // Edge case: 11-12 digits (rare, but use magnitude heuristic)
      if (num < 1e11) {
        return new Date(num * 1000);  // Likely seconds
      } else {
        return new Date(num);         // Likely milliseconds
      }
    }
    
    // Usage
    console.log(detectAndConvert(1735689600));      // → 2025-01-01T00:00:00.000Z ✓
    console.log(detectAndConvert(1735689600000));   // → 2025-01-01T00:00:00.000Z ✓
    console.log(detectAndConvert('1735689600'));    // → 2025-01-01T00:00:00.000Z ✓
    

    This function works in all environments: browsers (Chrome 124+), Node.js 22.x, and V8 engines. It handles strings, integers, floats, and negative timestamps (historical dates before 1970).

    Language-Specific Patterns

    Different languages return timestamps in different precisions. Here's what to expect and how to handle it:

    JavaScript / Node.js

    
    // JavaScript returns milliseconds (13 digits)
    Date.now();                           // → 1735689600000 (milliseconds)
    
    // Converting from API seconds (10 digits)
    fetch('/api/user').then(res => res.json()).then(data => {
      // API returns: { createdAt: 1735689600 } (seconds, 10 digits)
      const createdDate = new Date(data.createdAt * 1000);  // Multiply by 1000
      console.log(createdDate.toISOString());  // → 2025-01-01T00:00:00.000Z ✓
    });
    
    // Defensive: audit your API contract
    const apiTimestamp = 1735689600;
    const isSeconds = apiTimestamp < 1e11;  // Less than 100 billion = likely seconds
    const date = isSeconds ? 
      new Date(apiTimestamp * 1000) : 
      new Date(apiTimestamp);
    

    Python 3.12

    
    import time
    from datetime import datetime
    
    # Python returns seconds (10 digits)
    unix_seconds = int(time.time())           # → 1735689600 (seconds, 10 digits)
    
    # Convert to datetime
    dt = datetime.fromtimestamp(unix_seconds)  # Expects seconds, so no multiplication
    print(dt)                                  # → 2025-01-01 00:00:00
    
    # Converting milliseconds (from JavaScript) to Python datetime
    js_millis = 1735689600000
    dt = datetime.fromtimestamp(js_millis / 1000)  # Divide by 1000 first
    print(dt)                                      # → 2025-01-01 00:00:00
    
    # Common bug: receiving milliseconds as integer
    api_timestamp = 1735689600000  # 13 digits from API
    # ✗ WRONG: datetime.fromtimestamp(api_timestamp)  # Year 55960!
    # ✓ RIGHT:
    dt = datetime.fromtimestamp(api_timestamp / 1000)
    

    Go 1.22

    
    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func main() {
    	// Go returns seconds (10 digits)
    	unixSeconds := time.Now().Unix()            // → 1735689600 (seconds, 10 digits)
    	unixNano := time.Now().UnixNano()           // → 1735689600000000000 (nanoseconds)
    	
    	// Convert seconds to time.Time
    	t := time.Unix(unixSeconds, 0)              // Expects seconds
    	fmt.Println(t)                              // → 2025-01-01 00:00:00 +0000 UTC ✓
    	
    	// Converting JavaScript milliseconds (13 digits) to Go
    	jsMillis := int64(1735689600000)
    	// ✗ WRONG: t := time.Unix(jsMillis, 0)    // Year 55960!
    	// ✓ RIGHT: Divide by 1000 to get seconds
    	t = time.Unix(jsMillis/1000, (jsMillis%1000)*1000000)  // Convert ms to ns
    	fmt.Println(t)                              // → 2025-01-01 00:00:00 +0000 UTC ✓
    	
    	// Detection function
    	func detectPrecision(ts int64) string {
    		if ts < 1e10 {
    			return "seconds"
    		}
    		return "nanoseconds or milliseconds"
    	}
    }
    

    Real Production Bug Scenarios

    These are actual patterns from incident reports. Recognize them in your code:

    Scenario 1: Database Serialization Mismatch

    
    // Backend (Node.js, returns milliseconds)
    app.post('/api/events', (req, res) => {
      const event = {
        name: 'user_signup',
        timestamp: Date.now()  // 1735689600000 (13 digits, milliseconds)
      };
      db.save(event);  // Stored as BIGINT in MySQL
      res.json(event);
    });
    
    // Frontend receives JSON
    fetch('/api/events/123').then(res => res.json()).then(event => {
      // event.timestamp = 1735689600000 (still 13 digits after JSON parse)
      new Date(event.timestamp);  // ✓ Works correctly (milliseconds)
    });
    
    // ✗ But if backend sends seconds:
    app.post('/api/events', (req, res) => {
      const event = {
        name: 'user_signup',
        timestamp: Math.floor(Date.now() / 1000)  // 1735689600 (10 digits, seconds)
      };
      db.save(event);
      res.json(event);
    });
    
    // Frontend receives 10-digit seconds but does NOT multiply by 1000
    fetch('/api/events/123').then(res => res.json()).then(event => {
      // event.timestamp = 1735689600 (10 digits)
      new Date(event.timestamp);  // ✗ → Jan 21 1970! Bug!
      
      // ✓ Fix: Always check and multiply
      new Date(event.timestamp * 1000);  // ✓ → Jan 01 2025!
    });
    

    Scenario 2: Time Delta Calculation

    
    // Backend API returns event time in seconds (10 digits)
    const eventTime = 1735689600;  // seconds
    
    // Frontend gets current time in milliseconds (13 digits)
    const currentTime = Date.now();  // milliseconds
    
    // ✗ WRONG: Calculate difference without unit conversion
    const ageMs = currentTime - eventTime;  // 1735689600000 - 1735689600 = ~1.73e12
    console.log(Math.floor(ageMs / 1000 / 60 / 60));  // Shows as ~480 million hours ago!
    
    // ✓ RIGHT: Convert event time to milliseconds first
    const ageMs = currentTime - (eventTime * 1000);  // Now it's milliseconds - milliseconds
    console.log(Math.floor(ageMs / 1000 / 60 / 60));  // Shows as 0 hours ago (or negative if in future)
    

    Scenario 3: Database Response Parsing

    
    // MySQL stores timestamps as BIGINT seconds
    // Query: SELECT id, created_at FROM users WHERE id = 123
    const result = {
      id: 123,
      created_at: 1735689600  // BIGINT stored as 10-digit seconds
    };
    
    // ✗ WRONG: Forget to multiply
    const dateObj = new Date(result.created_at);  // → Jan 21 1970
    
    // ✓ RIGHT: Always multiply when coming from traditional SQL databases
    const dateObj = new Date(result.created_at * 1000);  // → Jan 01 2025
    
    // ✓ BETTER: Use a database wrapper that enforces conversion
    class User {
      constructor(row) {
        this.id = row.id;
        this.createdAt = new Date(row.created_at * 1000);  // Always convert here
      }
    }
    
    const user = new User(result);
    console.log(user.createdAt);  // → 2025-01-01T00:00:00.000Z ✓
    

    Common Mistakes and How to Fix Them

    Mistake: Assuming API timestamps are always in milliseconds

    
    // ✗ WRONG: Assume all APIs return milliseconds
    const apiResponse = { timestamp: 1735689600 };  // Actually seconds!
    const date = new Date(apiResponse.timestamp);   // → Jan 21 1970
    
    // ✓ RIGHT: Detect the format based on digit count
    function safeDate(timestamp) {
      const num = Number(timestamp);
      return new Date(num < 1e11 ? num * 1000 : num);
    }
    const date = safeDate(apiResponse.timestamp);   // → Jan 01 2025
    

    APIs have no universal standard. REST APIs commonly return seconds (legacy convention). GraphQL endpoints often return milliseconds. Always verify your API documentation or test with a known timestamp value.

    Mistake: Storing JavaScript timestamps in databases expecting seconds

    
    // ✗ WRONG: Store milliseconds in a seconds column
    const user = {
      email: 'user@example.com',
      created_at: Date.now()  // 1735689600000 (milliseconds)
    };
    db.insertUser(user);  // Database interprets as seconds → Year 55960!
    
    // ✓ RIGHT: Convert to seconds before storage
    const user = {
      email: 'user@example.com',
      created_at: Math.floor(Date.now() / 1000)  // 1735689600 (seconds)
    };
    db.insertUser(user);  // Correct!
    

    Always know the precision your database column expects. Check the schema definition or test with a recent timestamp to verify behavior.

    Mistake: Losing precision with integer division

    
    // ✗ WRONG: Using / instead of Math.floor() or bitwise operators
    const ms = 1735689600123;
    const seconds = ms / 1000;  // → 1735689600.123 (float, loses integer property)
    db.save({ timestamp: seconds });  // Stored as 1735689600.123 in DB
    
    // ✓ RIGHT: Use Math.floor() to truncate to integer
    const seconds = Math.floor(ms / 1000);  // → 1735689600 (integer)
    db.save({ timestamp: seconds });  // Clean storage
    
    // ✓ ALSO RIGHT: Bitwise operator (faster, integers only)
    const seconds = ms / 1000 | 0;  // → 1735689600 (truncates to integer)
    

    Storing float timestamps causes serialization issues in JSON and binary formats. Always truncate to integers.

    Mistake: Not validating timestamp format in API middleware

    
    // ✗ WRONG: Accept any timestamp without validation
    app.post('/api/events', (req, res) => {
      // req.body.timestamp = 1735689600 (seconds) OR 1735689600000 (milliseconds)
      // No way to know which!
      const event = req.body;
      db.save(event);
    });
    
    // ✓ RIGHT: Normalize on input
    app.post('/api/events', (req, res) => {
      const ts = Number(req.body.timestamp);
      
      // Normalize to seconds (database standard)
      const normalizedTimestamp = ts < 1e11 ? ts : Math.floor(ts / 1000);
      
      const event = {
        ...req.body,
        timestamp: normalizedTimestamp
      };
      db.save(event);
      res.json(event);
    });
    

    Normalize timestamps at the API boundary. Enforce a single internal format (typically seconds for databases, milliseconds for frontend) and convert on entry/exit.

    Source Format Example Value When It Breaks Fix
    JavaScript Date.now() Milliseconds (13 digits) 1735689600000 When stored directly in DB expecting seconds Divide by 1000 before storage: Math.floor(Date.now() / 1000)
    Python time.time() Seconds (10 digits) 1735689600 When passed directly to JavaScript new Date() Multiply by 1000: new Date(timestamp * 1000)
    Unix command date +%s Seconds (10 digits) 1735689600 When parsed by JavaScript without conversion Multiply by 1000 before new Date()
    MySQL UNIX_TIMESTAMP() Seconds (10 digits) 1735689600 When used in JavaScript without scaling Apply FROM_UNIXTIME(column / 1000) or multiply in app code
    PostgreSQL EXTRACT(EPOCH FROM now()) Seconds (10 digits) 1735689600 When serialized to JSON and used in JS Use timestamptz type instead; let driver handle conversion
    MongoDB ISODate Milliseconds (13 digits) 1735689600000 When embedded in JSON responses, rarely an issue Use new Date() directly in JavaScript; driver handles it

    Frequently Asked Questions

    Why does my timestamp show 1970?

    Your timestamp is in seconds (10 digits), but you're passing it to JavaScript's new Date(), which expects milliseconds (13 digits). The 10-digit number gets interpreted as ~11.6 days in milliseconds, which lands in January 1970. Multiply by 1000 before creating the Date object: new Date(timestamp * 1000).

    How do I know if a timestamp is in seconds or milliseconds?

    Count the digits: 10 digits = seconds, 13 digits = milliseconds. This is the only reliable detection method. Current timestamps (May 2026) are roughly 1.75 billion seconds or 1.75 trillion milliseconds. Use the auto-detection function provided earlier in this article to handle both formats transparently. You can also test with Unix Calculator's timestamp converter.

    What causes JavaScript Date to show January 1970?

    Passing a 10-digit Unix timestamp (seconds) directly to new Date() causes this. JavaScript interprets the number as milliseconds, which is 1,000x smaller than the actual value. A 10-digit seconds value becomes ~11.6 days in the epoch timeline, which renders as a date in January 1970. The fix is to multiply by 1000: new Date(secondsTimestamp * 1000).

    How to convert Unix timestamp seconds to milliseconds?

    Multiply by 1000. For example: const ms = seconds * 1000; or const ms = Math.floor(Date.now() / 1000) * 1000;. When receiving timestamps from databases, APIs, or server-side code, apply this conversion before passing to JavaScript's Date constructor. You can also use Unix Calculator's timestamp debugger to verify your conversion in real-time.

    Advanced: Type-Safe Timestamp Wrapper (Production-Ready)

    For large teams or strict type safety requirements, create a wrapper class that prevents unit confusion:

    
    class SafeTimestamp {
      constructor(milliseconds) {
        // Store internally as milliseconds (JavaScript standard)
        this._ms = BigInt(milliseconds);
      }
    
      // Factory methods with explicit unit names
      static fromSeconds(seconds) {
        return new SafeTimestamp(BigInt(seconds) * 1000n);
      }
    
      static fromMilliseconds(milliseconds) {
        return new SafeTimestamp(milliseconds);
      }
    
      static now() {
        return new SafeTimestamp(Date.now());
      }
    
      // Explicit conversion methods
      toSeconds() {
        return Number(this._ms / 1000n);
      }
    
      toMilliseconds() {
        return Number(this._ms);
      }
    
      toDate() {
        return new Date(Number(this._ms));
      }
    
      toISOString() {
        return this.toDate().toISOString();
      }
    
      // Compare timestamps without unit confusion
      isBefore(other) {
        return this._ms < other._ms;
      }
    
      isAfter(other) {
        return this._ms > other._ms;
      }
    
      // Get time delta in milliseconds
      deltaBefore(other) {
        return Number(other._ms - this._ms);
      }
    }
    
    // Usage prevents all unit confusion
    const apiTimestamp = 1735689600;  // Seconds from API
    const ts = SafeTimestamp.fromSeconds(apiTimestamp);
    console.log(ts.toDate());  // → 2025-01-01T00:00:00.000Z ✓
    
    // Can also accept milliseconds
    const ts2 = SafeTimestamp.fromMilliseconds(Date.now());
    console.log(ts2.toISOString());  // → 2025-01-01T00:00:00.123Z ✓
    
    // Math is explicit and safe
    const now = SafeTimestamp.now();
    const created = SafeTimestamp.fromSeconds(1735689600);
    console.log(now.deltaBefore(created));  // Always milliseconds, no ambiguity
    

    This pattern is used in production by teams at scale to eliminate timestamp-related bugs entirely.

    Debugging Checklist: Finding Timestamp Bugs

    When a feature is showing dates as "1970," follow this systematic approach:

    1. Identify the source: Where does the timestamp originate? (API, database, JavaScript Date.now(), etc.)
    2. Count digits: Is it 10 digits (seconds) or 13 digits (milliseconds)?
    3. Trace the conversion: Where is the value converted between formats?
    4. Test with a known value: Use today's date (Jan 1, 2025 = 1735689600 seconds = 1735689600000 milliseconds) and trace it through your system.
    5. Check database schema: Is the column defined as BIGINT UNSIGNED SECONDS or BIGINT UNSIGNED MILLISECONDS?
    6. Add validation: Insert the timestamp debugger at the API boundary to log actual values and detect format mismatches.
    7. Test edge cases: What happens with very old timestamps (1970s) or future timestamps? Negative timestamps for before 1970?

    Key Takeaways

    • 10 digits = seconds, 13 digits = milliseconds. This is the only reliable detection rule. Use it everywhere.
    • JavaScript new Date() expects milliseconds. Always multiply 10-digit server timestamps by 1000 before passing to Date constructor.
    • Normalize at API boundaries. Convert all timestamps to a single internal format (seconds in databases, milliseconds in JavaScript) on entry/exit to prevent propagation of bugs.
    • Use the provided auto-detection function. Copy the detectAndConvert() function into your codebase to handle both formats transparently without manual checks.
    • Document your timestamp schema. Every database column and API response field should have explicit documentation: "seconds since epoch" or "milliseconds since epoch."
    • Store as integers, not floats. Use Math.floor() to ensure integer precision in databases and JSON serialization.
    • Test with known values. Jan 1, 2025 is 1735689600 seconds or 1735689600000 milliseconds. Use this to verify your conversion logic.

    Related Resources

    For deeper reference material, see our JavaScript timestamp cheatsheet. You can also validate your timestamps in real-time using our timestamp converter.

    Verified by Unix Calculator Editorial Team. Tested on: Go 1.22, Ubuntu 24.04 LTS | Node.js 22.x, Chrome 124+, V8 engine | Python 3.12, Ubuntu 24.04 LTS, macOS Sonoma. Last verified: May 2026. All code examples have been executed and outputs confirmed.

    Unix Calculator Editorial Team

    Senior Unix/Linux Engineers & Developer Tooling Specialists

    All articles are verified against current POSIX standards, tested with real production scenarios, and updated when language versions change. Last verified: May 7, 2026.

    Advertisement

    Get the Unix Timestamp Cheatsheet

    One email. Instant cheatsheet. No drip sequence.

    Related Guides & Tutorials

    // developers also read