Great work!

XP to next level

BugEater

Milliseconds vs. Seconds: Precision Bugs in Timestamps

Learning Objectives

By the end of this lesson you will be able to:

  • Identify which languages and APIs return timestamps in milliseconds versus seconds
  • Explain the classic precision bug and its visible symptoms
  • Design black-box test cases that detect millisecond/second confusion
  • Interpret suspiciously large or small timestamps during log analysis

The Precision Divide

Different programming environments treat "a timestamp" differently:

Language / API Function Unit
JavaScript Date.now() Milliseconds
JavaScript new Date().getTime() Milliseconds
Java Instant.now().getEpochSecond() Seconds
Java Instant.now().toEpochMilli() Milliseconds
Python time.time() Seconds (float)
Python datetime.now().timestamp() Seconds (float)
Unix shell date +%s Seconds

The rule of thumb: JavaScript timestamps are in milliseconds by default. Most other environments default to seconds. This single difference is the source of one of the most common date-handling bugs in web applications.

The Classic Bug: Milliseconds Treated as Seconds

Imagine a JavaScript front-end that calls Date.now() and sends the result to a Java back-end that expects seconds. The value 1_700_000_000_000 (November 2023 in milliseconds) arrives, and Java's Instant.ofEpochSecond(1_700_000_000_000L) interprets it as 1.7 trillion seconds after 1970. That maps to roughly the year 55,792 — a date far in the future that will break virtually any date range validation or database column.

Symptoms you will see in a black-box test:

  • A "created date" or "expires at" field displays a year like 53000, 55000, or similar nonsense
  • The application accepts the record but then fails when rendering or querying by date range
  • A calendar widget simply refuses to render because the year is out of bounds

The Reverse Bug: Seconds Treated as Milliseconds

If a seconds timestamp is fed into a system that expects milliseconds, the opposite happens. 1_700_000_000 (seconds, November 2023) interpreted as milliseconds is only 1_700_000 seconds after epoch, which maps to January 20, 1970 — nearly 54 years in the past.

Symptoms:

  • Dates appear near 1970-01-01
  • Relative fields like "days since registration" show impossibly large values (19,000+ days)
  • Historical data queries return the record unexpectedly

Detecting Precision Bugs in Black-Box Testing

You cannot see the code, but you can observe the output:

  1. Inspect the raw API response — look at the timestamp field. Is it 10 digits (seconds) or 13 digits (milliseconds)? A 13-digit value like 1700000000000 signals millisecond precision.
  2. Calculate expected vs. actual — if a record was just created and its created_at field shows a year in the 1970s or 50000s, a precision bug is almost certain.
  3. Compare across endpoints — if one endpoint returns 10-digit timestamps and another returns 13-digit timestamps for the same concept, the system is inconsistent.
  4. Use anchor dates — create a record at a known moment and immediately read it back. The returned timestamp should map to approximately now, not to 1970 or 55000.

Reporting the Bug

A precision bug report should include:

  • The raw numeric timestamp value observed (e.g., 1700000000000)
  • The date it incorrectly displays (e.g., "Year 55792")
  • The expected behavior (e.g., "Should display 2023-11-14")
  • The endpoint and field name
  • Whether the bug appears on write, read, or both

Summary

Millisecond vs. second confusion is one of the most impactful and easily missed timestamp bugs. Knowing that JavaScript operates in milliseconds while Java and Python operate in seconds gives you the pattern recognition to spot these issues quickly, even without access to source code.

Quiz

What does Date.now() return in JavaScript?

What happens when a millisecond-precision timestamp (13 digits) is stored in a system that expects seconds?

A black-box test creates a user account and immediately reads it back. The created_at field returns 1970-01-20. What is the most likely cause?

Which language's standard function returns seconds (not milliseconds) by default?