A timestamp is meaningful only with a unit, epoch, sign convention, clock source, and precision contract. A 19-digit integer is often a contemporary Unix timestamp in nanoseconds, but digit count is only a heuristic: dates before the epoch, far-future values, leading zeros, negative values, and custom epochs break it. A nanosecond field also does not prove that the clock measured time accurately to one nanosecond.
Key Takeaways
- State the unit explicitly in an API or schema, such as
created_at_nsor an RFC 3339 string with a documented fractional precision. - Convert with integer division and remainder. Avoid floating-point conversion for 64-bit or 128-bit epoch values.
- Unix wall-clock timestamps and monotonic durations are different domains.
process.hrtime.bigint()andSystem.nanoTime()cannot be converted to a calendar date. - A unit's resolution, the clock's precision, its accuracy, and event-order guarantees are separate properties.
- Use UTC instants for interchange and a named IANA time zone only for presentation. Do not add a fixed eight-hour offset as a general time-zone conversion.
- Validate range, sign, overflow, fractional remainder, leap-second policy, and database/JSON representation at the boundary.
Define the Timestamp Contract
Document all of these fields:
| Question | Example decision |
|---|---|
| Epoch | Unix epoch 1970-01-01T00:00:00Z |
| Unit | integer nanoseconds |
| Sign | negative values allowed for pre-epoch instants |
| Time scale | UTC/Unix time; leap-second handling documented |
| Clock | wall clock for dates, monotonic clock for durations |
| Precision | stored digits versus measured clock resolution |
| Range | minimum and maximum accepted instant |
| Serialization | decimal string for values beyond consumer integer precision |
Prefer names that carry units (event_time_ns, timeout_ms) or a typed schema. Do not make every consumer infer units from magnitude.
Units and Integer Conversion
Unix time counts units from the epoch:
1 second = 1,000 milliseconds
1 millisecond = 1,000 microseconds
1 microsecond = 1,000 nanoseconds
To convert an integer count n into an instant, choose a unit and split it into whole seconds plus a non-negative fractional remainder. For nanoseconds:
seconds = floor_div(n, 1_000_000_000)
nanos = floor_mod(n, 1_000_000_000)
The floor operations matter for negative timestamps. Language operators that truncate toward zero can produce a negative remainder and the wrong instant unless corrected.
Why Digit Counting Fails
The following values may be valid under different contracts:
0is the Unix epoch in every unit;-1can mean one unit before the epoch;- a date in 1960 has fewer digits than a current nanosecond timestamp;
- a custom epoch or device tick count may have any magnitude;
- a decimal string may contain leading zeros;
- a 64-bit counter may be an elapsed duration rather than wall time.
If a legacy interface provides no unit, use a documented bounded heuristic only as a migration aid, emit an ambiguity error when multiple interpretations are plausible, and require explicit unit metadata for new interfaces.
Wall Clock, Monotonic Clock, and Accuracy
Wall clocks map an instant to an epoch and can jump because of synchronization, administrator changes, leap handling, or virtualization. Monotonic clocks are intended for elapsed-time measurement and do not map to UTC.
const started = process.hrtime.bigint();
// work
const elapsedNs = process.hrtime.bigint() - started;
elapsedNs is a duration. It must not be passed to new Date() or presented as a Unix timestamp. Use Date.now() or an injected wall clock for an event timestamp, and use the monotonic clock for timeout and latency measurement.
Resolution is the smallest representable increment; precision describes repeatability; accuracy describes closeness to a reference clock. A system can store nanoseconds while its physical clock is updated much less frequently. Distributed event ordering also needs sequence numbers, logical clocks, or trace IDs when equal or skewed wall times are possible.
JavaScript: Preserve Integer Precision
IEEE 754 Number safely represents integers only through 2^53 - 1. Contemporary epoch nanoseconds exceed that range. Keep the value as BigInt or a decimal string and convert only the part that the target API can represent:
const NS_PER_SECOND = 1_000_000_000n;
const NS_PER_MILLISECOND = 1_000_000n;
function epochNanosecondsToDate(ns) {
const value = typeof ns === "bigint" ? ns : BigInt(ns);
const milliseconds = value / NS_PER_MILLISECOND;
const date = new Date(Number(milliseconds));
if (!Number.isSafeInteger(Number(milliseconds)) || Number.isNaN(date.getTime())) {
throw new RangeError("timestamp is outside JavaScript Date range");
}
return date;
}
const instant = epochNanosecondsToDate("1706140800000000000");
console.log(instant.toISOString());
Date stores milliseconds and discards sub-millisecond remainder. If the remainder matters, return { milliseconds, nanosecondsRemainder } or keep an RFC 3339 string with the supported fractional digits.
Do not compute “current nanoseconds” as Date.now() * 1_000_000 and call it a precise measurement. It is a millisecond wall-clock reading expressed in another unit.
Python: Avoid Float Epoch Conversion
Use integer seconds and nanoseconds, then attach a timezone explicitly:
from datetime import datetime, timezone
NANOS_PER_SECOND = 1_000_000_000
def split_epoch_nanos(value: int) -> tuple[int, int]:
seconds, nanos = divmod(value, NANOS_PER_SECOND)
return seconds, nanos
def epoch_nanos_to_utc(value: int) -> datetime:
seconds, nanos = split_epoch_nanos(value)
return datetime.fromtimestamp(seconds, tz=timezone.utc).replace(
microsecond=nanos // 1_000
)
Python datetime stores microseconds, so the final 0–999 nanoseconds are not retained. For exact interchange, keep the integer or a decimal string. time.time_ns() returns an integer wall-clock reading, but the operating system's clock resolution and accuracy may be lower than one nanosecond.
For display, convert the UTC instant with an IANA zone:
from zoneinfo import ZoneInfo
beijing = epoch_nanos_to_utc(1706140800123456789).astimezone(
ZoneInfo("Asia/Shanghai")
)
print(beijing.isoformat())
Do not add a fixed offset as a general solution for time zones with daylight-saving or historical rule changes.
Go and Java: Use Native Epoch APIs
Go's time.Unix(seconds, nanos) accepts a seconds value and a nanosecond adjustment; use floor division for negative counts:
package timestamp
import "time"
const nanosPerSecond int64 = 1_000_000_000
func EpochNanosToTime(value int64) time.Time {
seconds := value / nanosPerSecond
nanos := value % nanosPerSecond
if nanos < 0 {
seconds--
nanos += nanosPerSecond
}
return time.Unix(seconds, nanos).UTC()
}
Check the target range before converting. UnixNano() can overflow outside the representable range of an int64, and time.Now().UnixNano() is a wall-clock value, not a monotonic duration.
Java's Instant also accepts seconds plus a non-negative nanosecond adjustment:
import java.time.Instant;
public final class Timestamps {
private static final long NANOS_PER_SECOND = 1_000_000_000L;
public static Instant fromEpochNanos(long value) {
long seconds = Math.floorDiv(value, NANOS_PER_SECOND);
long nanos = Math.floorMod(value, NANOS_PER_SECOND);
return Instant.ofEpochSecond(seconds, nanos);
}
}
Use ZoneId.of("Asia/Shanghai") or another named IANA zone only when formatting for a person. Do not infer units from String.length() or ignore the negative remainder.
Generating Current Values
Use the API that matches the purpose:
import time
wall_epoch_ns = time.time_ns() # epoch-based wall-clock reading
monotonic_ns = time.monotonic_ns() # elapsed-time domain
The two values are not interchangeable. A monotonic clock may be the right source for a timeout, benchmark, retry budget, or span duration; an epoch clock is the right source for an event's approximate occurrence time. Neither API guarantees physical nanosecond accuracy.
Database and API Representation
Storage precision and range vary by engine and version. A timestamp column that stores microseconds cannot round-trip nanoseconds. An integer BIGINT can preserve a chosen range, but teams must define signedness, range, unit, indexing, and display conversion.
JSON has no native BigInt. For epoch nanoseconds beyond the consumer's safe integer range, transmit a decimal string with a schema:
{
"event": "user_login",
"timestamp_ns": "1706140800123456789",
"timestamp_ms": 1706140800123
}
Do not include both units unless their consistency is validated. Reject malformed, out-of-range, ambiguous, or silently rounded values at the boundary. Record whether an input was rounded, truncated, or rejected.
Use Cases and Limits
Profiling
Measure durations with a monotonic clock and report the clock source, warm-up, sampling, and overhead. A wall-clock nanosecond timestamp cannot prove a function took a particular duration.
Logging and Tracing
Use an RFC 3339 UTC timestamp plus trace/span IDs and a monotonic duration when supported. Ordering by wall time alone is unsafe under clock skew and ties.
Financial and Scientific Systems
A nanosecond field does not establish exchange ordering, settlement correctness, or measurement accuracy. Define the time source, synchronization, uncertainty, sequence, correction, and audit rules. Monetary values should use a decimal representation rather than binary floating point.
Database Events
Store an instant and a domain sequence separately when ordering matters. Do not generate a unique ID from a timestamp alone; concurrent processes can share a timestamp and clocks can move backward.
Frequently Asked Questions
Can digit count identify timestamp precision?
Not reliably. It is only a heuristic for a bounded contemporary range. Require explicit units and reject ambiguous values in new APIs.
Does a nanosecond timestamp mean nanosecond accuracy?
No. Storage resolution, clock resolution, precision, accuracy, synchronization, and event-order guarantees are separate.
Can JavaScript Date preserve nanoseconds?
No. Date stores milliseconds. Use BigInt or a decimal string for the original value and retain the sub-millisecond remainder separately.
Are System.nanoTime() and process.hrtime.bigint() Unix timestamps?
No. They are monotonic elapsed-time sources. Use them for durations, not calendar dates.
How should time zones be handled?
Transmit and store an instant in UTC or an unambiguous offset. Apply a named IANA time zone only for presentation. Never assume “Beijing time” is a universal fixed-offset conversion for all historical or future rules.
Does Unix time include leap seconds?
Unix-time APIs and operating systems commonly ignore, smear, or otherwise model leap seconds according to their time scale. If leap-second labeling matters, document the time scale and use a domain library that supports it.
Primary Sources
- POSIX Base Definitions: Seconds Since the Epoch
- RFC 3339: Date and Time on the Internet
- IANA Time Zone Database
- W3C Trace Context
- ECMA-262: BigInt and Date
- Python
timedocumentation
Conclusion
High-resolution timestamps are a contract and clock-design problem, not merely a division by 1,000. State the epoch and unit, use integer arithmetic, separate wall time from monotonic durations, apply named time zones only at presentation, preserve precision across serialization, and measure accuracy independently. These rules prevent a plausible-looking date from hiding a wrong instant or an invalid performance claim.