DEV Community

Cover image for Unix Timestamps Explained: Seconds, Milliseconds, Time Zones and Common Mistakes
Julian Halden
Julian Halden

Posted on Originally published at helpers.work

Unix Timestamps Explained: Seconds, Milliseconds, Time Zones and Common Mistakes

Unix timestamps are everywhere. APIs, databases, logs, JavaScript and authentication tokens all use them, and they cause a surprising number of bugs.

Is 1790186400 a date? What about 1790186400000? Does a Unix timestamp contain a time zone? Why does JavaScript sometimes show a date in your local time when the timestamp itself is supposed to be UTC?

This article explains how Unix timestamps actually work, the difference between seconds and milliseconds, how time zones fit into the picture, and the mistakes developers most commonly make.

Table of contents

What is a Unix timestamp?

A Unix timestamp represents the amount of time that has passed since January 1, 1970 at 00:00:00 UTC. This moment is commonly called the Unix epoch.

Unix timestamp timeline from the 1970 Unix epoch to the Year 2038 limit

For example, 0 represents:

1970-01-01 00:00:00 UTC
Enter fullscreen mode Exit fullscreen mode

A timestamp such as 1790186400 represents a specific moment after the Unix epoch:

2026-09-23 18:00:00 UTC
Enter fullscreen mode Exit fullscreen mode

The important part is that the timestamp represents an instant in time, not a formatted date. A value such as 2026-09-23 18:00:00 is a human-readable representation. A Unix timestamp is simply a number.

Seconds vs milliseconds

One of the most common timestamp bugs comes from confusing seconds with milliseconds.

Unix timestamp seconds vs milliseconds comparison with 10-digit and 13-digit examples

Traditional Unix timestamps use seconds since January 1, 1970. Many programming environments, however, use milliseconds. The same instant looks like this in each unit:

Seconds:      1790186400
Milliseconds: 1790186400000
Enter fullscreen mode Exit fullscreen mode

Milliseconds usually have three extra digits. A quick rule of thumb:

10 digits -> usually seconds
13 digits -> usually milliseconds
Enter fullscreen mode Exit fullscreen mode

This works for modern dates, but you should not rely on digit count as formal validation.

JavaScript uses milliseconds

JavaScript's Date API expects milliseconds:

const timestamp = 1790186400000
const date = new Date(timestamp)
console.log(date)
Enter fullscreen mode Exit fullscreen mode

But many APIs return Unix timestamps in seconds:

const timestamp = 1790186400
Enter fullscreen mode Exit fullscreen mode

If you pass that directly to Date, JavaScript interprets it as milliseconds, and the resulting date lands in January 1970:

new Date(1790186400) // 1970-01-21T17:16:26.400Z
Enter fullscreen mode Exit fullscreen mode

Instead, convert seconds to milliseconds:

const timestamp = 1790186400
const date = new Date(timestamp * 1000)
Enter fullscreen mode Exit fullscreen mode

The reverse operation works the same way. To get the current Unix timestamp in seconds:

const timestamp = Math.floor(Date.now() / 1000)
Enter fullscreen mode Exit fullscreen mode

To get milliseconds:

const timestamp = Date.now()
Enter fullscreen mode Exit fullscreen mode

Does a Unix timestamp have a time zone?

No. This is one of the most important things to understand about timestamps.

A Unix timestamp does not contain a time zone. It represents one exact instant in time.

The same Unix timestamp displayed in Calgary, New York, London, and Tokyo time zones

For example, 1790186400 represents 2026-09-23 18:00:00 UTC. The exact same instant can be displayed as:

Location Local time
Calgary 2026-09-23 12:00:00 MDT
New York 2026-09-23 14:00:00 EDT
London 2026-09-23 19:00:00 BST

The timestamp does not change. Only its formatted representation changes.

Think of a Unix timestamp as a position on a global timeline. A time zone only changes how that position is displayed to a person.

