Precision & resolution
Seconds, millis, micros, nanos — and the bugs born when systems disagree.
Resolution is the smallest unit a representation can express. Different systems carry different resolutions, and mismatches between them silently truncate or distort values.
Classic Unix time (time_t) is whole seconds. JavaScript’s Date stores milliseconds (integer). PostgreSQL’s timestamp stores microseconds. Most modern OS APIs can return nanoseconds (though hardware precision may be lower). When you move a value between systems, you must be explicit about whether to truncate (floor), round, or raise an error.
The same instant lands at different resolutions depending on the type you reach for:
Temporal.Now.instant().epochNanoseconds; // BigInt nanoseconds
Date.now(); // milliseconds (number)
// Comparing a ms value to a Unix SECOND is off by 1000x.JS examples use Temporal — standardized in ES2026, available via a polyfill today.
import time
time.time() # float seconds since the epoch, e.g. 1780665600.123
time.time_ns() # int nanoseconds, e.g. 1780665600123456789
# Comparing a seconds value to an ns value is off by 1,000,000,000x.import java.time.Instant;
Instant now = Instant.now();
now.toEpochMilli(); // long milliseconds, e.g. 1783033290563
now.getNano(); // int nanoseconds within the current second
new java.util.Date().getTime(); // legacy: also epoch milliseconds, nothing finer
// Comparing a ms value to a Unix SECOND is off by 1000x.Java examples use java.time (JSR-310) — see the Java page for the legacy java.util.Date minefield.
import kotlin.time.Clock
val now = Clock.System.now()
now.toEpochMilliseconds() // Long milliseconds, e.g. 1783033290563
now.nanosecondsOfSecond // Int nanoseconds within the current second
java.util.Date().time // legacy: also epoch milliseconds, nothing finer
// Comparing a ms value to a Unix SECOND is off by 1000x.Kotlin examples use kotlinx-datetime, JetBrains’ library — separate from the stdlib.
import Foundation
Date().timeIntervalSince1970 // Double seconds, e.g. 1783033423.42867
// A Double holds ~microsecond precision at 2026 epoch values — and mixing
// it with ms or ns integers is off by 1000x per unit step.Swift examples use Foundation’s Calendar/DateComponents — see the Swift page.
import (
"fmt"
"time"
)
now := time.Now()
fmt.Println(now.Unix()) // seconds, e.g. 1783033290
fmt.Println(now.UnixMilli()) // milliseconds, e.g. 1783033290563
fmt.Println(now.UnixNano()) // nanoseconds, e.g. 1783033290563123456
// Comparing a ms value to a Unix SECOND is off by 1000x.Go examples use the standard library’s time package — see the Go page.
use jiff::Timestamp;
let now = Timestamp::now();
println!("{}", now.as_millisecond()); // i64 milliseconds, e.g. 1783033290563
println!("{}", now.subsec_nanosecond()); // i32 nanoseconds within the second
// std::time::SystemTime spans the same epoch as (seconds, nanos) — comparing
// a ms value to a Unix SECOND is off by 1000x.Rust examples use jiff, a third-party crate — Rust’s std has no calendar types at all.
#include <stdio.h>
#include <time.h>
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
printf("%lld.%09ld\n", (long long)ts.tv_sec, ts.tv_nsec); // seconds + nanoseconds, e.g. 1783143908.090800295
printf("%lld\n", (long long)time(NULL)); // whole seconds only, e.g. 1783143908
// CLOCK_MONOTONIC (not CLOCK_REALTIME) is the clock for measuring elapsed time.
// Comparing a seconds value to a nanoseconds value is off by 1,000,000,000x.C examples use <time.h> — see the C page. Standard C has no zone-aware or date-only type; the zone is a process-global set by TZ.
#include <chrono>
#include <iostream>
using namespace std::chrono;
auto now = system_clock::now().time_since_epoch();
std::cout << duration_cast<milliseconds>(now).count() << "\n"; // milliseconds since epoch
std::cout << duration_cast<nanoseconds>(now).count() << "\n"; // nanoseconds since epoch
// steady_clock (not system_clock) is the monotonic clock for elapsed time.
// Comparing a ms value to a Unix SECOND is off by 1000x.C++ examples use std::chrono (C++20) — see the C++ page. The pre-C++20 world lived on C’s <time.h>.
Precision refers to how meaningful those digits actually are — a nanosecond-resolution value read from a low-frequency timer has high resolution but low precision. Resolution is a property of the type; precision is a property of the measurement.
Mismatches cause bugs
The most common class of bug: a value is stored in a coarser-grained system (e.g. a Unix second) and later compared to a finer-grained value (e.g. a JavaScript millisecond timestamp). The comparison fails or produces unexpected results because the finer value has sub-second information that the stored value lost.
Pitfall: Storing a timestamp as a Unix second integer, then comparing it to Date.now() (milliseconds since epoch) without dividing by 1000. The values differ by three orders of magnitude and the comparison is silently wrong.
See also: Unix time and Instant vs civil time.
Go deeper: the Unix epoch, integer overflow, and database precision
The Unix epoch is 1970-01-01T00:00:00Z. A 32-bit signed Unix timestamp wraps at 2147483647 — which is 2038-01-19T03:14:07Z, the so-called “Year 2038 problem”. Systems still using 32-bit time_t will break on that date. 64-bit timestamps extend the range to approximately 292 billion years in either direction from the epoch.
PostgreSQL’s timestamp and timestamptz types store values as microseconds since 2000-01-01, which differs from the Unix epoch. When using PostgreSQL’s extract(epoch from ...), the result is a floating-point number of seconds since the Unix epoch; floating-point representation loses sub-microsecond precision for recent dates (the double’s resolution is roughly ±0.2–0.4 µs for dates in the 2000s).
MySQL’s DATETIME type has 1-second resolution by default; DATETIME(6) adds up to microsecond precision. SQLite stores timestamps as text or real numbers — see MySQL & SQLite.
For the Unix epoch and how systems count time at the second level, see Unix time and Instant vs civil time.