Before using any snippet: confirm whether the source is seconds, milliseconds, microseconds, or nanoseconds; keep large input as text or an arbitrary-size integer; format exchanged dates in UTC with an explicit Z or offset; and test negative, boundary, and sub-second values. Conversion code cannot infer metadata that the producer omitted.

Define the timestamp contract before the function

A robust timestamp field needs more than a name and numeric type. Specify its epoch, unit, accepted range, precision, transport representation, and timezone rules for date strings. State how fractions are rounded when converting to a coarser unit. A field named createdAt does not answer any of those questions; created_at_ms plus a schema description is much harder to misuse.

Decide whether the timestamp represents an absolute instant, a local appointment, or a duration. Unix conversion is appropriate for an instant. A recurring event such as “09:00 every business day in America/New_York” needs a local calendar rule and IANA timezone, because a single numeric offset cannot describe future daylight saving changes. A timeout of 500 milliseconds is a duration and should not be converted relative to 1970 at all.

Contract decisionGood explicit exampleRisk when omitted
UnitInteger milliseconds since Unix epoch1970 or far-future dates
PrecisionMicroseconds preserved; reject extra digitsSilent rounding and false ordering
TransportDecimal string for nanosecondsJSON floating-point loss
Date textRFC 3339 UTC ending in ZHost-dependent local interpretation
Range2020-01-01 through ingestion time + 5 minPlausible but corrupt records

Use the LiveParse Unix Timestamp Converter to inspect fixtures in seconds, milliseconds, microseconds, and nanoseconds before committing a rule. For a deeper identification workflow, read Unix timestamp units explained.

JavaScript and TypeScript

JavaScript Date accepts milliseconds since the epoch and stores a time value with millisecond resolution. Seconds must be multiplied by 1,000. Present-day millisecond values are safe as integers in Number, but present-day nanosecond values are not. Parse long values with BigInt before any numeric coercion.

Seconds or milliseconds to a UTC ISO string
function secondsToIso(epochSeconds: number): string {
  if (!Number.isFinite(epochSeconds)) throw new TypeError("finite seconds required");
  return new Date(epochSeconds * 1_000).toISOString();
}

function millisecondsToIso(epochMs: number): string {
  if (!Number.isSafeInteger(epochMs)) throw new RangeError("safe integer ms required");
  return new Date(epochMs).toISOString();
}

secondsToIso(1754208000);       // 2025-08-03T08:00:00.000Z
millisecondsToIso(1754208000000);

The seconds function permits a fraction because 1754208000.125 seconds can become an exact integer millisecond in this range. Binary fractions do not always map exactly, so use integer input plus a remainder when every microsecond or nanosecond matters. Validate the resulting Date; calling toISOString() on an invalid date throws a RangeError.

The following pattern keeps a nanosecond token exact, normalizes negative values using floor division, and creates a millisecond projection only after preserving the remainder.

Exact nanoseconds with BigInt
const NS_PER_SECOND = 1_000_000_000n;
const NS_PER_MILLISECOND = 1_000_000n;

function splitNanoseconds(totalNs: bigint) {
  let seconds = totalNs / NS_PER_SECOND;
  let nanoseconds = totalNs % NS_PER_SECOND;
  if (nanoseconds < 0n) {
    seconds -= 1n;
    nanoseconds += NS_PER_SECOND;
  }
  return { seconds, nanoseconds };
}

function nanosecondsToDateParts(raw: string) {
  const totalNs = BigInt(raw);
  const { seconds, nanoseconds } = splitNanoseconds(totalNs);
  const epochMs = seconds * 1_000n + nanoseconds / NS_PER_MILLISECOND;
  const date = new Date(Number(epochMs));
  if (!Number.isFinite(date.getTime())) throw new RangeError("outside Date range");
  return {
    date,
    seconds,
    nanoseconds,
    exactFraction: nanoseconds.toString().padStart(9, "0")
  };
}

Do not build an exact nine-digit UTC string by appending digits to Date.toISOString() without understanding carries and negative values. Format the normalized seconds and fraction together, or use a reviewed temporal library/type that supports nanoseconds. LiveParse directly serializes its exact UTC and ISO outputs from BigInt seconds plus the normalized remainder; its local, RFC-style, and native Date projections are millisecond-based.