UTC vs local time

JavaScript makes this distinction visible:

const date = new Date(1790186400000)
console.log(date.toISOString())
Enter fullscreen mode Exit fullscreen mode

toISOString() returns the date in UTC:

2026-09-23T18:00:00.000Z
Enter fullscreen mode Exit fullscreen mode

The Z means UTC. But toString() uses the computer's local time zone:

console.log(date.toString())
Enter fullscreen mode Exit fullscreen mode

A developer in Calgary might see something similar to:

Wed Sep 23 2026 12:00:00 GMT-0600 (Mountain Daylight Time)
Enter fullscreen mode Exit fullscreen mode

Both values represent the exact same instant.

Unix timestamp shown as different local times in multiple time zones with UTC conversion

This difference is responsible for many apparent "timezone bugs". The timestamp is often correct — what changed is the way the application formatted it.

ISO 8601 and Unix timestamps are different things

Developers sometimes mix up Unix timestamps and ISO 8601 dates.

A Unix timestamp looks like 1790186400. An ISO 8601 date looks like 2026-09-23T18:00:00Z. They can both represent the same instant — the difference is representation.

Unix timestamps are compact and convenient for machines. ISO 8601 strings are easier for humans to read and can explicitly contain a UTC offset. For example, these two strings represent the same moment:

2026-09-23T12:00:00-06:00
2026-09-23T18:00:00Z
Enter fullscreen mode Exit fullscreen mode

Common mistake #1: mixing seconds and milliseconds

Consider an API response:

{
  "created_at": 1790186400
}
Enter fullscreen mode Exit fullscreen mode

You might write:

const createdAt = new Date(response.created_at)
Enter fullscreen mode Exit fullscreen mode

This is wrong if created_at uses Unix seconds.

JavaScript Unix timestamp mistake caused by passing seconds instead of milliseconds to Date

The correct version is:

const createdAt = new Date(response.created_at * 1000)
Enter fullscreen mode Exit fullscreen mode

The reverse mistake happens as well. Suppose an API expects seconds, and you write:

const expiresAt = Date.now()
Enter fullscreen mode Exit fullscreen mode

You send 1790186400000, but the server expects 1790186400. Read as seconds, your expiration date suddenly lands tens of thousands of years in the future.

When working with timestamps, always check the API documentation for the expected unit.

Common mistake #2: assuming the timestamp is in local time

A timestamp does not mean "12:00 Calgary time" or "14:00 New York time". It represents a global instant. The time zone matters only when converting that instant into a calendar date and clock time.

This distinction becomes especially important when users are located in different countries. For example, your database might store 1790186400, and the frontend then displays that timestamp in each user's own time zone. That is usually much safer than storing separately formatted local dates.

Common mistake #3: removing the UTC indicator

Consider this ISO string:

2026-09-23T18:00:00Z
Enter fullscreen mode Exit fullscreen mode

The Z tells the parser that the value is UTC. Compare it with:

2026-09-23T18:00:00
Enter fullscreen mode Exit fullscreen mode

There is no time-zone information here. Depending on the language, library, environment or parsing rules, the second value may be interpreted differently — in JavaScript, for example, a date-time string without an offset is parsed as local time.

When exchanging dates between systems, explicit time-zone information is much safer. Prefer formats such as:

2026-09-23T18:00:00Z
2026-09-23T12:00:00-06:00
Enter fullscreen mode Exit fullscreen mode

instead of an ambiguous local date.

Common mistake #4: manually applying time-zone offsets

Imagine you receive a timestamp from an API and convert it to a JavaScript Date. JavaScript already knows how to display that date in the local time zone. A common mistake is then manually adding or subtracting an offset:

const date = new Date(timestamp * 1000)
date.setHours(date.getHours() - 6)
Enter fullscreen mode Exit fullscreen mode

In many cases this applies the time-zone conversion twice.

