ENES
unix-timestampEngineering Guide

Unix Timestamp: Milliseconds vs Seconds - Avoid the Off-By-1000 Bug

AC
Alex ChenΒ·Lead Systems Architect
Published on 2026-09-25Β·7 min readΒ·Daily Toolbox Engineering

Unix Timestamp: Milliseconds vs Seconds - Avoid the Off-By-1000 Bug

Your API returns a timestamp. You pass it to new Date(). The result shows January 1, 1970 instead of today's date.

Or worse: your calendar widget displays events in the year 53,000 AD.

This isn't science fiction. It's the milliseconds vs seconds bugβ€”one of the most common (and maddening) time-related errors in programming.

This guide explains exactly what causes it, how to detect it instantly, and how to prevent it in your code.


The Bug: When Timestamps Are Off By 1000x

Symptoms

Problem 1: Dates stuck in 1970

const timestamp = 1609459200;  // Your API returns this
const date = new Date(timestamp);
console.log(date);  // 1970-01-19 (WRONG!)

Problem 2: Dates in the distant future

const timestamp = 1609459200000;  // Your database returns this
const unixSeconds = timestamp;  // You forget to divide by 1000
// Result: Year 53,000+ dates

Problem 3: "Invalid Date" errors

const timestamp = "1609459200";  // String instead of number
const date = new Date(timestamp);  // NaN or Invalid Date

Root Cause: Two Different Unix Timestamp Standards

Unix timestamps measure time since January 1, 1970, 00:00:00 UTC (the "Unix Epoch").

But there are two units:

Unit Example Digit Count Used By
Seconds 1609459200 10 digits Python, Unix date, PHP, PostgreSQL TIMESTAMP
Milliseconds 1609459200000 13 digits JavaScript, Java, MySQL UNIX_TIMESTAMP() * 1000

The problem:

  • JavaScript's Date() expects milliseconds
  • Most other systems use seconds
  • Mixing them causes 1000x errors

How to Detect Which Format You Have

Method 1: Count the Digits

const timestamp = 1609459200;  // Your value

if (timestamp.toString().length === 10) {
  console.log("Seconds format");
  // Convert to milliseconds: timestamp * 1000
} else if (timestamp.toString().length === 13) {
  console.log("Milliseconds format");
  // Use directly in JavaScript
} else {
  console.error("Invalid timestamp");
}

Method 2: Check If Date Is Reasonable

function isValidTimestamp(timestamp) {
  const date = new Date(timestamp);
  const year = date.getFullYear();
  
  // Reasonable range: 1970-2100
  return year >= 1970 && year <= 2100;
}

// Test
isValidTimestamp(1609459200);     // false (1970)
isValidTimestamp(1609459200000);  // true (2021)

Method 3: Use a Timestamp Converter Tool

Paste your timestamp into the free DailyToolbox Unix Timestamp Converter:

  • Seconds: Shows a recent date
  • Milliseconds: Shows a date 1000x later (or in 1970)

Common Scenarios Where This Bug Appears

Scenario 1: JavaScript ↔ Python API

Python backend (returns seconds):

import time
timestamp = int(time.time())  # 1609459200 (10 digits)
return {"created_at": timestamp}

JavaScript frontend (expects milliseconds):

fetch('/api/data')
  .then(res => res.json())
  .then(data => {
    // BUG: created_at is in seconds, but Date() expects milliseconds
    const date = new Date(data.created_at);  // Shows 1970!
    
    // FIX: Multiply by 1000
    const date = new Date(data.created_at * 1000);  // Correct!
  });

Scenario 2: SQL TIMESTAMP β†’ JavaScript

PostgreSQL (stores seconds):

SELECT EXTRACT(EPOCH FROM created_at) AS unix_time FROM orders;
-- Returns: 1609459200 (10 digits)

JavaScript (expects milliseconds):

const rows = await db.query('SELECT ...');
const timestamp = rows[0].unix_time;

// BUG
const date = new Date(timestamp);  // 1970!

// FIX
const date = new Date(timestamp * 1000);  // Correct

Scenario 3: Redis TTL β†’ JavaScript setTimeout

Redis (uses seconds):

// Set key to expire in 3600 seconds (1 hour)
await redis.set('session:123', data, 'EX', 3600);

JavaScript (expects milliseconds):

const ttl = await redis.ttl('session:123');  // Returns 3600 (seconds)

// BUG: setTimeout expects milliseconds
setTimeout(() => {
  console.log('Session expired');
}, ttl);  // Fires after 3.6 seconds instead of 1 hour!

// FIX: Convert to milliseconds
setTimeout(() => {
  console.log('Session expired');
}, ttl * 1000);  // Correct: 1 hour

Scenario 4: Inconsistent API Documentation

API docs say:

"Returns Unix timestamp"

But which format?

  • Could be seconds
  • Could be milliseconds
  • Could be microseconds (rare, but some systems use it)

Solution: Always test with actual data and check if the date makes sense.


How to Fix: Universal Timestamp Handling Function

Use this helper to automatically detect and convert:

