Quick reference: six representations of one instant

These values all point to the same instant:

Format Example What the value tells you
Unix seconds 1700000000 unit must be known from the contract
Unix milliseconds 1700000000000 unit must be known from the contract
Unix microseconds 1700000000000000 unit must be known from the contract
Unix nanoseconds 1700000000000000000 unit and integer width both matter
RFC 3339 / ISO-style string 2023-11-14T22:13:20Z date, time, and UTC offset are visible
HTTP date Tue, 14 Nov 2023 22:13:20 GMT fixed HTTP header representation

The numeric forms look similar because they share the Unix epoch, differing only by scale. The strings carry more syntax, but even a string with an offset does not necessarily identify an IANA timezone such as America/New_York.

“Timestamp” is the broad term

In developer conversations, Unix time, epoch time, POSIX time, and Unix timestamp are often used interchangeably. Usually they mean a numeric count from 1970-01-01 00:00:00 UTC.

The word timestamp is broader. Depending on the system, it might refer to:

  • Unix seconds or milliseconds
  • a database timestamp value
  • an RFC 3339 string
  • an HTTP Date header
  • Windows FILETIME or .NET ticks
  • an Apple, NTP, GPS, Excel, or WebKit counter with a different starting epoch

That distinction matters in migrations. A column named timestamp tells you almost nothing about its unit, epoch, timezone treatment, or precision. A field named created_at_ms provides an actual contract.

Digit count is a clue, not proof

For positive timestamps near the present day, the digit count works well for triage:

Digits Likely Unix unit Divide by this to get seconds
10 seconds 1
13 milliseconds 1,000
16 microseconds 1,000,000
19 nanoseconds 1,000,000,000

But the rule has boundaries:

  • A valid Unix-seconds value before September 2001 can have nine digits.
  • Unix seconds become eleven digits in November 2286.
  • A negative value needs its sign removed before digit counting is useful.
  • Small fixture values such as 0 and 1 are ambiguous without a unit.
  • A 16-digit value might be WebKit microseconds since 1601, not Unix microseconds.
  • An integer might count from 1900, 2001, or another platform epoch.

Use this order when the input is unfamiliar:

  1. Read the API, schema, or field documentation.
  2. Identify the starting epoch.
  3. Identify the unit and integer type.
  4. Decode the value as UTC.
  5. Check whether the result is plausible for the dataset.

The site converter automatically determines whether Unix seconds or milliseconds are being used based on the project's threshold for seconds versus milliseconds. First, scale the microsecond and nanosecond values; the tool does not infer those higher-precision units.

Diagnose seconds and milliseconds by the failure pattern

Unit mistakes have two recognizable symptoms. A recent timestamp that renders in January 1970 was probably supplied in seconds to a millisecond API. A value that produces a far-future year or a range error was probably supplied in milliseconds to a seconds-based API.

new Date(1700000000).toISOString();
// "1970-01-20T16:13:20.000Z" — seconds treated as milliseconds

new Date(1700000000 * 1000).toISOString();
// "2023-11-14T22:13:20.000Z"
from datetime import datetime, timezone

datetime.fromtimestamp(1700000000000 / 1000, tz=timezone.utc)
# 2023-11-14 22:13:20+00:00

JWT fields such as exp, iat, and nbf are another common boundary: they use NumericDate seconds, while JavaScript's Date.now() returns milliseconds. Convert once at the boundary and keep one unit inside each subsystem.

const expiresAtSeconds = Math.floor(Date.now() / 1000) + 60 * 60;

For database and API fields, names such as created_at_seconds, created_at_ms, and event_time_ns are safer than timestamp or time. If TypeScript is available, branded unit types can add compile-time friction, but external values still need runtime validation.

Unix seconds: the classic POSIX-shaped value

Unix seconds are whole seconds since the epoch. This is apparent in command-line tools, operating-system APIs, and server-side languages.

For the example used throughout this article:

1700000000 = 2023-11-14T22:13:20Z

Typical producers include:

Platform Current Unix seconds
Python int(time.time())
PHP time()
Go time.Now().Unix()
Ruby Time.now.to_i
C time(NULL)
PostgreSQL FLOOR(EXTRACT(EPOCH FROM clock_timestamp()))::bigint

The storage width is separate from the unit. A signed 32-bit seconds field reaches its maximum at 2147483647, which is 2038-01-19T03:14:07Z. A signed 64-bit integer removes that storage bottleneck, although the date library, database, or serialization format can still impose a much narrower usable range.

Unix milliseconds: JavaScript's numeric date unit

