DEV Community

Cover image for Unix Timestamp Bugs in JavaScript: Seconds, Milliseconds, and Time Zones Explained
ToolZenhub
ToolZenhub

Posted on

Unix Timestamp Bugs in JavaScript: Seconds, Milliseconds, and Time Zones Explained

You inspect an API response and find this:

{
  "userId": 1042,
  "createdAt": 1700000000,
  "lastLogin": 1700000000000
}
Enter fullscreen mode Exit fullscreen mode

Both fields represent Unix timestamps.

Both contain numbers.

But one is measured in seconds, and the other in milliseconds.

If you handle them incorrectly, your application might display a date in 1970, create a completely unrealistic future date, or incorrectly decide that an authentication token has expired.

These bugs are especially frustrating because the timestamps look valid.

Let's investigate how they happen, how to fix them in JavaScript, and how to debug timestamps more quickly.

1. What exactly is a Unix timestamp?

A Unix timestamp represents elapsed time since the Unix epoch:

January 1, 1970, at 00:00:00 UTC.

It represents an instant in time, independent of how that instant is displayed in different time zones.

For example:

1700000000
Enter fullscreen mode Exit fullscreen mode

This timestamp, measured in seconds, represents:

2023-11-14 22:13:20 UTC
Enter fullscreen mode Exit fullscreen mode

The same instant in India is:

2023-11-15 03:43:20 IST
Enter fullscreen mode Exit fullscreen mode

The underlying instant hasn't changed.

Only its local representation has changed.

That distinction is essential when working with APIs, logs, authentication tokens, databases, and international applications.

2. The most common mistake: seconds vs milliseconds

Unix timestamps are commonly represented in two units:

  • Seconds
  • Milliseconds

JavaScript's Date constructor expects a numeric timestamp in milliseconds.

Many backend APIs, including systems that use Unix seconds, provide timestamps in seconds.

This difference creates one of the most common timestamp bugs.

Incorrect example

const timestamp = 1700000000;

const date = new Date(timestamp);

console.log(date.toISOString());
Enter fullscreen mode Exit fullscreen mode

Output:

1970-01-20T16:13:20.000Z
Enter fullscreen mode Exit fullscreen mode

That is probably not the date you expected.

The problem is that JavaScript interpreted the value as milliseconds.

Correct example

const timestampInSeconds = 1700000000;

const date = new Date(
  timestampInSeconds * 1000
);

console.log(date.toISOString());
Enter fullscreen mode Exit fullscreen mode

Output:

2023-11-14T22:13:20.000Z
Enter fullscreen mode Exit fullscreen mode

Multiplying by 1,000 converts seconds into milliseconds.

The reverse mistake

What happens if you multiply an existing millisecond timestamp by 1,000?

const timestampInMilliseconds = 1700000000000;

const incorrectDate = new Date(
  timestampInMilliseconds * 1000
);

console.log(Number.isNaN(incorrectDate.getTime()));
Enter fullscreen mode Exit fullscreen mode

Output:

true
Enter fullscreen mode Exit fullscreen mode

The resulting numeric value exceeds JavaScript's supported Date range.

This is why blindly multiplying timestamps is dangerous.

The correct solution is to know the timestamp's unit before converting it.

3. Why guessing from the number of digits can fail

You might have seen code like this:

function convertTimestamp(value) {
  const milliseconds =
    String(value).length === 10
      ? value * 1000
      : value;

  return new Date(milliseconds);
}
Enter fullscreen mode Exit fullscreen mode

This might work for common modern timestamps.

But it is not a reliable universal solution.

Why?

Because timestamp length depends on the timestamp's value and unit.

For example:

  • Negative timestamps represent dates before the Unix epoch.
  • Earlier positive timestamps may have fewer digits.
  • Larger timestamps may have more digits.
  • Some systems use microseconds or nanoseconds.

A reliable API should document which unit it returns.

When possible, make the unit explicit in your code.

