The conversion is simple once you settle two questions

Unix timestamp 1700000000 converts to:

2023-11-14T22:13:20Z
Tuesday, November 14, 2023 at 22:13:20 UTC

In JavaScript, the conversion is one line:

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

The code is the easy part. Before you trust the result, you may answer two questions:

  1. Is the input in seconds, milliseconds, microseconds, or nanoseconds?
  2. Should the result be displayed in UTC or in a specific local timezone?

If you get the unit wrong, then a 2023 event will land in 1970. Get the timezone wrong and the instant is still correct, but the visible hour or even the calendar date is not what the user expects.

First prove the unit

The digit number is a first check for timestamps close to today. This is a heuristic, not a data contract.

Example Likely unit Common source What a seconds-based API needs
1700000000 seconds Unix/POSIX APIs, PHP, Python use as-is
1700000000000 milliseconds JavaScript, Java, application databases divide by 1,000
1700000000000000 microseconds data warehouses, event streams divide by 1,000,000
1700000000000000000 nanoseconds Go, tracing, telemetry divide by 1,000,000,000

The classic unit bug is easy to reproduce:

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"

Do not turn the digit rule into permanent business logic. A 9-digit value can be a valid pre-2001 Unix timestamp, and Unix seconds become 11 digits in 2286. More importantly, a small test value such as 0 or 1 tells you nothing about its intended unit.

At system boundaries, prefer names that carry the contract: createdAtSeconds, created_at_ms, or eventTimeMicros. If the value came from an API, webhook, database column, JWT, or CSV export, its documentation should outrank any guess based on length.

The converter on this site will automatically recognize milliseconds from regular Unix seconds. If you input microseconds or nanoseconds scale it to one of those units before pasting into the tool.

Then separate the instant from its display

Unix time represents elapsed time from the Unix epoch at 1970-01-01 00:00:00 UTC. In everyday developer use, it identifies an instant. It does not contain a timezone such as New York, Tokyo, or the server's local zone.

The Open Group's POSIX definition is more exact than the shorthand “seconds since 1970,” especially around leap seconds. For ordinary application conversion, the useful mental model remains: decode the number into one instant, then render that instant in a chosen timezone.

Here is the same value in four zones:

Timezone Local date and time for 1700000000
UTC 2023-11-14 22:13:20
America/New_York 2023-11-14 17:13:20 (EST)
Asia/Tokyo 2023-11-15 07:13:20 (JST)
Australia/Sydney 2023-11-15 09:13:20 (AEDT)

It is the same event, but New York and Tokyo have different dates on their calendars. The timestamp did not move, just the wall clock representation changed.

Use this as the default policy:

  • Keep logs, API payloads, database comparisons, and audit data in UTC.
  • Format for a user's IANA timezone, such as America/New_York, at the presentation layer.
  • Store the IANA zone separately when the user's local-time context matters.
  • Do not store a bare local date-time string as the only record of an instant.

Pick the conversion that matches the job

Task Recommended conversion
Inspect one timestamp use the Epoch to Date Converter
JavaScript seconds new Date(seconds * 1000)
JavaScript milliseconds new Date(milliseconds)
Python UTC datetime datetime.fromtimestamp(seconds, tz=timezone.utc)
Linux UTC output date -u -d @seconds
macOS UTC output date -u -r seconds
PostgreSQL to_timestamp(seconds)
MySQL FROM_UNIXTIME(seconds) after checking the session timezone
SQLite datetime(seconds, 'unixepoch')
BigQuery TIMESTAMP_SECONDS, TIMESTAMP_MILLIS, or TIMESTAMP_MICROS
Excel seconds =A1/86400 + DATE(1970,1,1)
Google Sheets seconds =EPOCHTODATE(A1, 1)

If you think a conversion is wrong, create the UTC value first. When that instant is right, local formatting is a separate, much smaller problem.

JavaScript: remember that Date uses milliseconds

JavaScript's Date timestamp value is milliseconds from the Unix epoch. Multiply Unix seconds by 1,000:

const createdAtSeconds = 1700000000;
const createdAt = new Date(createdAtSeconds * 1000);

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

A 13-digit millisecond value is already in the unit Date expects:

const createdAtMs = 1700000000000;

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

For user-facing text, set timeZone explicitly. Otherwise the result depends on the browser, server, container, or test runner executing the code.

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

formatter.format(new Date(1700000000 * 1000));
// "Nov 14, 2023, 5:13 PM"

If your application knows the unit, make it an argument instead of guessing:

function unixToDate(value, unit = "seconds") {
  if (!Number.isFinite(value)) {
    throw new TypeError("timestamp must be a finite number");
  }

  if (unit === "seconds") return new Date(value * 1000);
  if (unit === "milliseconds") return new Date(value);
  if (unit === "microseconds") return new Date(Math.trunc(value / 1000));

  throw new RangeError(`Unsupported unit: ${unit}`);
}

unixToDate(1700000000, "seconds").toISOString();
// "2023-11-14T22:13:20.000Z"

Keep an eye out for form and JSON values. JavaScript supports some inputs that are technically valid but probably not what you intended:

new Date(null).toISOString();
// "1970-01-01T00:00:00.000Z"

new Date(undefined).toString();
// "Invalid Date"

Nanosecond epoch values also exceed the exact-integer range of a JavaScript Number. Keep them as BigInt or strings until you deliberately reduce the precision.

Python: create an aware UTC datetime

Pass the timezone when you construct the datetime:

from datetime import datetime, timezone

created_at = datetime.fromtimestamp(1700000000, tz=timezone.utc)

created_at.isoformat()
# '2023-11-14T22:13:20+00:00'

Without tz, fromtimestamp() uses the machine's local timezone and returns a naive datetime:

datetime.fromtimestamp(1700000000)
# Result depends on the machine's local timezone

Scale subsecond units before calling a seconds-based API:

datetime.fromtimestamp(1700000000000 / 1_000, tz=timezone.utc)
datetime.fromtimestamp(1700000000000000 / 1_000_000, tz=timezone.utc)

Python 3.11 and newer also provide datetime.UTC:

from datetime import UTC, datetime

datetime.fromtimestamp(1700000000, tz=UTC)

Avoid datetime.utcfromtimestamp() in new code. It returns a naive object and has been deprecated since Python 3.12; the documented replacement is datetime.fromtimestamp(timestamp, UTC).

For dates far outside the normal operating range, fromtimestamp() can raise OverflowError or OSError depending on the platform. Unit mistakes often surface as range errors, which is a good reason to validate the unit before blaming the date library.

Python datetime.fromtimestamp

PHP: choose UTC or a named timezone deliberately

gmdate() formats Unix seconds in UTC:

echo gmdate('c', 1700000000);
// 2023-11-14T22:13:20+00:00

By contrast, date() uses PHP's configured default timezone. For application code that needs a named zone, DateTimeImmutable keeps the conversion explicit:

$instant = new DateTimeImmutable('@1700000000');

echo $instant
  ->setTimezone(new DateTimeZone('Asia/Tokyo'))
  ->format(DateTimeInterface::ATOM);
// 2023-11-15T07:13:20+09:00

These APIs take seconds. Divide millisecond input by 1,000 first.

PHP gmdate

Java: use Instant for the point on the timeline

The java.time API makes the unit visible in the method name:

Instant instant = Instant.ofEpochSecond(1700000000L);

instant.toString();
// 2023-11-14T22:13:20Z

For milliseconds:

Instant instant = Instant.ofEpochMilli(1700000000000L);

Attach a zone only when you need local calendar fields:

ZonedDateTime tokyo = Instant
    .ofEpochSecond(1700000000L)
    .atZone(ZoneId.of("Asia/Tokyo"));

Legacy java.util.Date(long) expects milliseconds, so passing Unix seconds directly produces the familiar 1970 error.

Oracle Java Instant · Java timestamp snippets

C# and .NET: let DateTimeOffset carry the instant

Use the method that names your input unit:

var createdAt = DateTimeOffset.FromUnixTimeSeconds(1700000000);

createdAt.ToString("O");
// 2023-11-14T22:13:20.0000000+00:00

For milliseconds:

var createdAt = DateTimeOffset.FromUnixTimeMilliseconds(1700000000000);

Then perform timezone conversion for display:

var zone = TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
var local = TimeZoneInfo.ConvertTime(createdAt, zone);

Support for timezone identifiers depends on the operating system and the.NET deployment. Don’t assume an IANA or Windows ID will work everyplace, test it on the platform where the application runs.

Microsoft DateTimeOffset.FromUnixTimeSeconds · C# timestamp snippets

Go: match the helper to the precision

Go's original constructor accepts seconds plus a nanosecond offset:

t := time.Unix(1700000000, 0).UTC()

fmt.Println(t.Format(time.RFC3339))
// 2023-11-14T22:13:20Z

Modern Go also has helpers for common subsecond epoch values:

time.UnixMilli(1700000000000).UTC()
time.UnixMicro(1700000000000000).UTC()

