Quick answer
If you have this 13-digit timestamp:
1700000000000
it is usually epoch milliseconds. It represents:
2023-11-14T22:13:20.000Z
Use it directly in APIs that expect milliseconds:
new Date(1700000000000).toISOString()
// 2023-11-14T22:13:20.000Z
Divide by 1000 in APIs that expect Unix seconds:
datetime.fromtimestamp(1700000000000 / 1000, tz=timezone.utc)
# 2023-11-14 22:13:20+00:00
The practical rule:
13 digits in a modern timestamp usually means milliseconds.
10 digits usually means seconds.
Convert once at the system boundary, then keep one unit inside that system.
What epoch milliseconds mean
Epoch milliseconds count thousandths of a second since 1970-01-01 00:00:00 UTC. They describe the same instant as Unix seconds, just with three extra digits of precision.
For example:
| Unit | Value | UTC date |
|---|---|---|
| Unix seconds | 1700000000 |
2023-11-14T22:13:20Z |
| Epoch milliseconds | 1700000000000 |
2023-11-14T22:13:20.000Z |
| Epoch microseconds | 1700000000000000 |
2023-11-14T22:13:20.000000Z |
| Epoch nanoseconds | 1700000000000000000 |
2023-11-14T22:13:20.000000000Z |
Digit counting is a fast triage tool:
| Digits today | Usually means | Common sources |
|---|---|---|
| 10 digits | Unix seconds | Linux date +%s, JWT exp, many server APIs, PHP time() |
| 13 digits | Epoch milliseconds | JavaScript Date.now(), Java System.currentTimeMillis(), .NET Unix milliseconds, MongoDB Date |
| 16 digits | Epoch microseconds | some Postgres exports, BigQuery, analytics pipelines |
| 19 digits | Epoch nanoseconds | OpenTelemetry, InfluxDB, high-resolution tracing |
Do not rely on digit count alone. A value from the year 2286 in seconds can also be 11 digits, and a custom system may use a different epoch. The field name, API docs, and source system matter more than the number of digits.
Good field names:
created_at_ms
timestampMs
epoch_millis
event_time_us
trace_time_ns
expires_at_seconds
Risky field name:
timestamp
Which languages expect milliseconds?
Some APIs accept milliseconds directly. Others expect seconds and need division.
| Runtime or system | Input for date conversion | Correct millisecond conversion |
|---|---|---|
JavaScript Date |
milliseconds | new Date(ms) |
Java Instant |
milliseconds or seconds, depending on method | Instant.ofEpochMilli(ms) |
| Kotlin/JVM | Java Instant milliseconds |
Instant.ofEpochMilli(ms) |
| C# / .NET | milliseconds or seconds, depending on method | DateTimeOffset.FromUnixTimeMilliseconds(ms) |
| Go | milliseconds in Go 1.17+ | time.UnixMilli(ms) |
Python datetime |
seconds | datetime.fromtimestamp(ms / 1000, tz=timezone.utc) |
PHP DateTime('@...') |
seconds | new DateTime('@' . intdiv($ms, 1000)) |
Ruby Time.at |
seconds, accepts fractional | Time.at(ms / 1000.0).utc |
SQLite datetime(..., 'unixepoch') |
seconds | datetime(ms / 1000, 'unixepoch') |
PostgreSQL to_timestamp |
seconds | to_timestamp(ms / 1000.0) |
This is where most real bugs happen: a frontend sends Date.now() in milliseconds, but the backend parser expects seconds.
JavaScript: convert 13-digit timestamp to Date
JavaScript Date uses epoch milliseconds. Do not divide a 13-digit value before passing it to new Date().
const ms = 1700000000000;
new Date(ms).toISOString();
// 2023-11-14T22:13:20.000Z
new Date(ms).getTime();
// 1700000000000
If you have Unix seconds, multiply first:
const seconds = 1700000000;
new Date(seconds).toISOString();
// 1970-01-20T16:13:20.000Z <-- wrong unit
new Date(seconds * 1000).toISOString();
// 2023-11-14T22:13:20.000Z
Format in a named timezone with Intl.DateTimeFormat:
const date = new Date(1700000000000);
new Intl.DateTimeFormat("en-US", {
timeZone: "America/New_York",
dateStyle: "medium",
timeStyle: "medium",
}).format(date);
// Nov 14, 2023, 5:13:20 PM
Be precise with performance.now(). It is a millisecond value, but it is not epoch milliseconds:
Date.now(); // epoch milliseconds
performance.now(); // elapsed milliseconds since timeOrigin
Use Date.now() for wall-clock instants. Use performance.now() for measuring elapsed time.
Java and Kotlin: long milliseconds to date
On the JVM, a long timestamp is often milliseconds. The safe modern API is java.time.Instant.
long ms = 1700000000000L;
Instant instant = Instant.ofEpochMilli(ms);
System.out.println(instant);
// 2023-11-14T22:13:20Z
ZonedDateTime newYork = instant.atZone(ZoneId.of("America/New_York"));
Use the method name to make the unit obvious:
Instant.ofEpochMilli(ms); // milliseconds
Instant.ofEpochSecond(sec); // seconds
Instant.now().toEpochMilli(); // current epoch milliseconds
Instant.now().getEpochSecond(); // current Unix seconds
Kotlin on the JVM uses the same API:
val ms = 1700000000000L
val instant = Instant.ofEpochMilli(ms)
val local = instant.atZone(ZoneId.of("America/New_York"))
For multiplatform Kotlin, use kotlinx-datetime:
val instant = kotlinx.datetime.Instant.fromEpochMilliseconds(ms)
Avoid guessing from Long alone. A Java long can hold seconds, milliseconds, microseconds, or IDs. The method name and field name must carry the unit.
C# / .NET: FromUnixTimeMilliseconds
.NET has dedicated methods for Unix seconds and milliseconds.
long ms = 1700000000000L;
var dto = DateTimeOffset.FromUnixTimeMilliseconds(ms);
Console.WriteLine(dto.ToString("O"));
// 2023-11-14T22:13:20.0000000+00:00
Use the matching method:
DateTimeOffset.FromUnixTimeMilliseconds(ms);
DateTimeOffset.FromUnixTimeSeconds(seconds);
DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
DateTimeOffset.UtcNow.ToUnixTimeSeconds();
For display, keep the UTC instant separate from the output timezone:
var pacific = TimeZoneInfo.FindSystemTimeZoneById("America/Los_Angeles");
var local = TimeZoneInfo.ConvertTime(dto, pacific);
Do not replace the built-in conversion with hand-written new DateTime(1970, 1, 1).AddMilliseconds(ms) unless you are maintaining very old framework code.
Python: divide by 1000 and pass timezone
Python's datetime.fromtimestamp() expects seconds. It accepts fractional seconds, so a millisecond value can be divided by 1000.
from datetime import datetime, timezone
ms = 1700000000000
dt = datetime.fromtimestamp(ms / 1000, tz=timezone.utc)
print(dt.isoformat())
# 2023-11-14T22:13:20+00:00
For a named timezone:
from zoneinfo import ZoneInfo
datetime.fromtimestamp(ms / 1000, tz=ZoneInfo("America/New_York"))
# 2023-11-14T17:13:20-05:00
For current epoch milliseconds:
int(datetime.now(timezone.utc).timestamp() * 1000)
Avoid naive UTC conversions in new code:
# Avoid: returns a naive datetime and is deprecated in Python 3.12
datetime.utcfromtimestamp(ms / 1000)
If you are converting very large batches, pandas handles the unit directly:
pd.to_datetime(series, unit="ms", utc=True)
Go: time.UnixMilli
Go 1.17 added time.UnixMilli, which is the clearest way to decode epoch milliseconds.
ms := int64(1700000000000)
t := time.UnixMilli(ms).UTC()
fmt.Println(t.Format(time.RFC3339Nano))
// 2023-11-14T22:13:20Z
For older Go code:
time.Unix(0, ms*int64(time.Millisecond)).UTC()
For the current time:
time.Now().UnixMilli()
time.Now().Unix()
Use UnixMicro and UnixNano only when the source value is actually microseconds or nanoseconds.
SQL: convert epoch milliseconds in queries
SQL functions often expect Unix seconds, not milliseconds.
PostgreSQL:
SELECT to_timestamp(1700000000000 / 1000.0);
-- 2023-11-14 22:13:20+00
SQLite:
SELECT datetime(1700000000000 / 1000, 'unixepoch');
-- 2023-11-14 22:13:20
MySQL:
SELECT FROM_UNIXTIME(1700000000000 / 1000);
For range filters, compare in the unit stored by the column:
-- Millisecond column
WHERE created_at_ms >= 1700000000000
AND created_at_ms < 1700086400000
-- Second column
WHERE created_at_seconds >= 1700000000
AND created_at_seconds < 1700086400
Do not compare a millisecond column to second boundaries:
-- Wrong
WHERE created_at_ms < 1700086400
PHP, Ruby, Rust, and Swift quick recipes
PHP:
$ms = 1700000000000;
$dt = (new DateTimeImmutable('@' . intdiv($ms, 1000)))
->setTimezone(new DateTimeZone('UTC'));
echo $dt->format(DateTimeInterface::ATOM);
Ruby:
ms = 1700000000000
Time.at(ms / 1000.0).utc.iso8601(3)
# "2023-11-14T22:13:20.000Z"
Rust with chrono:
let ms = 1700000000000;
let dt = chrono::DateTime::<chrono::Utc>::from_timestamp_millis(ms).unwrap();
println!("{}", dt.to_rfc3339());
Swift:
let ms = 1700000000000.0
let date = Date(timeIntervalSince1970: ms / 1000.0)
The pattern is the same: if the API expects seconds, divide by 1000. If the API name says milliseconds, pass the value directly.
How to convert milliseconds to Unix seconds
The basic conversion is:
seconds = milliseconds / 1000
milliseconds = seconds * 1000
The rounding decision depends on what the timestamp means.
| Use case | Suggested conversion |
|---|---|
| Current Unix timestamp for logs | Math.floor(Date.now() / 1000) |
| Display a precise date | keep fractional seconds, such as ms / 1000 |
| Expiration time that must not expire early | consider ceil(ms / 1000) if the API only accepts seconds |
| Half-open range lower bound | floor only if the source is already aligned to the desired precision |
| Half-open range upper bound | compute the boundary in the target unit, then use < end |
For example, if a JavaScript frontend sends expiresAtMs, do not quietly floor it in five different places. Convert once at the API boundary, document the rounding choice, and keep the stored unit clear.
Why epoch milliseconds convert to strange dates
Most wrong-date bugs come from one of these cases.
| Symptom | Likely cause | Fix |
|---|---|---|
| Date shows January 1970 | Seconds passed to a millisecond API | Multiply by 1000 |
| Date shows year 55000, or the runtime throws an out-of-range timestamp error | Milliseconds passed to a seconds API | Divide by 1000 |
| Date is off by hours | Runtime formatted in local timezone | Format with UTC or an IANA timezone |
| Milliseconds disappeared | Code converted to integer seconds too early | Keep milliseconds until the boundary requires seconds |
| JSON value changed | Nanoseconds or very large integer exceeded JS safe integer range | Use string or BigInt |
Spreadsheet shows 1.7E+12 |
CSV/Excel formatted the column as a number | Import as text when exact values matter |
Two test values are enough to catch the common mistake:
1700000000 -> 2023-11-14T22:13:20Z if seconds
1700000000000 -> 2023-11-14T22:13:20Z if milliseconds
If both paths in your code produce the same date without an explicit multiply or divide, something is probably hidden.
Storing epoch milliseconds in JSON and databases
Epoch milliseconds fit safely in a JavaScript Number for present-day timestamps. Current epoch nanoseconds do not. This matters when APIs serialize timestamps through JSON:
{
"created_at_ms": 1700000000000,
"trace_time_ns": "1700000000000000000"
}
Use a number for milliseconds when your stack expects it. Use a string or BigInt for nanoseconds and other values that may exceed Number.MAX_SAFE_INTEGER.
In SQL, a BIGINT column is a good fit for epoch milliseconds when:
- the source system already emits milliseconds,
- range scans are numeric,
- the field name includes
_ms, - the application owns timezone display separately.
A native timestamp column is often better when:
- analysts need date functions,
- SQL queries group by day, week, or month,
- the database should enforce date semantics,
- human debugging matters more than raw ingestion speed.
There is no universal winner. The bad choice is an unlabeled integer column called timestamp.
PR review checklist
Before merging code that converts epoch milliseconds, check:
- The source unit is named in the variable, field, or schema.
- A 13-digit value is not passed into a seconds-only API.
- A 10-digit value is not passed into a millisecond-only API.
- The display timezone is explicit: UTC, local, or an IANA timezone.
- API boundaries convert once, not repeatedly across layers.
- Range queries compare values in the stored unit.
- Large nanosecond values are strings or BigInt, not unsafe JSON numbers.
- Tests include both
1700000000and1700000000000.
Official references
- MDN Date - JavaScript
Datestores a timestamp as milliseconds since the Unix epoch. - MDN Date.now - returns the current epoch time in milliseconds.
- MDN performance.now - high-resolution elapsed time, not a Unix timestamp.
- Java Instant.ofEpochMilli - JVM conversion from epoch milliseconds.
- Microsoft DateTimeOffset.FromUnixTimeMilliseconds - .NET conversion from epoch milliseconds.
- Python datetime.fromtimestamp - Python conversion from Unix seconds, with timezone support.
- Go time.UnixMilli - Go conversion from epoch milliseconds.
- IANA Time Zone Database - canonical timezone names and rule data.
Related timestamp guides
FAQ
- How do I convert epoch milliseconds to a date?
- Treat a modern 13-digit value as milliseconds. JavaScript: new Date(ms).toISOString(). Java: Instant.ofEpochMilli(ms). C#: DateTimeOffset.FromUnixTimeMilliseconds(ms). Python: datetime.fromtimestamp(ms / 1000, tz=timezone.utc).
- Is a 13-digit timestamp always milliseconds?
- For modern Unix-era dates, 13 digits usually means milliseconds. It is still a heuristic, not a contract. Always check the field name or API docs. Values named created_at_ms, timestampMs, epochMillis, or System.currentTimeMillis are milliseconds; values named exp, iat, unix, or epoch seconds are often seconds.
- Why does my converted date show 1970?
- A 1970 date usually means you passed Unix seconds to an API that expects milliseconds, such as JavaScript new Date(1700000000). Multiply the 10-digit value by 1000 first: new Date(1700000000 * 1000).
- Why does my converted date show the year 55000?
- A far-future date, or an out-of-range timestamp error, usually means you passed epoch milliseconds to an API that expects seconds. Divide by 1000 before calling seconds-based APIs such as Python datetime.fromtimestamp or SQL to_timestamp.
- Does JavaScript use milliseconds or seconds?
- JavaScript Date uses epoch milliseconds. Date.now(), new Date().getTime(), and the Date constructor all work with milliseconds. performance.now() is also measured in milliseconds, but it is not a Unix timestamp; it is relative to performance.timeOrigin and is for elapsed-time measurement.
- How do I convert milliseconds to Unix seconds?
- Divide by 1000. For a current timestamp in JavaScript, Math.floor(Date.now() / 1000) returns Unix seconds. For future expiration boundaries, be explicit about whether floor, round, or ceil matches the API contract.
- How do I convert epoch milliseconds in Python?
- Use datetime.fromtimestamp(ms / 1000, tz=timezone.utc). Python fromtimestamp expects seconds and accepts fractional seconds, so dividing by 1000 preserves the millisecond portion for normal date conversion.
- How do I convert Java currentTimeMillis to a date?
- Use Instant.ofEpochMilli(System.currentTimeMillis()). For display, attach a zone with atZone(ZoneId.of("UTC")) or the user's IANA timezone.
- How should I store epoch milliseconds in SQL?
- Use a BIGINT column with the unit in the name, such as created_at_ms, when the source system already produces milliseconds. For SQL date functions, divide by 1000 when converting to timestamp/date types that expect seconds.
- Are epoch milliseconds safe in JavaScript Number?
- Current epoch milliseconds are safe in JavaScript Number because they are far below Number.MAX_SAFE_INTEGER. Current epoch nanoseconds are not safe. Use BigInt or strings for nanosecond timestamps.
- Should I display epoch milliseconds in UTC or local time?
- Decode the number as one UTC instant first. Then display that instant in UTC, the viewer's local timezone, or a named IANA timezone such as America/New_York. The timestamp itself does not contain a timezone.