function fromUnixSeconds(seconds) {
  if (!Number.isFinite(seconds)) {
    throw new TypeError(
      "Expected a finite number of seconds"
    );
  }

  const date = new Date(seconds * 1000);

  if (Number.isNaN(date.getTime())) {
    throw new RangeError("Timestamp is outside Date range");
  }

  return date;
}

function fromUnixMilliseconds(milliseconds) {
  if (!Number.isFinite(milliseconds)) {
    throw new TypeError(
      "Expected a finite number of milliseconds"
    );
  }

  const date = new Date(milliseconds);

  if (Number.isNaN(date.getTime())) {
    throw new RangeError("Timestamp is outside Date range");
  }

  return date;
}

console.log(
  fromUnixSeconds(1700000000).toISOString()
);

console.log(
  fromUnixMilliseconds(1700000000000).toISOString()
);
Enter fullscreen mode Exit fullscreen mode

Both return:

2023-11-14T22:13:20.000Z
Enter fullscreen mode Exit fullscreen mode

These functions make the expected unit clear.

For production applications, also validate whether the input is an integer and whether it matches your API's contract.

4. A real-world bug: Authentication token expiration

Suppose your backend returns an expiration timestamp:

{
  "accessToken": "example-token",
  "expiresAt": 1700003600
}
Enter fullscreen mode Exit fullscreen mode

Assume the API contract specifies that expiresAt is measured in Unix seconds.

You want to determine whether the token has expired.

Incorrect comparison

const expiresAt = 1700003600;

const expired = Date.now() >= expiresAt;

console.log(expired);
Enter fullscreen mode Exit fullscreen mode

The problem?

Date.now() returns milliseconds.

expiresAt is in seconds.

You're comparing two different units.

The result will be incorrect for many real-world scenarios.

Correct comparison

const expiresAtSeconds = 1700003600;

const expiresAtMilliseconds =
  expiresAtSeconds * 1000;

const isExpired =
  Date.now() >= expiresAtMilliseconds;

console.log(isExpired);
Enter fullscreen mode Exit fullscreen mode

Now both values use milliseconds.

For real authentication systems, expiration checks may also incorporate clock skew or a safety margin.

And remember: decoding a JWT's expiration claim does not validate its signature or establish that the token is trustworthy.

Only trusted, properly verified token data should drive security decisions.

5. Unix timestamps and time zones

Here's another common misunderstanding:

A Unix timestamp does not have a time zone.

It identifies a particular instant.

Time zones determine how that instant is displayed to people.

Consider:

const timestamp = 1700000000;

const date = new Date(timestamp * 1000);

console.log(date.toISOString());
Enter fullscreen mode Exit fullscreen mode

Output:

2023-11-14T22:13:20.000Z
Enter fullscreen mode Exit fullscreen mode

Now display the same instant in India:

const indiaTime = new Intl.DateTimeFormat(
  "en-IN",
  {
    timeZone: "Asia/Kolkata",
    dateStyle: "medium",
    timeStyle: "medium",
    hourCycle: "h23"
  }
).format(date);

console.log(indiaTime);
Enter fullscreen mode Exit fullscreen mode

The exact presentation depends on locale formatting rules, but the time corresponds to:

15 Nov 2023, 03:43:20 IST
Enter fullscreen mode Exit fullscreen mode

Now consider New York:

const newYorkTime = new Intl.DateTimeFormat(
  "en-US",
  {
    timeZone: "America/New_York",
    dateStyle: "medium",
    timeStyle: "medium"
  }
).format(date);

console.log(newYorkTime);
Enter fullscreen mode Exit fullscreen mode

The time corresponds to:

November 14, 2023, 5:13:20 PM EST
Enter fullscreen mode Exit fullscreen mode

One instant.

Different local times.

Neither representation changes the timestamp.

Practical rule

Store and transmit unambiguous instants.

Convert them into an appropriate time zone when displaying information to users.

And avoid assuming that every time zone has a fixed UTC offset all year.

6. Daylight saving time makes things more complicated

Consider this local time:

2026-11-01 01:30
America/New_York
Enter fullscreen mode Exit fullscreen mode