To convert a JavaScript Date back to Unix time, date.getTime() returns integer milliseconds. Divide for seconds only with an explicit rounding policy. Math.floor(ms / 1000) gives the containing Unix second and behaves consistently for negative dates; Math.trunc gives a different result before 1970.

Date to integer epoch units
function dateToEpoch(date: Date) {
  const ms = date.getTime();
  if (!Number.isFinite(ms)) throw new RangeError("invalid date");
  return {
    secondsFloor: Math.floor(ms / 1_000),
    milliseconds: ms,
    microseconds: BigInt(ms) * 1_000n,
    nanoseconds: BigInt(ms) * 1_000_000n
  };
}

Multiplying a millisecond Date into microseconds or nanoseconds adds zeros; it does not create measurement precision. Preserve a separate fraction from the source if one exists.

Python

Python’s datetime API accepts POSIX seconds in fromtimestamp(). Always pass an explicit timezone for an aware result. Omitting it returns local time and makes output depend on the machine configuration. datetime.timestamp() returns floating-point seconds, which is convenient but not the right final representation for exact nanosecond identifiers.

Aware UTC conversion in Python
from datetime import datetime, timezone

def seconds_to_utc(epoch_seconds: int) -> datetime:
    return datetime.fromtimestamp(epoch_seconds, tz=timezone.utc)

def milliseconds_to_utc(epoch_ms: int) -> datetime:
    seconds, milliseconds = divmod(epoch_ms, 1_000)
    base = datetime.fromtimestamp(seconds, tz=timezone.utc)
    return base.replace(microsecond=milliseconds * 1_000)

print(seconds_to_utc(1754208000).isoformat())
# 2025-08-03T08:00:00+00:00

Python datetime stores microseconds, not nanoseconds. Split nanosecond input with integer divmod(), keep the nanosecond remainder authoritative, and create a datetime projection with the first six fractional digits.

Preserve Python nanoseconds separately
def split_epoch_ns(epoch_ns: int):
    seconds, nanoseconds = divmod(epoch_ns, 1_000_000_000)
    instant_us = datetime.fromtimestamp(seconds, tz=timezone.utc).replace(
        microsecond=nanoseconds // 1_000
    )
    return instant_us, nanoseconds

instant, exact_ns = split_epoch_ns(1754208000123456789)
print(instant.isoformat())  # 2025-08-03T08:00:00.123456+00:00
print(f"{exact_ns:09d}")    # 123456789

Python integers have arbitrary precision, so the input arithmetic remains exact. Calendar conversion still has platform and datetime range limits. Catch OverflowError, OSError, and ValueError at untrusted boundaries, and apply an application-specific acceptable range before conversion.

Java

Java’s java.time.Instant models a point on the timeline with a seconds component and a nanosecond adjustment. Its factory names make units explicit: ofEpochSecond and ofEpochMilli. Use OffsetDateTime or ZonedDateTime only when a human-facing offset or regional timezone is needed.

Seconds, milliseconds, and nanoseconds in Java
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;

Instant fromSeconds = Instant.ofEpochSecond(1_754_208_000L);
Instant fromMillis = Instant.ofEpochMilli(1_754_208_000_000L);

long epochNs = 1_754_208_000_123_456_789L;
long seconds = Math.floorDiv(epochNs, 1_000_000_000L);
long nanos = Math.floorMod(epochNs, 1_000_000_000L);
Instant exact = Instant.ofEpochSecond(seconds, nanos);

System.out.println(exact); // 2025-08-03T08:00:00.123456789Z
System.out.println(ZonedDateTime.ofInstant(exact, ZoneId.of("Asia/Seoul")));

Math.floorDiv and Math.floorMod keep negative values normalized correctly. A signed long nanosecond counter covers only a few centuries around 1970; if the contract needs a wider range, accept a decimal string and use BigInteger before splitting into the range supported by Instant. Catch DateTimeException for unsupported instants.

Go

Go’s time.Unix(sec, nsec) constructor accepts whole seconds and a nanosecond adjustment, and normalizes an adjustment outside the usual range. time.UnixMilli, time.UnixMicro, and the corresponding instance methods make common unit conversions readable.

