Unix Timestamp Guide: Epoch Time Explained for Developers

By Taylor Feb 22, 2026 14 min read

Every time you call Date.now() in JavaScript, time.time() in Python, or System.currentTimeMillis() in Java, you are working with Unix timestamps. This deceptively simple concept, counting seconds since a fixed point in time, underlies virtually every time-related operation in computing: database records, API responses, log files, cron jobs, JWT expiration, and file modification dates.

Yet timestamps are a consistent source of bugs. Timezone mismatches, seconds vs. milliseconds confusion, and the looming Y2K38 problem have caused countless production incidents. This guide explains exactly how Unix timestamps work, how to convert between formats in every major language, and how to avoid the pitfalls that trip up even experienced developers.

Table of Contents
  1. What Is a Unix Timestamp?
  2. Why Timestamps Exist
  3. Converting Between Formats
  4. Timestamps in Every Language
  5. The Timezone Problem
  6. Seconds vs. Milliseconds vs. Microseconds
  7. The Year 2038 Problem (Y2K38)
  8. Common Gotchas
  9. Best Practices

1. What Is a Unix Timestamp?

A Unix timestamp (also called epoch time, POSIX time, or Unix time) is the number of seconds that have elapsed since January 1, 1970, 00:00:00 UTC. This date is called the "Unix epoch" or simply "the epoch."

For example, the timestamp 1740268800 represents February 23, 2025, 00:00:00 UTC. The timestamp 0 represents the epoch itself: midnight on January 1, 1970. Negative timestamps represent dates before the epoch: -86400 is December 31, 1969.

TimestampHuman-Readable Date (UTC)
0January 1, 1970 00:00:00
86400January 2, 1970 00:00:00
946684800January 1, 2000 00:00:00
1000000000September 9, 2001 01:46:40
1609459200January 1, 2021 00:00:00
1769472000January 1, 2026 00:00:00
2000000000May 18, 2033 03:33:20
2147483647January 19, 2038 03:14:07 (Y2K38 limit)

The key insight is that a Unix timestamp is always in UTC. It does not have a timezone. The timestamp 1769472000 means exactly the same thing regardless of whether you are in New York, Tokyo, or London. The timezone only matters when you display the timestamp as a human-readable date.

2. Why Timestamps Exist

Storing dates as a single integer has significant advantages over storing them as formatted strings like "February 23, 2025":

3. Converting Between Formats

Timestamp to Human-Readable Date

The most common conversion. Given a timestamp, produce a date string that humans can read. The key decision is which timezone to display in.

// JavaScript: timestamp to readable date
const timestamp = 1769472000;
const date = new Date(timestamp * 1000); // JS uses milliseconds

console.log(date.toUTCString());
// "Wed, 01 Jan 2026 00:00:00 GMT"

console.log(date.toISOString());
// "2026-01-01T00:00:00.000Z"

console.log(date.toLocaleDateString('en-US', {
    weekday: 'long', year: 'numeric',
    month: 'long', day: 'numeric',
    timeZone: 'America/New_York'
}));
// "Wednesday, December 31, 2025" (EST is UTC-5)

Date String to Timestamp

The reverse conversion. Given a human-readable date, produce a Unix timestamp.

// JavaScript: date string to timestamp
const dateStr = '2026-01-01T00:00:00Z';
const timestamp = Math.floor(new Date(dateStr).getTime() / 1000);
console.log(timestamp); // 1769472000

// Be careful: without the Z, JavaScript may interpret
// the string in local time, not UTC!
const ambiguous = new Date('2026-01-01T00:00:00');  // Local time!
const explicit  = new Date('2026-01-01T00:00:00Z'); // UTC!

Convert Timestamps Instantly

Timestamp Forge converts between Unix timestamps, ISO 8601, and human-readable dates in real time. No signup required.

Open Timestamp Forge

4. Timestamps in Every Language

JavaScript

// Current timestamp (seconds)
const now = Math.floor(Date.now() / 1000);

// Current timestamp (milliseconds) - native JS precision
const nowMs = Date.now();

// Timestamp to Date object
const date = new Date(timestamp * 1000);