JavaScript's Date API uses milliseconds since the Unix epoch. MDN defines Date.now() as returning the elapsed milliseconds from the start of 1970-01-01 UTC.

const unixSeconds = 1700000000;
const unixMilliseconds = 1700000000000;

new Date(unixSeconds * 1000).toISOString();
// "2023-11-14T22:13:20.000Z"

new Date(unixMilliseconds).toISOString();
// "2023-11-14T22:13:20.000Z"

The unit mismatch produces the familiar 1970 bug:

new Date(1700000000).toISOString();
// "1970-01-20T16:13:20.000Z"

Java and .NET also expose explicit millisecond APIs, including System.currentTimeMillis(), Instant.ofEpochMilli(), and DateTimeOffset.FromUnixTimeMilliseconds().

MDN: Date.now() · JavaScript Date and Unix timestamps

Microseconds and nanoseconds: precision needs integer discipline

Event pipelines, database work, tracing and systems code all tend to involve microsecond and nanosecond timestamps. Those extra digits cause two separate problems . The API needs to know what unit it is . The integer type needs to be able to hold the value exactly .

Go provides unit-specific constructors and accessors:

package main

import "time"

func main() {
    microseconds := int64(1700000000000000)
    nanoseconds := int64(1700000000000000000)

    time.UnixMicro(microseconds).UTC()
    time.Unix(0, nanoseconds).UTC()
}

Rust obtains an epoch-relative Duration before asking for whole microseconds or nanoseconds:

use std::time::{SystemTime, UNIX_EPOCH};

fn main() {
    let elapsed = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("system clock is before the Unix epoch");

    let unix_micros = elapsed.as_micros();
    let unix_nanos = elapsed.as_nanos();

    println!("{unix_micros} {unix_nanos}");
}

PostgreSQL exposes epoch seconds with a decimal part. Explicitly convert to an integer microsecond contract when that’s what downstream code expects:

SELECT ROUND(EXTRACT(EPOCH FROM clock_timestamp()) * 1000000)::bigint;

JavaScript Number cannot exactly represent a present-day Unix-nanosecond integer. Keep nanoseconds as BigInt or a decimal string:

const epochNanoseconds = 1700000000000000000n;
const epochMilliseconds = epochNanoseconds / 1_000_000n;
const discardedNanoseconds = epochNanoseconds % 1_000_000n;

The remainder makes precision loss visible. Converting through milliseconds and then back to nanoseconds cannot recover the discarded portion.

Go package time · Rust Duration

ISO 8601 and RFC 3339: readable strings with an offset

ISO 8601 is a broad date and time standard. RFC 3339 defines the narrower Internet profile most developers recognize from APIs and logs.

2023-11-14T22:13:20Z
2023-11-14T17:13:20-05:00
2023-11-14T22:13:20.123456Z

These strings convey more than a bare integer:

  • the calendar date and clock time are visible
  • T separates the date from the time
  • Z means an offset of zero from UTC
  • -05:00 records a numeric offset
  • the fractional part carries subsecond precision

An offset is not a timezone rule set. -05:00 does not tell you whether the intended zone is New York, Toronto, Lima, or a fixed-offset system. Store an IANA timezone separately when future daylight-saving or calendar calculations depend on the user's zone.

JavaScript's toISOString() always returns a UTC string with Z. Python's datetime.isoformat() preserves the datetime's offset, and Go commonly uses time.RFC3339 or time.RFC3339Nano.

Email uses the date-time syntax defined by RFC 5322. A typical UTC email header uses a numeric offset:

Date: Tue, 14 Nov 2023 22:13:20 +0000

HTTP uses HTTP-date. RFC 9110 requires senders to generate the preferred IMF-fixdate form:

Tue, 14 Nov 2023 22:13:20 GMT

That fixed HTTP representation is a single-zone subset derived from the Internet Message Format. Calling both strings “RFC 2822” hides constraints that matter to parsers, especially HTTP's required GMT spelling and fixed layout.

JavaScript's toUTCString() produces the HTTP-style form:

new Date(1700000000 * 1000).toUTCString();
// "Tue, 14 Nov 2023 22:13:20 GMT"

Resolution, precision, and accuracy are different

A nanosecond field has nanosecond resolution : it can hold values that differ by one nanosecond. That does not prove the source clock is accurate to 1 nanosecond. A fractional digit system can measure time with a much more coarse clock, but fill six or nine fractional digits.