Correct and incorrect ways to convert Unix timestamps between UTC and local time in JavaScript

It also breaks when daylight saving time changes.

Instead, keep the underlying timestamp unchanged and apply the desired time zone only when formatting the value:

const date = new Date(timestamp * 1000)

const formatted = new Intl.DateTimeFormat('en-CA', {
  dateStyle: 'medium',
  timeStyle: 'long',
  timeZone: 'America/Edmonton'
}).format(date)

console.log(formatted)
Enter fullscreen mode Exit fullscreen mode

This is much safer than manually adding or subtracting hours.

Common mistake #5: ignoring daylight saving time

Time-zone offsets are not always constant. A location might use one UTC offset during part of the year and another during the rest of the year. Because of that, code such as:

timestamp - 6 * 60 * 60
Enter fullscreen mode Exit fullscreen mode

should not be used as a general way to convert UTC to a local time zone. The correct offset may depend on:

  • location;
  • date;
  • daylight saving rules;
  • historical time-zone changes.

Use an actual IANA time-zone identifier instead:

America/Edmonton
America/New_York
Europe/London
Asia/Tokyo
Enter fullscreen mode Exit fullscreen mode

Modern date/time libraries and Intl.DateTimeFormat apply the appropriate rules for you.

Common mistake #6: storing local time instead of an instant

Imagine a database field contains:

2026-09-23 12:00:00
Enter fullscreen mode Exit fullscreen mode

What does that mean? 12:00 where — Calgary, Toronto, London, UTC? Without additional information, the value is ambiguous.

For events that represent an exact moment, storing either a Unix timestamp or an explicitly UTC-based datetime usually avoids this problem. Values such as 1790186400 or 2026-09-23T18:00:00Z can be converted into the user's preferred local time later.

Not every date should be a Unix timestamp

There is an important exception: some dates do not represent a global instant.

A birthday is a good example. If someone was born on 1990-05-15, you care about the calendar date, not an exact UTC moment. The same applies to:

  • birthdays;
  • anniversaries;
  • all-day calendar events;
  • billing dates;
  • holidays.

Converting every date into a timestamp can introduce unnecessary time-zone problems — a birthday stored as midnight UTC can show up as the previous day for users west of Greenwich. Choose the representation based on what the value actually means.

Negative Unix timestamps

Dates before January 1, 1970 are represented with negative timestamps. For example, -1 represents one second before the Unix epoch:

1969-12-31 23:59:59 UTC
Enter fullscreen mode Exit fullscreen mode

Many modern systems support negative Unix timestamps, but behavior can differ between platforms and older libraries. If your application works with historical dates, test them explicitly. The lower limit of 32-bit time is covered in Unix timestamp -2147483648 and the 1901 limit.

What about the Year 2038 problem?

A signed 32-bit integer can store values only up to 2147483647. Interpreted as seconds since the Unix epoch, that limit is reached on January 19, 2038 at 03:14:07 UTC. Older systems that store timestamps as signed 32-bit integers overflow after that point.

Modern 64-bit systems do not have this practical limitation — a signed 64-bit timestamp in seconds covers a range far beyond anything normal applications need. However, the 2038 problem can still matter when working with:

  • legacy operating systems;
  • embedded systems;
  • old databases;
  • binary protocols;
  • older C applications.

Notable Unix timestamps

A few values come up again and again in logs, tests and bug reports:

Timestamp Date (UTC) Why it matters
-2147483648 1901-12-13 20:45:52 Signed 32-bit minimum and 2038 overflow target
0 1970-01-01 00:00:00 The Unix epoch, and why buggy dates show 1970
1000000000 2001-09-09 01:46:40 The billennium: timestamps become 10 digits
1234567890 2009-02-13 23:31:30 The famous sequential timestamp
1700000000 2023-11-14 22:13:20 Round-number milestones from 1.5 to 2.1 billion
2000000000 2033-05-18 03:33:20 The last round billion before 2038
2147483647 2038-01-19 03:14:07 The Year 2038 problem
4294967295 2106-02-07 06:28:15 The unsigned 32-bit limit and Year 2106

