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:
- Inspect the raw API response — look at the timestamp field. Is it 10 digits (seconds) or 13 digits (milliseconds)? A 13-digit value like
1700000000000signals millisecond precision. - Calculate expected vs. actual — if a record was just created and its
created_atfield shows a year in the 1970s or 50000s, a precision bug is almost certain. - Compare across endpoints — if one endpoint returns 10-digit timestamps and another returns 13-digit timestamps for the same concept, the system is inconsistent.
- 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.