Representation Nominal resolution Typical concern
Unix seconds 1 second insufficient for event ordering within a second
Unix milliseconds 1 millisecond common API and JavaScript boundary
Unix microseconds 1 microsecond may exceed some library precision
Unix nanoseconds 1 nanosecond exceeds JavaScript safe-integer range
FILETIME / .NET ticks 100 nanoseconds uses a non-Unix epoch
NTP timestamp fraction 2^-32 second wire representation is not decimal nanoseconds

Select the coarsest unit that meets the real need. More digits cost in storage and interoperability, and do not make an imprecise clock more accurate.

Convert units without hiding precision loss

The scale factors are simple:

From To Operation
seconds milliseconds multiply by 1,000
milliseconds seconds divide by 1,000
microseconds milliseconds divide by 1,000
nanoseconds milliseconds divide by 1,000,000
nanoseconds seconds divide by 1,000,000,000

The rounding policy is complicated. If division has a remainder, decide whether the application should truncate, floor, round to nearest or reject the conversion. This is particularly relevant to negative pre-epoch values as the rules of integer division are different between languages.

Keep the original integer and unit at a system boundary until it has been validated. Auditing a migration is much easier when you can compare the parsed instant to the exact value that arrived.

Some large counters use a different epoch entirely

A long integer is not automatically a high resolution Unix timestamp. These popular formats modify either the epoch, the unit, or both:

Format Unit and epoch
Windows FILETIME 100-nanosecond intervals since 1601-01-01 UTC
.NET DateTime.Ticks 100-nanosecond intervals since 0001-01-01
WebKit / Chrome timestamp microseconds since 1601-01-01 UTC
Mac Absolute Time / Core Data seconds since 2001-01-01 UTC
Excel serial date / OADate days since 1899-12-30 in the common conversion model
NTP timestamp seconds and a binary fraction from the NTP epoch in 1900
GPS time seconds from 1980-01-06, with different leap-second treatment from UTC
Julian Day days from a historical astronomical epoch beginning at noon

We can’t safely distinguish these formats by digit count. Use the source system, field name, and documented epoch.

Choose a format that makes the contract obvious

There is no one format that works best at every boundary:

  • For public APIs, use RFC 3339 strings or a clearly named integer such as createdAtMs.
  • For logs, a UTC RFC 3339 string is readable and sorts naturally when fields are normalized.
  • For event streams, document the integer unit and width in the schema.
  • For databases, prefer the native instant-aware timestamp type unless raw integer interoperability is required.
  • For nanosecond or non-Unix counters in JavaScript, use BigInt or strings rather than Number.
  • For user schedules, preserve the IANA timezone as well as the resulting instant.

The best timestamp format is the one the next system can decode without guessing.

Timestamp format converters

Use these tools when the source does not use the Unix epoch:

Official references

Frequent questions:

Q: Is a timestamp the same as Unix time?
A: Not always. Timestamp is a broad term for a value that identifies a date, time, or instant. A Unix timestamp specifically counts from 1970-01-01 00:00:00 UTC, usually in seconds or milliseconds.
Q: What is a POSIX timestamp?
A: A POSIX timestamp represents seconds since the Unix epoch under the POSIX definition of Seconds Since the Epoch. In ordinary application code, it is the same numeric form people usually mean by Unix seconds.
Q: How many digits is a Unix timestamp?
A: Near the present day, Unix seconds usually have 10 digits, milliseconds 13, microseconds 16, and nanoseconds 19. This is only a heuristic: older dates, future dates, negative values, and non-Unix epochs break the pattern.
Q: What timestamp format does JavaScript use?
A: JavaScript Date uses milliseconds since the Unix epoch. Date.now() returns a millisecond number, and new Date(value) interprets a numeric value as milliseconds. Multiply Unix seconds by 1000 before passing them to Date.
Q: What is the difference between Unix time and ISO 8601?
A: Unix time is a numeric count from the Unix epoch and needs a separately documented unit. ISO 8601 is a family of date and time string formats. The common Internet profile, RFC 3339, includes a date, time, and UTC offset such as 2023-11-14T22:13:20Z.
Q: What format is used for email and HTTP dates?
A: Email dates use the RFC 5322 date-time syntax and normally include a numeric UTC offset. HTTP uses IMF-fixdate, a fixed UTC form such as Tue, 14 Nov 2023 22:13:20 GMT. The formats are related but not interchangeable labels.
Q: How do I detect whether a timestamp is seconds or milliseconds?
A: Read the field or API contract first. For a present-day value, 10 digits usually indicates seconds and 13 digits usually indicates milliseconds. Then decode it in UTC and confirm that the date is plausible.