On this date, New York transitions from daylight saving time to standard time.

The local clock repeats the hour between 1:00 AM and 2:00 AM.

That means 1:30 AM occurs twice.

The two possible instants are:

2026-11-01T05:30:00Z
2026-11-01T06:30:00Z
Enter fullscreen mode Exit fullscreen mode

They have different UTC offsets:

01:30 EDT (UTC-04:00)
01:30 EST (UTC-05:00)
Enter fullscreen mode Exit fullscreen mode

Simply specifying "1:30 AM in New York" is not enough to identify a unique instant.

This matters when building:

  • Meeting schedulers
  • Automated jobs
  • Booking systems
  • Financial transaction logs
  • Monitoring applications

A robust system must define what happens when a local time is repeated or skipped.

For instance, on the spring daylight-saving transition, certain local times do not exist at all.

Time-zone conversion should preserve the instant, while local-time interpretation must account for ambiguity.

7. Debugging timestamps without writing temporary scripts

Sometimes you're not building date-handling logic.

You simply need to investigate a production log, inspect an API response, or check a timestamp from a database.

Writing a temporary JavaScript script every time can become repetitive.

This is one reason I built the Unix Timestamp Converter as part of ToolZenHub.

You can use it here:

https://toolzenhub.in/tools/unix-timestamp-converter/

It runs directly in your browser.

Let's walk through the earlier example.

Step 1: Enter the timestamp

Paste:

1700000000
Enter fullscreen mode Exit fullscreen mode

Step 2: Confirm the unit

Select Seconds.

The tool also provides an automatic unit-detection option, but explicitly selecting the unit is useful when you already know the API contract.

Step 3: Choose your time zone

Select the time zone in which you want to inspect the timestamp.

For example:

Asia/Kolkata
Enter fullscreen mode Exit fullscreen mode

The converted time corresponds to:

15 November 2023
03:43:20 IST
Enter fullscreen mode Exit fullscreen mode

Step 4: Compare the same instant across zones

The converter supports displaying the same timestamp in multiple time zones.

This can be useful when investigating an event involving users or servers in different countries.

Step 5: Convert a date back into a timestamp

You can also enter a date and time with its time zone and convert it into a Unix timestamp.

This is particularly useful when preparing test data or checking scheduled events.

The tool explains ambiguous or nonexistent daylight-saving times rather than silently choosing an interpretation.

8. Debugging multiple timestamps from logs

Now imagine you receive these timestamps:

1700000000
1700000300
1700000600
1700000900
Enter fullscreen mode Exit fullscreen mode

You want to understand the event sequence.

You could write a script:

const timestamps = [
  1700000000,
  1700000300,
  1700000600,
  1700000900
];

const events = timestamps.map((timestamp) => ({
  timestamp,
  utc: new Date(timestamp * 1000).toISOString()
}));

console.table(events);
Enter fullscreen mode Exit fullscreen mode

This works well for reusable automation.

But sometimes you only need a quick investigation.

ToolZenHub's Unix Timestamp Converter also supports batch conversion.

You can paste multiple timestamp values or compatible log lines.

The tool supports up to 200 values in one batch and uses the selected time zone for the results.

This helps avoid repeatedly converting individual timestamps during a debugging session.

For very large log files, a dedicated script or approved log-analysis system is more appropriate.

9. What about microseconds and nanoseconds?

Not every system uses seconds or milliseconds.

Some databases and telemetry systems represent timestamps with microsecond or nanosecond precision.

For the same example instant:

Unit Timestamp
Seconds 1700000000
Milliseconds 1700000000000
Microseconds 1700000000000000
Nanoseconds 1700000000000000000

A critical problem arises when working with large integers in JavaScript.

JavaScript's standard Number type cannot represent every integer above:

9007199254740991
Enter fullscreen mode Exit fullscreen mode

This is Number.MAX_SAFE_INTEGER.

Modern nanosecond timestamps commonly exceed that limit.

For example:

const timestamp = 1700000000000000000;

console.log(Number.isSafeInteger(timestamp));
Enter fullscreen mode Exit fullscreen mode

Output:

false
Enter fullscreen mode Exit fullscreen mode

This means you should not assume that converting a nanosecond timestamp to Number preserves its exact integer value.

Use BigInt when exact integer arithmetic is required

const nanoseconds = 1700000000000000000n;

const milliseconds =
  nanoseconds / 1000000n;

console.log(milliseconds.toString());
Enter fullscreen mode Exit fullscreen mode

Output:

1700000000000
Enter fullscreen mode Exit fullscreen mode

This example produces exact millisecond precision because the original timestamp is an exact multiple of one million nanoseconds.

For timestamps with sub-millisecond components, integer division drops the remainder unless you preserve it separately.

Also note that native JavaScript Date objects only represent time at millisecond precision.

If your application needs exact microsecond or nanosecond values, preserve those values in an appropriate integer or timestamp representation rather than relying solely on Date.

The ToolZenHub converter supports seconds, milliseconds, microseconds, and nanoseconds, including exact handling of large timestamp values.

10. A safer approach to API timestamp design

A major source of timestamp bugs is unclear API documentation.

Consider this response:

{
  "createdAt": 1700000000
}
Enter fullscreen mode Exit fullscreen mode

The client has to guess the unit.

A clearer contract could use an explicitly named field:

{
  "createdAtUnixSeconds": 1700000000
}
Enter fullscreen mode Exit fullscreen mode

Another option is an unambiguous ISO 8601 timestamp:

{
  "createdAt": "2023-11-14T22:13:20Z"
}
Enter fullscreen mode Exit fullscreen mode

Both approaches can work.

The important part is consistency.

For an API contract, document:

  1. Whether the timestamp represents an instant or a local date and time.
  2. The unit used for numeric timestamps.
  3. The expected precision.
  4. The accepted date range.
  5. How time zones are handled.
  6. Whether missing or invalid timestamps are allowed.

This makes client implementations and automated tests more predictable.

11. A practical timestamp-debugging checklist

When you encounter a suspicious timestamp, check the following:

1. What is the original value?

Avoid converting the value before recording what the API actually returned.

2. What unit does the API use?

Seconds, milliseconds, microseconds, or nanoseconds?

3. Is the timestamp within the expected range?

An application created in 2026 probably shouldn't have an ordinary creation timestamp in 1970.

4. Are you mixing Date.now() with Unix seconds?

Remember that Date.now() uses milliseconds.

5. Are you parsing a date with an explicit UTC offset?

For example:

2026-10-28T10:00:00+05:30
Enter fullscreen mode Exit fullscreen mode

This identifies an instant.

A local date-time string without an offset may require a separate time-zone interpretation.

6. Does daylight saving apply?

Some local times are ambiguous or nonexistent.

7. Are you losing precision?

Be especially careful when processing microsecond and nanosecond timestamps.

8. Are the expected and actual values using the same unit?

Never compare seconds directly with milliseconds.

Final thoughts

Unix timestamps are simple in principle.

But timestamp bugs often arise from hidden assumptions:

  • Assuming a number is measured in milliseconds.
  • Guessing units from digit count.
  • Mixing local time with UTC.
  • Ignoring daylight-saving transitions.
  • Losing precision when handling large integers.
  • Comparing values expressed in different units.

A little clarity around units and time zones prevents many of these problems.

For reusable application logic, write explicit conversion functions and automated tests.

For quick investigation, a timestamp converter can make debugging faster.

If you need to inspect API timestamps, compare time zones, or convert several log entries, you can try the ToolZenHub utility I maintain:

https://toolzenhub.in/tools/unix-timestamp-converter/

The goal isn't to replace understanding with another tool.

It's to remove repetitive conversion work so you can focus on the actual issue.


References

Disclosure: I maintain ToolZenHub, the tool website referenced in this tutorial. This article was created with AI assistance and should be reviewed for technical accuracy before publication.

Top comments (0)