// Date to timestamp
const ts = Math.floor(date.getTime() / 1000);

Python

import time
from datetime import datetime, timezone

# Current timestamp (float with subsecond precision)
now = time.time()  # e.g., 1769472000.123456

# Current timestamp (integer)
now_int = int(time.time())

# Timestamp to datetime (UTC)
dt = datetime.fromtimestamp(1769472000, tz=timezone.utc)
# datetime(2026, 1, 1, 0, 0, tzinfo=timezone.utc)

# Datetime to timestamp
ts = int(dt.timestamp())

# Formatted string
formatted = dt.strftime('%Y-%m-%d %H:%M:%S UTC')
# '2026-01-01 00:00:00 UTC'

PHP

// Current timestamp
$now = time();

// Timestamp to date string
$date = date('Y-m-d H:i:s', 1769472000);
// '2026-01-01 00:00:00'

// Date string to timestamp
$ts = strtotime('2026-01-01 00:00:00 UTC');

// DateTime object
$dt = new DateTime('@1769472000');
$dt->setTimezone(new DateTimeZone('UTC'));

Java

import java.time.Instant;
import java.time.ZoneOffset;
import java.time.LocalDateTime;

// Current timestamp (seconds)
long now = Instant.now().getEpochSecond();

// Timestamp to Instant
Instant instant = Instant.ofEpochSecond(1769472000L);

// Instant to LocalDateTime in UTC
LocalDateTime dt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);

// LocalDateTime to timestamp
long ts = dt.toEpochSecond(ZoneOffset.UTC);

Go

package main

import (
    "fmt"
    "time"
)

func main() {
    // Current timestamp
    now := time.Now().Unix()

    // Timestamp to Time
    t := time.Unix(1769472000, 0).UTC()
    fmt.Println(t) // 2026-01-01 00:00:00 +0000 UTC

    // Time to timestamp
    ts := t.Unix()

    // Formatted string
    formatted := t.Format("2006-01-02 15:04:05")
}

Bash / Command Line

# Current timestamp
date +%s

# Timestamp to human readable (GNU date)
date -d @1769472000
# Thu Jan  1 00:00:00 UTC 2026

# Timestamp to human readable (macOS date)
date -r 1769472000

# Human readable to timestamp (GNU date)
date -d "2026-01-01 00:00:00 UTC" +%s

SQL

-- PostgreSQL
SELECT to_timestamp(1769472000);
SELECT EXTRACT(EPOCH FROM NOW());
SELECT EXTRACT(EPOCH FROM TIMESTAMP '2026-01-01 00:00:00 UTC');

-- MySQL
SELECT FROM_UNIXTIME(1769472000);
SELECT UNIX_TIMESTAMP();
SELECT UNIX_TIMESTAMP('2026-01-01 00:00:00');

-- SQLite
SELECT datetime(1769472000, 'unixepoch');
SELECT strftime('%s', 'now');
SELECT strftime('%s', '2026-01-01 00:00:00');

5. The Timezone Problem

Timezones are the single greatest source of timestamp-related bugs. The root cause is that a Unix timestamp is always UTC, but humans think in local time. Every conversion between the two is an opportunity for error.

The Offset Trap

UTC offsets change throughout the year due to Daylight Saving Time (DST). New York is UTC-5 in winter and UTC-4 in summer. This means you cannot simply subtract 5 hours from UTC to get New York time, because sometimes you need to subtract 4. The only reliable way to handle this is using timezone databases (like IANA/Olson tz database) that track historical and future DST rules.

// WRONG: hard-coding UTC offset
const nyTime = new Date(timestamp * 1000 - 5 * 3600 * 1000);

// RIGHT: using timezone-aware formatting
const nyTime = new Date(timestamp * 1000).toLocaleString('en-US', {
    timeZone: 'America/New_York'
});

Store UTC, Display Local

The golden rule of timestamp handling: always store timestamps in UTC (or as Unix timestamps, which are inherently UTC), and convert to the user's local timezone only at display time. Never store local times in your database.

The ISO 8601 Standard

