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:
- Is the input in seconds, milliseconds, microseconds, or nanoseconds?
- 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.
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.
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.
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.
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
- The Open Group: Seconds Since the Epoch
- MDN
Dateconstructor - Python
datetime.fromtimestamp - PHP
gmdate - Oracle Java
Instant - Microsoft
DateTimeOffset.FromUnixTimeSeconds - Go package
time - PostgreSQL date/time functions
- MySQL date/time functions
- SQLite date/time functions
- BigQuery timestamp functions
- Google Sheets
EPOCHTODATE - Microsoft Excel
DATEfunction - GNU Coreutils
datemanual - Linux
date(1)manual - FreeBSD
date(1)manual
Related articles
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.