Understanding Unix Timestamps: A Developer's Guide

CodeKit
timestampdatetimeunix

What is a Unix Timestamp?

A Unix timestamp (also called “epoch time” or “POSIX time”) is the number of seconds that have elapsed since January 1, 1970, 00:00:00 UTC—a moment known as the Unix epoch. It’s the lingua franca of time in computing: a single integer that every programming language, database, and operating system can understand.

Epoch: 1970-01-01 00:00:00 UTC
Now:   1718448000  (June 15, 2025, 12:00:00 UTC)

The beauty of a Unix timestamp is its simplicity. There are no timezones, no daylight saving quirks, no ambiguous date formats—just a number that always goes up. That makes it perfect for storing, comparing, and sorting moments in time.

You can convert between human-readable dates and Unix timestamps with the Timestamp Converter on CodeKit.

Seconds vs Milliseconds

Here’s the first trap that catches every developer: not all timestamps use the same unit. The classic Unix timestamp counts seconds, but many modern systems use milliseconds.

SystemUnitExample (June 15, 2025)
Unix / POSIXSeconds1718448000
JavaScript Date.now()Milliseconds1718448000000
Java System.currentTimeMillis()Milliseconds1718448000000
Python time.time()Seconds (float)1718448000.0
Go time.Now().Unix()Seconds1718448000
MongoDB DateMilliseconds1718448000000

Mix these up and you’ll either set a date in the year 56,000 or in 1970. This bug is so common it has its own name: the “millisecond vs second” error.

// The classic mistake
const ts = 1718448000; // seconds

// Wrong: treats it as milliseconds → January 1970
new Date(ts); // 1970-01-20T20:40:48.000Z

// Correct: multiply by 1000
new Date(ts * 1000); // 2025-06-15T12:00:00.000Z

Rule of thumb: If a timestamp is 10 digits, it’s seconds. If it’s 13 digits, it’s milliseconds. When in doubt, check the documentation of the API you’re consuming.

Working with Timestamps Across Languages

Here’s how to get and convert Unix timestamps in popular languages:

// JavaScript / Node.js
const now = Date.now();              // milliseconds
const seconds = Math.floor(now / 1000);
const date = new Date(seconds * 1000);
console.log(date.toISOString());    // "2025-06-15T12:00:00.000Z"
# Python
import time
from datetime import datetime, timezone

now = time.time()                    # seconds (float)
dt = datetime.fromtimestamp(now, tz=timezone.utc)
print(dt.isoformat())                # "2025-06-15T12:00:00+00:00"

# Convert a datetime back to a timestamp
ts = int(dt.timestamp())
// Go
now := time.Now()
seconds := now.Unix()                 // seconds
millis := now.UnMilli()               // milliseconds
formatted := time.Unix(seconds, 0).UTC().Format(time.RFC3339)
-- PostgreSQL
SELECT EXTRACT(EPOCH FROM NOW());     -- seconds (float)
SELECT TO_TIMESTAMP(1718448000);      -- 2025-06-15 12:00:00+00

Timezone Handling

A Unix timestamp is always UTC. That’s its superpower—there’s no such thing as a “Pacific timezone timestamp.” The timezone only enters the picture when you display the timestamp to a human.

const ts = 1718448000; // June 15, 2025, 12:00 UTC

// Same timestamp, different displays:
new Date(ts * 1000).toLocaleString('en-US', { timeZone: 'America/New_York' });
// "6/15/2025, 8:00:00 AM" (New York, UTC-4)

new Date(ts * 1000).toLocaleString('en-GB', { timeZone: 'Asia/Tokyo' });
// "15/06/2025, 21:00:00" (Tokyo, UTC+9)

The best practice is straightforward:

  1. Store timestamps in UTC (as a Unix epoch integer or an ISO 8601 string with a Z suffix).
  2. Convert to local time only at the presentation layer, closest to the user.
  3. Never store “local time” without an explicit offset—it’s a bug waiting to happen.

The Year 2038 Problem

A 32-bit signed integer can hold values up to 2,147,483,647. That corresponds to January 19, 2038, 03:14:07 UTC—the moment when 32-bit Unix timestamps will overflow and wrap around to a negative number, representing a date in 1901.

This is the Y2038 problem, and it’s the spiritual successor to Y2K. Systems still using 32-bit integers for time will break:

  • Embedded devices and IoT hardware with 32-bit CPUs
  • Legacy C/C++ codebases using time_t
  • Old binary file formats that store timestamps as 32-bit integers
  • Database columns defined as INT instead of BIGINT
// 32-bit time_t overflow
#include <time.h>
time_t future = 2147483647;
printf("%s", ctime(&future));
// "Tue Jan 19 03:14:07 2038" on 64-bit systems
// Could wrap to 1901 on 32-bit systems

The fix: Use 64-bit integers. A 64-bit Unix timestamp won’t overflow for 292 billion years. Modern languages (JavaScript, Python, Java, Go) already use 64-bit numbers internally, so most application code is safe. The risk lives in infrastructure, embedded systems, and legacy databases.

If you’re designing a schema today, always use BIGINT for timestamp columns, never INT.

Common Pitfalls

1. Forgetting leap seconds

Unix timestamps pretend leap seconds don’t exist. Every day is defined as exactly 86,400 seconds, which means Unix time can drift slightly from true UTC. For 99.9% of applications this doesn’t matter, but high-precision systems (financial trading, astronomy) need to account for it.

2. Negative timestamps

Dates before 1970 are represented as negative timestamps. Date.parse("1969-12-31") returns -86400000. This works fine in most languages, but some legacy systems choke on negative values.

3. Floating-point precision

Python’s time.time() returns a float. For sub-second precision this is fine, but comparing floats for equality is risky. Prefer integer milliseconds when you need exact comparisons.

4. Assuming timestamps are unique

Two events that happen in the same millisecond get the same timestamp. If you’re using a timestamp as a unique identifier, you’ll get collisions. Use a UUID or a monotonic counter instead.

Best Practices

  • Store as integers, not strings. 1718448000 is cheaper to index and compare than "2025-06-15T12:00:00Z".
  • Always include the timezone when displaying. ISO 8601 (2025-06-15T12:00:00Z) is unambiguous; 2025-06-15 12:00:00 is not.
  • Use a library for date math. Adding “one month” to a timestamp is harder than it looks (months have different lengths). Libraries like date-fns, Luxon, or Python’s pendulum handle edge cases correctly.
  • Test around boundaries: midnight UTC, daylight saving transitions, and the epoch itself.

Conclusion

Unix timestamps are the simplest, most portable way to represent a moment in time. They sidestep timezones entirely and make sorting and comparison trivial. Just remember the two golden rules: know your unit (seconds vs milliseconds) and always store in UTC.

When you need to quickly convert a timestamp into a readable date—or the other way around—use the Timestamp Converter on CodeKit. It handles seconds, milliseconds, and ISO 8601, all in your browser.