Exact epoch conversion in Go
package main

import (
    "fmt"
    "time"
)

func main() {
    epochNs := int64(1754208000123456789)
    seconds := epochNs / int64(time.Second)
    nanos := epochNs % int64(time.Second)
    instant := time.Unix(seconds, nanos).UTC()

    fmt.Println(instant.Format(time.RFC3339Nano))
    // 2025-08-03T08:00:00.123456789Z

    fromMs := time.UnixMilli(1754208000000).UTC()
    fmt.Println(fromMs.Unix(), fromMs.UnixMilli(), fromMs.UnixNano())
}

Go’s UnixNano() returns int64, so its usable range is much narrower than time.Time itself. Do not call it for arbitrary far-future or historical values without checking the documented range. Keep seconds plus nanoseconds when a broader time.Time range is needed.

PostgreSQL, MySQL, and SQLite

SQL conversion behavior depends on both the database and the session timezone. Decide whether the target type represents an instant or a timezone-free calendar value. Store absolute instants in the product’s timezone-aware type where available, and set an explicit display zone in queries and clients.

PostgreSQL

PostgreSQL to_timestamp(double precision) interprets its number as Unix seconds and returns timestamp with time zone. Division by 1000.0 is convenient for millisecond data, but the floating-point path is not an exact nanosecond transport. PostgreSQL timestamps have microsecond resolution, so preserve an original nanosecond identifier separately if the final three digits matter.

PostgreSQL epoch conversion
-- Epoch seconds to an instant, displayed as UTC by the session
SET TIME ZONE 'UTC';
SELECT to_timestamp(1754208000);

-- Integer milliseconds using interval arithmetic
SELECT TIMESTAMPTZ '1970-01-01 00:00:00+00'
       + 1754208000123 * INTERVAL '1 millisecond';

-- Timestamp to whole epoch milliseconds (define rounding deliberately)
SELECT floor(extract(epoch FROM event_time) * 1000)::bigint
FROM events;

MySQL

MySQL FROM_UNIXTIME() accepts seconds and returns a value in the current session timezone. Set or verify that timezone instead of assuming UTC. The supported range and fractional precision depend on the server’s temporal type and version configuration.

MySQL seconds and milliseconds
SET time_zone = '+00:00';

SELECT FROM_UNIXTIME(1754208000);
SELECT FROM_UNIXTIME(1754208000123 / 1000.0);

-- UTC date to Unix seconds
SELECT UNIX_TIMESTAMP('2025-08-03 08:00:00');

SQLite

SQLite’s unixepoch modifier tells its date functions to interpret a number as Unix seconds. UTC is the internal reference for these functions. Converting integer milliseconds requires scaling before the modifier; verify fractional behavior and precision for the SQLite version embedded in your application.

SQLite epoch conversion
SELECT datetime(1754208000, 'unixepoch');
SELECT datetime(1754208000123 / 1000.0, 'unixepoch');
SELECT unixepoch('2025-08-03 08:00:00');

Do not paste a database recipe without checking type and zone. A session setting can change displayed calendar fields, an integer division can discard fractions, and a driver can map an exact 64-bit value into an inexact language number. Test the query through the same driver and serialization path used in production.

JSON and API boundaries

JSON has no native date or integer-width type. A timestamp can be a JSON number or string, and the consumer decides how to represent it. Milliseconds near the present fit safely in a JavaScript number; nanoseconds do not. A dependable cross-language nanosecond contract often uses a decimal string:

Explicit JSON timestamp contract
{
  "occurred_at": "2025-08-03T08:00:00.123456789Z",
  "occurred_at_ns": "1754208000123456789",
  "timestamp_unit": "nanoseconds"
}

The text date is readable and the decimal epoch string supports exact sorting and calculation after parsing with an arbitrary-size integer. Keeping both can be useful, but designate one as authoritative and validate that they agree. Otherwise conflicting duplicates create a new integrity problem.

When changing an existing API, treat number-to-string, unit, and precision changes as contract changes. Introduce a versioned field, make readers dual-compatible during a controlled period, compare old and new output on real ranges, then retire the old field. The JSON Compare tool can locate representation changes, while the API response comparison guide explains how to handle intentionally volatile timestamp values.