For a full nanosecond epoch count, this form preserves the intended unit:

time.Unix(0, 1700000000000000000).UTC()

Load an IANA location when producing wall-clock output:

loc, err := time.LoadLocation("Europe/Berlin")
if err != nil {
    log.Fatal(err)
}

fmt.Println(time.Unix(1700000000, 0).In(loc).Format(time.RFC3339))

Go package time · Go timestamp snippets

Convert Unix timestamps with Linux and macOS date

The awkward part is not Unix time. It is that date is not the same command everywhere.

Environment Common implementation What to remember
Ubuntu, Debian, Fedora, RHEL GNU coreutils date supports -d, @SECONDS, %N, --rfc-3339, and --iso-8601
macOS and FreeBSD BSD date use -r SECONDS for epoch-to-date conversion
Alpine and small CI images often BusyBox date the option set is smaller; test the exact image
macOS with GNU coreutils GNU gdate use gdate for Linux-compatible commands

On GNU/Linux, prefix epoch seconds with @. Add -u when the result must be repeatable across laptops, servers, containers, and CI jobs:

date -u -d @1700000000

date -u -d @1700000000 +'%Y-%m-%dT%H:%M:%SZ'
# 2023-11-14T22:13:20Z

date -u -d @1700000000 --rfc-3339=seconds
# 2023-11-14 22:13:20+00:00

Omit -u only when you intentionally want the machine's local timezone. To render a named timezone without changing the host clock, set TZ for that command:

TZ=America/New_York date -d @1700000000 +'%Y-%m-%d %H:%M:%S %Z'
# 2023-11-14 17:13:20 EST

TZ=Asia/Tokyo date -d @1700000000 +'%Y-%m-%d %H:%M:%S %Z'
# 2023-11-15 07:13:20 JST

That TZ= prefix changes only the displayed wall-clock value. It does not alter the instant represented by the timestamp.

Convert epoch milliseconds in shell

Most command-line timestamp converters read seconds. Passing a 13-digit millisecond value directly to date -d @... treats it as seconds and produces a date far in the future. Divide by 1,000 first when whole-second precision is enough:

ms=1700000000000
seconds=$((ms / 1000))
date -u -d "@$seconds" +'%Y-%m-%dT%H:%M:%SZ'
# 2023-11-14T22:13:20Z

If the fractional milliseconds matter, split the value into seconds and the final three digits:

ms=1700000000123
seconds=${ms%???}
millis=${ms#$seconds}

date -u -d "@$seconds.$millis" +'%Y-%m-%dT%H:%M:%S.%3NZ'
# 2023-11-14T22:13:20.123Z

Keep units in variable names such as created_at_s, created_at_ms, or client_sent_at_ms. That prevents more mistakes than a clever digit-count guess buried deep in a script.

Use BSD date on macOS and FreeBSD

BSD date uses -r SECONDS instead of GNU -d @SECONDS:

date -u -r 1700000000 +'%Y-%m-%dT%H:%M:%SZ'
# 2023-11-14T22:13:20Z

TZ=America/New_York date -r 1700000000 +'%Y-%m-%d %H:%M:%S %Z'
# 2023-11-14 17:13:20 EST

The same flag can mean different things: GNU date -r FILE displays a file's modification time, while BSD date -r SECONDS converts epoch seconds. If macOS scripts need Linux syntax, install GNU coreutils and call gdate explicitly:

gdate -u -d @1700000000 +'%Y-%m-%dT%H:%M:%SZ'

Validate timestamp input before calling date

For script arguments, reject unexpected input instead of silently guessing the unit:

ts=${1:-}

case "$ts" in
  ''|*[!0-9]*)
    printf 'usage: %s UNIX_SECONDS\n' "$0" >&2
    exit 2
    ;;
esac

date -u -d "@$ts" +'%Y-%m-%dT%H:%M:%SZ'

This example only accepts positive integer seconds, on purpose. If negative timestamp or milliseconds are valid, expose the unit as a separate argument instead of having the script infer it.

Troubleshoot common date command failures

Symptom Likely cause Fix
date: illegal option -- d macOS/BSD date use date -u -r SECONDS, or install GNU gdate
Output is near 1970 seconds were passed to a millisecond API elsewhere check the unit at the boundary
Output is far in the future milliseconds were passed to date as seconds divide by 1,000 or preserve the fractional part
date +%s%3N ends with 3N BSD date does not support GNU %N use Python, Node.js, Perl, or gdate
Script works on Ubuntu but fails on Alpine BusyBox date has fewer options test the exact image or install coreutils
Laptop and server show different hours local timezone was used add -u or an explicit TZ=Region/City