When you need to exchange timestamps as strings (in APIs, JSON, logs), use ISO 8601 format: 2026-01-01T00:00:00Z. The trailing Z means UTC. You can also specify an offset: 2026-01-01T05:30:00+05:30 (India Standard Time). ISO 8601 is unambiguous, sortable, and universally understood.

6. Seconds vs. Milliseconds vs. Microseconds

Different systems use different units for timestamps, and confusing them is a common source of bugs:

UnitUsed ByExample for 2026-01-01
SecondsUnix/POSIX, Python, PHP, most APIs1769472000
MillisecondsJavaScript, Java, Firestore1769472000000
MicrosecondsPostgreSQL, Python datetime1769472000000000
NanosecondsGo, InfluxDB, Prometheus1769472000000000000

A simple heuristic for identifying the unit: count the digits. 10 digits is seconds. 13 digits is milliseconds. 16 digits is microseconds. 19 digits is nanoseconds.

// Detecting timestamp precision
function detectPrecision(timestamp) {
    const digits = String(Math.abs(timestamp)).length;
    if (digits <= 10) return 'seconds';
    if (digits <= 13) return 'milliseconds';
    if (digits <= 16) return 'microseconds';
    return 'nanoseconds';
}

// Converting to seconds (the universal format)
function toSeconds(timestamp) {
    const digits = String(Math.abs(timestamp)).length;
    if (digits <= 10) return timestamp;
    if (digits <= 13) return Math.floor(timestamp / 1000);
    if (digits <= 16) return Math.floor(timestamp / 1000000);
    return Math.floor(timestamp / 1000000000);
}

7. The Year 2038 Problem (Y2K38)

On January 19, 2038, at 03:14:07 UTC, Unix timestamps stored as signed 32-bit integers will overflow. The maximum value of a signed 32-bit integer is 2,147,483,647, which corresponds to that exact date and time. One second later, the counter wraps around to -2,147,483,648, which the system interprets as December 13, 1901.

This is not a theoretical concern. It has already caused real problems:

The Solution

Use 64-bit timestamps. A signed 64-bit integer can represent dates until approximately 292 billion years from now, which should suffice. Most modern operating systems and programming languages have already migrated:

-- MySQL: TIMESTAMP has Y2K38 problem, DATETIME does not
-- BAD: overflows in 2038
ALTER TABLE events ADD created_at TIMESTAMP;

-- GOOD: safe until year 9999
ALTER TABLE events ADD created_at DATETIME;

-- ALSO GOOD: store as 64-bit integer
ALTER TABLE events ADD created_at BIGINT;

8. Common Gotchas

JavaScript's Millisecond Timestamps

JavaScript's Date object works in milliseconds, but most APIs return timestamps in seconds. Forgetting to multiply or divide by 1000 is the most common timestamp bug in JavaScript.

// BUG: treats seconds as milliseconds
const date = new Date(1769472000);
// Result: January 21, 1970 (wrong!)

// FIX: multiply by 1000
const date = new Date(1769472000 * 1000);
// Result: January 1, 2026 (correct!)

Floating-Point Precision

Python's time.time() returns a float, which can lose precision for timestamps with microsecond components. For precise timing, use time.time_ns() (Python 3.7+) which returns nanoseconds as an integer.

Leap Seconds

Unix time does not count leap seconds. When a leap second occurs, Unix time either repeats a second or skips one. This means Unix timestamps are not perfectly aligned with TAI (International Atomic Time). In practice, this rarely matters unless you are working on time-critical scientific or financial systems.

Date Parsing Without Timezone

Parsing a date string without an explicit timezone is dangerous because the result depends on the system's local timezone, which varies between servers, user devices, and even between browser tabs.

// DANGEROUS: no timezone specified, behavior is implementation-defined
new Date('2026-01-01 00:00:00')

// SAFE: explicit UTC
new Date('2026-01-01T00:00:00Z')

// SAFE: explicit offset
new Date('2026-01-01T00:00:00+00:00')

9. Best Practices

Convert Any Timestamp Format

Timestamp Forge handles seconds, milliseconds, ISO 8601, and human-readable dates. Convert between any format instantly.

Open Timestamp Forge