Parse date strings without host-dependent surprises

Use offset-bearing input for machine exchange. 2025-08-03T08:00:00Z is UTC; 2025-08-03T17:00:00+09:00 names the same instant. A string like 2025-08-03 08:00:00 does not state a timezone and may be interpreted as local time, rejected, or parsed differently across environments.

Validate syntax and meaning before conversion. Calendar validation should reject impossible dates rather than normalizing them silently. If a local time and IANA zone are the source, define behavior for daylight saving overlaps and gaps. During a fall-back overlap, the same wall-clock fields can name two instants; during a spring-forward gap, some local times name none.

Converting to an epoch intentionally discards the original display zone. If the zone conveys user intent—such as a meeting meant to remain at 09:00 local after future rule changes—store the zone identifier and local schedule separately. An epoch is appropriate for an event that has already been resolved to one instant.

Test conversions at boundaries, not only at “now”

A single current timestamp misses most implementation defects. Build table-driven tests that run through the actual API, serializer, database driver, and display layer. Assert exact values, not screenshots or locale-dependent strings.

  • The epoch: zero must become 1970-01-01T00:00:00Z.
  • Before the epoch: include -1 in every unit and a negative value with a fraction.
  • Unit boundaries: one tick before, at, and after a whole second tests quotient and remainder handling.
  • Leap calendar rules: include a valid February 29 and an invalid non-leap-year February 29 for date parsing.
  • Timezone transitions: test both sides of a real gap and overlap for every supported regional scheduling zone.
  • Safe-integer limits: send adjacent long nanosecond values and verify they remain distinct through JSON and database layers.
  • Year 2038: test 2147483647 and 2147483648 seconds wherever 32-bit code or storage could remain.
  • Maximum product range: reject dates outside business bounds even when a runtime can represent them.

Round-trip tests are useful but insufficient. If the same wrong unit is used in both directions, a value can round-trip while still naming the wrong instant. Include independently calculated fixtures and compare with a trusted external representation.

Production review checklist

  • The field name or schema explicitly states seconds, milliseconds, microseconds, or nanoseconds.
  • Long integers remain strings or arbitrary-size integers until exact splitting is complete.
  • Conversion to a coarser unit has a documented floor, truncation, or rounding rule.
  • UTC output includes Z or an explicit numeric offset.
  • Local scheduling data retains its IANA timezone and overlap/gap policy.
  • The accepted range reflects the product domain and storage/runtime limits.
  • Database session timezone and driver mappings are controlled in tests.
  • Sub-millisecond source precision is not claimed after conversion through a millisecond-only type.
  • Negative, 2038-boundary, safe-integer, and maximum-range fixtures pass end to end.
  • Raw values remain available during migrations and failed rows are quarantined rather than guessed.

Need a known conversion while reviewing code? Use the exact browser-based converter to generate UTC, local, and epoch outputs for seconds, milliseconds, microseconds, or nanoseconds.

Convert a Unix timestamp

Frequently asked questions

Why does JavaScript use milliseconds instead of seconds?

The ECMAScript Date time value is defined in milliseconds. Unix and many server interfaces traditionally use seconds, so a boundary conversion is required. Name the unit in variables—epochSeconds and epochMs—instead of relying on memory.

What is the safest JSON format for nanosecond timestamps?

A decimal string is widely interoperable because parsers cannot round it before application code sees it. Convert the string to an arbitrary-size integer in the consumer and validate its grammar and range. An exact nine-digit UTC string is another interoperable choice when calendar formatting is primary.

Should I use UTC everywhere?

Use UTC instants for storage, logs, ordering, and exchange. Retain regional timezone and local calendar intent for future schedules and user-facing rules. “UTC everywhere” should not mean discarding the information needed to reproduce a civil-time schedule.

Can I convert nanoseconds with a JavaScript Date?

You can create a millisecond projection after splitting an exact BigInt value, but Date cannot retain the final six fractional digits. Keep those digits separately or use a nanosecond-aware type and serializer.

Why does SQL show a different hour than my application?

The database session and application may format the same instant in different timezones. Inspect the stored type, session timezone, driver mapping, and output offset before changing the epoch arithmetic.

Official language and database references