function parseTimestamp(timestamp) {
  // Handle string inputs
  if (typeof timestamp === 'string') {
    timestamp = parseInt(timestamp, 10);
  }
  
  // Detect format by digit count
  const digits = timestamp.toString().length;
  
  if (digits === 10) {
    // Seconds β†’ convert to milliseconds
    return new Date(timestamp * 1000);
  } else if (digits === 13) {
    // Milliseconds β†’ use directly
    return new Date(timestamp);
  } else if (digits === 16) {
    // Microseconds (rare) β†’ convert to milliseconds
    return new Date(timestamp / 1000);
  } else {
    throw new Error(`Invalid timestamp: ${timestamp}`);
  }
}

// Usage
parseTimestamp(1609459200);      // Correctly handles seconds
parseTimestamp(1609459200000);   // Correctly handles milliseconds
parseTimestamp("1609459200");    // Correctly handles strings

Prevention: Best Practices

βœ… 1. Always Document Timestamp Units in APIs

Bad:

{
  "created_at": 1609459200
}

Good:

{
  "created_at_seconds": 1609459200,
  "created_at_ms": 1609459200000
}

Or use descriptive field names:

{
  "created_at_unix": 1609459200,     // Seconds
  "created_at_epoch_ms": 1609459200000  // Milliseconds
}

βœ… 2. Use ISO 8601 Strings Instead

Avoid the problem entirely:

{
  "created_at": "2021-01-01T00:00:00Z"  // ISO 8601
}

Advantages:

  • Human-readable
  • No ambiguity
  • Works across all languages
  • Includes timezone info

JavaScript handling:

const date = new Date("2021-01-01T00:00:00Z");  // Works perfectly

βœ… 3. Add Unit Suffix to Variable Names

// BAD (ambiguous)
const timestamp = 1609459200;

// GOOD (clear)
const timestampSeconds = 1609459200;
const timestampMs = 1609459200000;

βœ… 4. Write Tests for Edge Cases

describe('Timestamp handling', () => {
  it('should handle seconds format', () => {
    const date = parseTimestamp(1609459200);
    expect(date.getFullYear()).toBe(2021);
  });
  
  it('should handle milliseconds format', () => {
    const date = parseTimestamp(1609459200000);
    expect(date.getFullYear()).toBe(2021);
  });
  
  it('should reject invalid timestamps', () => {
    expect(() => parseTimestamp(123)).toThrow();
  });
});

Real-World Examples

Example 1: Node.js API That Returns Seconds

// Backend: Express.js
app.get('/api/user', (req, res) => {
  const user = {
    id: 123,
    created_at: Math.floor(Date.now() / 1000)  // Seconds
  };
  res.json(user);
});

// Frontend: React
fetch('/api/user')
  .then(res => res.json())
  .then(user => {
    // Must multiply by 1000
    const date = new Date(user.created_at * 1000);
    console.log(date.toLocaleString());
  });

Example 2: Python β†’ JavaScript Data Pipeline

Python script exports CSV:

import time
import csv

with open('events.csv', 'w') as f:
    writer = csv.writer(f)
    writer.writerow(['event_id', 'timestamp'])
    writer.writerow([1, int(time.time())])  # Seconds

JavaScript reads CSV:

const fs = require('fs');
const csv = require('csv-parser');

fs.createReadStream('events.csv')
  .pipe(csv())
  .on('data', (row) => {
    // Must convert to milliseconds
    const date = new Date(row.timestamp * 1000);
    console.log(date);
  });

FAQ

Q: Why does JavaScript use milliseconds when everyone else uses seconds?
A: Historical reasons. JavaScript's Date object was designed in the 1990s to match Java's java.util.Date, which uses milliseconds.

Q: How do I get the current Unix timestamp in JavaScript?
A: Seconds: Math.floor(Date.now() / 1000)
Milliseconds: Date.now()

Q: What about timezone issues?
A: Unix timestamps are always UTC. Timezones only matter when you display the date to users. Use toLocaleString() or libraries like Luxon/date-fns for timezone handling.

Q: Can I use Date.parse() instead of new Date()?
A: Date.parse() expects an ISO 8601 string, not a Unix timestamp. Use new Date(timestamp) for numeric timestamps.


Debugging Checklist

When you see a date bug:

☐ Is the timestamp in seconds or milliseconds?
☐ Did you multiply/divide by 1000 correctly?
☐ Is the timestamp a string instead of a number?
☐ Does the API documentation specify the unit?
☐ Does the date make logical sense (between 1970-2100)?
☐ Are you mixing UTC and local time?


Conclusion

The milliseconds vs seconds bug is 100% preventable. The solution is simple:

  1. Always document which format you're using
  2. Use ISO 8601 strings when possible
  3. Auto-detect the format with digit counting
  4. Test with real data before deploying

Remember: A 10-digit timestamp is seconds. A 13-digit timestamp is milliseconds. Everything else is probably wrong.

#unix-timestamp#javascript#debugging#time
AC
Written by Alex ChenLead Architect

Alex Chen is a distributed systems engineer and core maintainer at Daily Toolbox with over 10 years of experience in client-side web technologies, RFC standards compliance, and cryptographic protocols. He specializes in zero-knowledge client architectures and WebAssembly-accelerated algorithms.

Try the free tools mentioned above

⚑ Open Unix Timestamp Converter β†’