What is a Unix Timestamp
A Unix timestamp is a single integer representing a moment in time as the number of seconds that have elapsed since the Unix epoch: midnight Coordinated Universal Time (UTC) on January 1, 1970. This seemingly arbitrary reference point was chosen by the original Unix engineers and has become a universal standard for representing time in computing systems.
The genius of the Unix timestamp is its simplicity. A single integer is trivial to store, compare, sort, and transmit. Two timestamps can be compared with a single integer operation, durations are computed by subtraction, and there is no ambiguity about time zones, daylight saving, or calendar quirks. The same timestamp means the same instant everywhere on Earth, regardless of local time conventions.
Despite its simplicity, the Unix timestamp underpins an enormous amount of modern infrastructure. Databases store created-at and updated-at columns as timestamps. Logs are tagged with timestamps for correlation. Caches use timestamps for expiration. APIs exchange timestamps in JSON payloads. Distributed systems use timestamps (often combined with logical clocks) to order events. Anywhere a system needs to record when something happened, a Unix timestamp is likely involved.
- Integer count of seconds since 00:00:00 UTC on January 1, 1970
- Time-zone-independent: same instant, same value everywhere
- Trivial to store, compare, sort, and transmit
- Foundation of logging, caching, scheduling, and event ordering
Understanding Epoch Time
The Unix epoch — midnight UTC on January 1, 1970 — is the zero point of the Unix timestamp system. Every second after that moment increments the timestamp by one; every second before it decrements by one (using negative values). The choice of 1970 was pragmatic: it was roughly when the Unix operating system was being developed, and starting from a recent round date kept early timestamps small and manageable on the hardware of the era.
Epoch-based time representation is not unique to Unix. Many systems use some form of epoch counting: GPS time counts weeks and seconds from January 6, 1980. The NTP (Network Time Protocol) epoch is January 1, 1900. The Julian Day system used in astronomy counts days from January 1, 4713 BC. Each epoch reflects the constraints and conventions of its originating domain, but the Unix epoch is by far the most widely adopted in general-purpose computing.
The original Unix timestamp was stored in a 32-bit signed integer, which can represent values up to 2,147,483,647 — corresponding to January 19, 2038. This is the famous "Y2038 problem" or "Unix millennium bug." Systems still using 32-bit signed integers will overflow on that date, producing undefined behavior. Modern systems using 64-bit integers have a range extending hundreds of billions of years into the future, effectively eliminating the problem. Most modern operating systems, databases, and programming languages have already migrated to 64-bit timestamps.
- Epoch: 00:00:00 UTC, January 1, 1970
- Negative timestamps represent moments before the epoch
- Y2038 problem: 32-bit signed integer overflows on January 19, 2038
- 64-bit timestamps effectively eliminate the overflow concern
Seconds vs Milliseconds
One of the most common sources of bugs with Unix timestamps is the difference between second-based and millisecond-based representations. JavaScript's Date.now() and Date.getTime() return milliseconds since the epoch, while most Unix command-line tools, databases, and APIs use seconds. Mixing the two without conversion produces timestamps that are either 1000x too large or 1000x too small, leading to dates thousands of years in the future or past.
Conventions vary by ecosystem. JavaScript and Java default to milliseconds. Python's time.time() returns seconds (as a float, with fractional seconds), while datetime.now() returns a datetime object. Go's time package uses nanoseconds. Most SQL databases store timestamps in their own formats but expose Unix seconds via functions like UNIX_TIMESTAMP() in MySQL or EXTRACT(EPOCH FROM ...) in PostgreSQL. Always check the documentation before assuming a unit.
When in doubt, normalize at system boundaries. Pick one unit (seconds is the more common convention) and convert immediately at every API boundary, database write, or external call. Logging the unit alongside the value during debugging can save hours of confusion. A timestamp like 1690000000 could mean July 22, 2023 (seconds) or January 20, 1970 (milliseconds) — never assume which without checking the source.
- JavaScript and Java: milliseconds since epoch
- Python, Ruby, PHP, and most databases: seconds since epoch
- Go: nanoseconds since epoch via time.Time
- Always convert explicitly at system boundaries to avoid 1000x bugs
Working with Time Zones
Time zones are the source of more bugs in date and time handling than any other concept. A Unix timestamp itself is always UTC — it represents an absolute instant with no geographic context. Time zones only come into play when converting a timestamp to a human-readable calendar date and time, or when parsing user input into a timestamp.
The golden rule of timestamp handling is: store and transmit timestamps in UTC, convert to local time only at the moment of display. Storing local times in databases is a recipe for bugs, especially in regions that observe daylight saving time, when the same local time can occur twice (during the fall-back transition) or not at all (during the spring-forward transition). UTC has no such ambiguities.
Be aware that time zone rules change. Governments periodically redefine time zone offsets and daylight saving schedules, often with little notice. A timestamp stored as a local time without a time zone identifier becomes ambiguous retroactively: the local rules in effect when it was stored may no longer apply. The IANA Time Zone Database (tzdata) tracks these rules and is updated several times a year; keep your system's copy current. When converting timestamps for display, always use a library that consults tzdata rather than hardcoding offsets.
- Unix timestamps are always UTC — store and transmit in UTC
- Convert to local time only at the moment of display
- Daylight saving transitions create ambiguous or missing local times
- Use the IANA tzdata via a maintained library, not hardcoded offsets
Common Timestamp Pitfalls
Even experienced developers make timestamp mistakes. The most common is assuming that "now" is the same everywhere — it is, in timestamp terms, but client clocks are frequently wrong. Browsers, mobile devices, and IoT sensors may have clocks that drift by seconds or even minutes. Never trust a client-provided timestamp for security-critical operations like token expiration or rate limiting; always use the server's clock.
Another frequent pitfall is computing date differences by subtracting timestamps and dividing by 86400 (seconds per day). This works for raw durations but fails for calendar arithmetic. A "day" in calendar terms is not always 86400 seconds: daylight saving transitions make some days 23 or 25 hours long, and leap seconds occasionally make a minute 61 seconds long. For calendar calculations — "what date is 30 days from now" — use a date library that understands the local calendar rules rather than raw timestamp arithmetic.
Floating-point timestamps introduce their own subtleties. Python's time.time() returns a float, and the fractional part represents sub-second precision. For most uses this is fine, but floating-point comparisons can produce surprising results due to rounding. If you need precise sub-second timing, prefer integer nanoseconds or use a dedicated monotonic clock such as time.monotonic() that is not affected by system clock adjustments.
- Never trust client-provided timestamps for security decisions
- Calendar days are not always 86400 seconds (DST, leap seconds)
- Use date libraries for calendar arithmetic, not raw subtraction
- Prefer monotonic clocks for measuring elapsed time
Best Practices for Time Handling
Robust time handling follows a small set of principles that, once internalized, eliminate the majority of timestamp bugs. First, store and transmit all timestamps in UTC, ideally as Unix timestamps or ISO 8601 strings with explicit UTC designators. Local time is a presentation concern, not a storage concern. Second, use a mature date and time library in every language you work with — never roll your own date arithmetic. Third, treat client clocks as untrusted and use server time for any decision with security or billing implications.
For APIs, prefer ISO 8601 with timezone designators for human-readable payloads, since the format is self-describing and language-agnostic. The format 2024-01-15T14:30:00Z unambiguously identifies a UTC instant, while 2024-01-15T14:30:00-05:00 includes an explicit offset. Avoid ambiguous formats like 01/15/2024 that vary by locale. Include milliseconds or nanoseconds only if your use case requires that precision; otherwise, second precision is usually sufficient and avoids floating-point concerns.
Finally, build a small set of utility functions or middleware that enforces these conventions across your codebase. Centralizing timestamp parsing, formatting, and conversion catches bugs at a single chokepoint rather than scattering them across every feature. Document the conventions clearly for new team members, and add lint rules or tests that flag raw timestamp arithmetic. Time is hard, but consistent practices make it manageable.
- Store and transmit in UTC; convert to local only for display
- Use mature date libraries — never hand-roll date arithmetic
- Prefer ISO 8601 with explicit timezone designators in APIs
- Treat client clocks as untrusted for security and billing decisions
- Centralize timestamp handling in utility functions and review carefully