Unix timestamp precision

Unix-style timestamps can use different levels of precision. The same instant in each unit:

Seconds:      1790186400
Milliseconds: 1790186400000
Microseconds: 1790186400000000
Nanoseconds:  1790186400000000000
Enter fullscreen mode Exit fullscreen mode

This is why receiving "a timestamp" from an API is not enough information. Before parsing it, check whether the value represents seconds, milliseconds, microseconds or nanoseconds.

Converting Unix timestamps in JavaScript

Timestamp in seconds to Date

const timestamp = 1790186400
const date = new Date(timestamp * 1000)
Enter fullscreen mode Exit fullscreen mode

Timestamp in milliseconds to Date

const timestamp = 1790186400000
const date = new Date(timestamp)
Enter fullscreen mode Exit fullscreen mode

Current Unix timestamp in seconds

const timestamp = Math.floor(Date.now() / 1000)
Enter fullscreen mode Exit fullscreen mode

Current Unix timestamp in milliseconds

const timestamp = Date.now()
Enter fullscreen mode Exit fullscreen mode

Date to Unix seconds

const date = new Date()
const timestamp = Math.floor(date.getTime() / 1000)
Enter fullscreen mode Exit fullscreen mode

Convert to UTC ISO format

const date = new Date(1790186400000)
console.log(date.toISOString()) // 2026-09-23T18:00:00.000Z
Enter fullscreen mode Exit fullscreen mode

Debugging an unknown timestamp

Suppose you find 1790186400 in a log and do not know what it means. A useful debugging process is:

  1. Determine whether it is probably seconds, milliseconds, microseconds or nanoseconds.
  2. Convert it into UTC.
  3. Compare the result with the expected date.
  4. Check whether the system displays the result in local time.
  5. Look for any manually applied time-zone offsets.
  6. Verify how the originating API or database documents the field.

For quick debugging, use the Unix Timestamp Converter to convert between Unix timestamps and human-readable dates in any time zone.

A good rule for APIs

When designing APIs, consistency matters more than which representation you choose. You might decide that your API always returns ISO 8601 strings:

{
  "created_at": "2026-09-23T18:00:00Z"
}
Enter fullscreen mode Exit fullscreen mode

Or you might use Unix timestamps:

{
  "created_at": 1790186400
}
Enter fullscreen mode Exit fullscreen mode

Both approaches work. Problems begin when one endpoint returns seconds (1790186400), another returns milliseconds (1790186400000), and a third returns a local datetime (2026-09-23 12:00:00) without documenting the difference.

Pick a convention and use it consistently across your API. The same applies to tokens: the exp, iat and nbf claims in a JWT are always Unix seconds, which you can check with the JWT Decoder.

Practical rules to remember

  • A Unix timestamp represents an instant in time.
  • Unix timestamps do not contain a time zone.
  • Traditional Unix timestamps use seconds.
  • JavaScript timestamps normally use milliseconds.
  • Do not apply fixed UTC offsets manually when you can use a real time-zone database.
  • Store exact moments in a time-zone-independent format.
  • Convert to the user's local time only when displaying the value.
  • Use explicit time-zone information when exchanging formatted datetime strings.
  • Always verify the timestamp unit used by an API.
  • Do not use timestamps for calendar-only dates unless you actually need an instant in time.

Conclusion

Unix timestamps are simple numbers, but the systems around them — time zones, daylight saving time, date formatting and different precision levels — make them surprisingly easy to misuse.

The most useful mental model is this:

A Unix timestamp identifies a point on the global timeline. A time zone only determines how that point is displayed.

Once you separate those two ideas, most timestamp problems become much easier to understand and debug.


Originally published at helpers.work.

Top comments (0)