A small platform branch is useful when a script must run with either GNU or BSD date:

if date --version >/dev/null 2>&1; then
  date -u -d @1700000000 +'%Y-%m-%dT%H:%M:%SZ'
else
  date -u -r 1700000000 +'%Y-%m-%dT%H:%M:%SZ'
fi

Decode SOURCE_DATE_EPOCH in reproducible builds

Many build tools recognize SOURCE_DATE_EPOCH as epoch seconds so generated files can receive stable timestamps:

export SOURCE_DATE_EPOCH=1700000000
date -u -d "@$SOURCE_DATE_EPOCH" +'%Y-%m-%dT%H:%M:%SZ'
# 2023-11-14T22:13:20Z

Keep the value in seconds unless the consuming tool explicitly documents another unit.

GNU Coreutils date manual · Linux date(1) manual · FreeBSD date(1) manual

SQL: conversion and rendering may use different timezones

Database functions are concise, but their displayed output can depend on the session timezone.

Database Unix-seconds conversion Important behavior
PostgreSQL to_timestamp(1700000000) returns timestamp with time zone; display follows the session zone
MySQL FROM_UNIXTIME(1700000000) renders in the current session timezone
SQLite datetime(1700000000, 'unixepoch') returns UTC unless localtime is added
BigQuery TIMESTAMP_SECONDS(1700000000) use a unit-specific constructor

PostgreSQL:

SELECT to_timestamp(1700000000) AT TIME ZONE 'UTC';
-- 2023-11-14 22:13:20

MySQL:

SET time_zone = '+00:00';
SELECT FROM_UNIXTIME(1700000000);
-- 2023-11-14 22:13:20

SQLite:

SELECT datetime(1700000000, 'unixepoch');
-- 2023-11-14 22:13:20

BigQuery makes precision explicit:

SELECT TIMESTAMP_SECONDS(1700000000);
SELECT TIMESTAMP_MILLIS(1700000000000);
SELECT TIMESTAMP_MICROS(1700000000000000);

During a migration you might want to keep both the raw integer and the parsed timestamp until the data has been audited. That gives you a trail back to the records imported with the incorrect unit.

Excel: convert seconds to days, then format the cell

Dates are stored as serial numbers in days. Divide Unix seconds by the seconds in a day and add the epoch date:

=A1/86400 + DATE(1970,1,1)

For other units:

=A1/86400000 + DATE(1970,1,1)      // milliseconds
=A1/86400000000 + DATE(1970,1,1)   // microseconds

If the cell shows a decimal, apply a date-time number format. The formula may already be correct.

Spreadsheets introduce two practical dangers: they do not attach a timezone to the serial value, and CSV imports may display large integers in scientific notation. If you need precision-sensitive timestamp columns, import them as text, and call the column UTC or local.

Microsoft Excel DATE function

Google Sheets: use EPOCHTODATE and name the unit

Google Sheets accepts seconds, milliseconds, or microseconds through a unit argument:

=EPOCHTODATE(A1, 1)  // seconds
=EPOCHTODATE(A1, 2)  // milliseconds
=EPOCHTODATE(A1, 3)  // microseconds

The result is UTC, not the spreadsheet's local timezone. Google Sheets also rejects negative timestamps in this function.

For positive Unix seconds, the day-based formula is an alternative:

=A1/86400 + DATE(1970,1,1)

In a shared sheet, headers such as created_at_seconds_utc and created_at_ms_utc save the next person from having to reverse-engineer both the unit and the intended timezone.

Google Sheets EPOCHTODATE

Debug wrong dates by symptom

Symptom Likely cause First fix to try
January 1970 seconds passed to a millisecond API multiply by 1,000
Thousands of years in the future milliseconds or microseconds passed as seconds divide by 1,000 or 1,000,000
Laptop and server disagree each runtime used its default timezone specify UTC or an IANA timezone
Python datetime has no offset a naive datetime was created pass tz=timezone.utc
MySQL output shifts by hours session timezone differs set or convert the session timezone
Google Sheets rejects the value negative timestamp or wrong unit check the function's supported range and unit argument
CSV value lost digits spreadsheet numeric coercion import as text or use integer-safe tooling
Pre-1970 date fails target platform has a restricted range test negative timestamps in that exact system

Most timestamp conversion failures come down to unit, timezone, or range. Check those three before replacing working date code.

Keep a small set of known values in tests

One happy-path value is not enough. These cases expose the most common assumptions:

Timestamp Expected UTC value What it tests
0 1970-01-01T00:00:00Z epoch boundary
-1 1969-12-31T23:59:59Z negative timestamp support
1000000000 2001-09-09T01:46:40Z older 10-digit seconds
1700000000 2023-11-14T22:13:20Z ordinary seconds
2147483647 2038-01-19T03:14:07Z signed 32-bit boundary
1700000000000 2023-11-14T22:13:20Z millisecond input
1700000000000000 2023-11-14T22:13:20Z microsecond input

For a user interface, add at least one timezone assertion too:

1700000000 in America/New_York = 2023-11-14 17:13:20 EST
1700000000 in Asia/Tokyo       = 2023-11-15 07:13:20 JST

That test catches a conversion whose instant is correct but whose presentation uses the wrong zone.

The rule worth carrying into production

Converting Unix time to a readable date is not really one operation. It is a short pipeline:

raw integer → known unit → UTC instant → timezone-aware display

Those phases should not be mixed. Name the unit at the boundary, keep the instant in UTC, and only choose a timezone when rendering for a person. Once that contract is established , language specific code is routine , not arcane .

Official references

Frequent questions:

Q: How do I convert Unix time to a date?
A: Identify the unit first. Treat 1700000000 as Unix seconds and 1700000000000 as milliseconds. Both represent 2023-11-14T22:13:20Z. Decode the value as UTC, then format that instant in the timezone you need.
Q: Why does my Unix timestamp convert to 1970?
A: The usual cause is a unit mismatch. JavaScript Date expects milliseconds, so new Date(1700000000) reads the value as 1.7 billion milliseconds after the epoch. Use new Date(1700000000 * 1000) for Unix seconds.
Q: How do I convert Unix time to date in JavaScript?
A: For Unix seconds, use new Date(seconds * 1000).toISOString(). For Unix milliseconds, pass the value directly to new Date(milliseconds). Use Intl.DateTimeFormat with an explicit timeZone for local output.
Q: How do I convert Unix time to date in Python?
A: Use datetime.fromtimestamp(seconds, tz=timezone.utc). The tz argument matters because omitting it returns a datetime in the machine's local timezone. Avoid utcfromtimestamp() in new code because it returns a naive datetime and is deprecated.
Q: How do I convert Unix time to date in SQL?
A: Use the native function for your database: PostgreSQL to_timestamp(seconds), MySQL FROM_UNIXTIME(seconds), SQLite datetime(seconds, 'unixepoch'), or BigQuery TIMESTAMP_SECONDS, TIMESTAMP_MILLIS, and TIMESTAMP_MICROS. Check the database session timezone before comparing rendered strings.
Q: How do I convert a 13-digit timestamp to a date?
A: A current 13-digit epoch value is usually milliseconds. JavaScript Date, Java Instant.ofEpochMilli, and .NET FromUnixTimeMilliseconds accept milliseconds directly. Python, PHP, PostgreSQL, MySQL, and many shell tools usually expect seconds, so divide by 1000.
Q: How do I convert a 16-digit timestamp to a date?
A: A current 16-digit epoch value is usually microseconds. Divide by 1,000,000 for seconds-based APIs, or use a native microsecond function such as BigQuery TIMESTAMP_MICROS or Go time.UnixMicro.
Q: Should I display Unix time as UTC or local time?
A: Use UTC for logs, APIs, database exports, audit trails, and cross-system debugging. Use the user's IANA timezone for dashboards, deadlines, receipts, and calendar-facing UI. Keep the original instant as the source of truth.
Q: Can Unix time be negative?
A: Yes. Negative Unix seconds represent instants before 1970-01-01 UTC. Many languages support them, but spreadsheets, databases, and operating-system calls may have narrower ranges, so test pre-1970 data in the actual target system.
Q: Is Unix time the same as epoch time?
A: In most developer contexts, Unix time, Unix timestamp, epoch time, and POSIX time refer to a count from 1970-01-01 00:00:00 UTC. The unit is not guaranteed, so confirm whether a system stores seconds, milliseconds, microseconds, or nanoseconds.
Q: Why does date -d fail on macOS?
A: macOS ships BSD date rather than GNU date. Use date -u -r 1700000000 to convert epoch seconds, or install GNU coreutils and run gdate -u -d @1700000000.
Q: Why does date +%s%3N print 3N on macOS?
A: The %N nanosecond format is a GNU date extension. BSD date may print 3N literally, so use Python, Node.js, Perl, or GNU gdate for portable current epoch milliseconds.