Unix Epoch Timestamp: The Complete Developer's Reference
Table of Contents
- 1. What Is Unix Epoch Time and Why It Matters
- 2. The Y2K38 Problem: 32-Bit Timestamp Overflow
- 3. Getting the Current Epoch Time in 8 Languages
- 4. Converting Epoch to Human-Readable Dates and Back
- 5. Epoch Time in Databases
- 6. Epoch Time in APIs
- 7. Milliseconds vs Seconds: A Common Source of Bugs
- 8. Time Zones and Epoch Time
- 9. Epoch Values Every Developer Should Know
- 10. Debugging Timestamp Issues
Convert Epoch Timestamps Instantly
Need to convert a Unix timestamp right now? Use our Epoch Converter to translate between Unix timestamps and human-readable dates in real time, right in your browser.
1. What Is Unix Epoch Time and Why It Matters
Unix epoch time (also called POSIX time, Unix time, or simply "epoch") is a system for tracking time as a single number: the count of seconds that have elapsed since January 1, 1970, at 00:00:00 UTC. That moment in time is known as the Unix epoch, and every second since then increments this counter by one. Right now, as you read this in 2026, the epoch value is somewhere around 1.77 billion.
This deceptively simple concept underpins virtually every piece of software you interact with. When your browser stores a cookie expiration, when a database records when a row was created, when a log file marks when an error occurred, when a message queue orders events, they are almost always using epoch timestamps under the hood.
Why January 1, 1970?
The choice of epoch date is historical. Early Unix systems at Bell Labs in the late 1960s needed a reference point for time. The original Unix time used January 1, 1971, as the epoch and stored time as a 32-bit integer counting in sixtieths of a second. This was quickly changed: the resolution was reduced to whole seconds (making the 32-bit range span many more years) and the epoch was shifted to January 1, 1970, a conveniently round date close to Unix's birth. This decision, made over half a century ago, still defines how billions of devices track time today.
Why Epoch Time Matters
Epoch timestamps solve several fundamental problems in computing:
- Language-agnostic: The number
1707609600means exactly the same thing whether you are working in JavaScript, Python, Go, or C. There is no ambiguity about date format (MM/DD/YYYYvsDD/MM/YYYY), no locale-dependent month names, no parsing errors. - Time zone neutral: Epoch time is always UTC. You never wonder whether
1707609600is Eastern Time or Pacific Time. It is one absolute point in time, and you convert to local time only at display time. - Arithmetic-friendly: Want to know how many seconds between two events? Subtract one epoch from another. Want to add 24 hours? Add
86400. Need to check if an event happened within the last hour? Comparenow - event < 3600. No date parsing libraries required. - Sort-friendly: Epoch timestamps are integers. Sorting events chronologically is just sorting numbers, which every database and programming language does natively and efficiently.
- Compact storage: A 32-bit integer takes 4 bytes. A 64-bit integer takes 8 bytes. The equivalent ISO 8601 string
"2026-02-11T12:00:00Z"takes 20 bytes. In systems processing billions of events, this difference adds up.
These properties make epoch timestamps the de facto standard for storing and transmitting time in software systems. The human-readable format (February 11, 2026, 3:00 PM EST) is for display only. Under the surface, everything is epoch.
2. The Y2K38 Problem: 32-Bit Timestamp Overflow
If you are old enough to remember the Y2K scare at the turn of the millennium, the Y2K38 problem may feel like deja vu. On January 19, 2038, at 03:14:07 UTC, the Unix epoch timestamp will reach 2,147,483,647, which is the maximum value a signed 32-bit integer can hold. One second later, systems still using 32-bit timestamps will overflow, wrapping around to -2,147,483,648, which corresponds to December 13, 1901.
Critical Date: January 19, 2038, 03:14:07 UTC. At epoch value 2147483647, signed 32-bit integers overflow. Systems using time_t as a 32-bit int will interpret the next second as a date in 1901.
Why 32-Bit?
When Unix was designed in the early 1970s, 32-bit integers were the natural word size. A signed 32-bit integer can represent values from -2,147,483,648 to 2,147,483,647. Starting from January 1, 1970, that gives a range of about 68 years in each direction: back to 1901 and forward to 2038. In 1970, a 68-year horizon felt impossibly distant. No one expected software written that year to still be running in 2038. And yet here we are.
What Systems Are Affected?
- Embedded systems: IoT devices, industrial controllers, automotive computers, and medical devices often use 32-bit processors and may run for decades without firmware updates.
- Legacy databases: Older MySQL versions stored
TIMESTAMPas a 32-bit integer. Any database column using this type will break. - File formats: Some binary file formats hardcode timestamps as 32-bit integers. Tar archives, for example, use a fixed-width octal representation that limits dates.
- C programs using
time_t: On 32-bit systems,time_tis often a 32-bit signed integer. Code compiled for these targets will fail.
The Fix: 64-Bit Timestamps
The solution is straightforward: use 64-bit integers for timestamps. A signed 64-bit integer can represent epoch values up to 9,223,372,036,854,775,807, which corresponds to a date approximately 292 billion years in the future. That should be sufficient.
Most modern systems have already migrated. Linux moved to a 64-bit time_t on all architectures. Modern versions of MySQL, PostgreSQL, and most databases use 64-bit timestamps. JavaScript, Python, Java, and Go all use 64-bit (or larger) representations internally. The risk lies in legacy systems, embedded devices, and binary file formats that were never updated.
If you maintain any system that stores or processes timestamps, audit your code now. Search for 32-bit timestamp usage: int or unsigned int storing epoch values in C/C++, INT columns storing timestamps in databases, and any serialization format that writes timestamps as 4-byte values.
3. Getting the Current Epoch Time in 8 Languages
Here is how to get the current Unix epoch timestamp in every major programming language. Pay close attention to whether each returns seconds or milliseconds, as this is the single most common source of timestamp bugs.
JavaScript
// Milliseconds since epoch (JavaScript's default)
const msEpoch = Date.now();
console.log(msEpoch); // 1739232000000
// Seconds since epoch
const secEpoch = Math.floor(Date.now() / 1000);
console.log(secEpoch); // 1739232000
// From a Date object
const date = new Date();
const epoch = Math.floor(date.getTime() / 1000);
// High-resolution timestamp (for performance measurement)
const hrTime = performance.now(); // milliseconds with microsecond precision
Watch out: JavaScript's Date.now() returns milliseconds, not seconds. Forgetting to divide by 1000 is the most common JavaScript timestamp bug. If your epoch value has 13 digits, it is milliseconds. If it has 10, it is seconds.
Python
import time
from datetime import datetime, timezone
# Seconds since epoch (float with sub-second precision)
epoch = time.time()
print(epoch) # 1739232000.123456
# Integer seconds
epoch_int = int(time.time())
print(epoch_int) # 1739232000
# Using datetime
epoch_dt = int(datetime.now(timezone.utc).timestamp())
# Nanoseconds (Python 3.7+)
epoch_ns = time.time_ns()
print(epoch_ns) # 1739232000123456789
Java
import java.time.Instant;
// Seconds since epoch
long epochSeconds = Instant.now().getEpochSecond();
System.out.println(epochSeconds); // 1739232000
// Milliseconds since epoch
long epochMillis = System.currentTimeMillis();
System.out.println(epochMillis); // 1739232000000
// Using Instant (preferred in modern Java)
Instant now = Instant.now();
long seconds = now.getEpochSecond();
int nanos = now.getNano();
Go
package main
import (
"fmt"
"time"
)
func main() {
// Seconds since epoch
epoch := time.Now().Unix()
fmt.Println(epoch) // 1739232000
// Milliseconds since epoch
epochMs := time.Now().UnixMilli()
fmt.Println(epochMs) // 1739232000000
// Nanoseconds since epoch
epochNs := time.Now().UnixNano()
fmt.Println(epochNs) // 1739232000000000000
}
Ruby
# Seconds since epoch (integer)
epoch = Time.now.to_i
puts epoch # 1739232000
# Seconds with fractional precision
epoch_f = Time.now.to_f
puts epoch_f # 1739232000.123456
# Milliseconds
epoch_ms = (Time.now.to_f * 1000).to_i
puts epoch_ms # 1739232000123
PHP
<?php
// Seconds since epoch
$epoch = time();
echo $epoch; // 1739232000
// Milliseconds (PHP 8.0+)
$epochMs = hrtime(true) / 1e6;
// With microtime
$epochMicro = microtime(true);
echo $epochMicro; // 1739232000.123456
// Using DateTime
$dt = new DateTime();
$epoch = $dt->getTimestamp();
?>
Bash
# Seconds since epoch
date +%s
# 1739232000
# Nanoseconds since epoch (GNU date)
date +%s%N
# 1739232000123456789
# Milliseconds since epoch
echo $(($(date +%s%N) / 1000000))
# macOS (BSD date)
date +%s
C
#include <stdio.h>
#include <time.h>
int main() {
// Seconds since epoch
time_t epoch = time(NULL);
printf("Epoch: %ld\n", (long)epoch);
// With higher precision (POSIX)
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
printf("Seconds: %ld\n", ts.tv_sec);
printf("Nanoseconds: %ld\n", ts.tv_nsec);
return 0;
}
Verify Your Timestamps
Not sure if your epoch value is correct? Paste it into our Epoch Converter to see the human-readable date instantly. It handles both seconds and milliseconds automatically.
4. Converting Epoch to Human-Readable Dates and Back
The most common operation with epoch timestamps is converting between the numeric value and a human-readable date string. Here are the patterns you will use daily.
Epoch to Date String
// JavaScript
const date = new Date(1739232000 * 1000); // multiply by 1000 for ms
console.log(date.toISOString()); // "2025-02-11T00:00:00.000Z"
console.log(date.toLocaleString()); // locale-dependent output
// Python
from datetime import datetime, timezone
dt = datetime.fromtimestamp(1739232000, tz=timezone.utc)
print(dt.isoformat()) # "2025-02-11T00:00:00+00:00"
print(dt.strftime("%Y-%m-%d %H:%M:%S")) # "2025-02-11 00:00:00"
// Go
t := time.Unix(1739232000, 0)
fmt.Println(t.UTC().Format(time.RFC3339)) // "2025-02-11T00:00:00Z"
// PHP
echo date('Y-m-d H:i:s', 1739232000); // "2025-02-11 00:00:00"
// Bash
date -d @1739232000 # GNU
date -r 1739232000 # macOS/BSD
Date String to Epoch
// JavaScript
const epoch = Math.floor(new Date("2026-02-11T00:00:00Z").getTime() / 1000);
console.log(epoch); // 1770681600
// Python
from datetime import datetime, timezone
dt = datetime.strptime("2026-02-11 00:00:00", "%Y-%m-%d %H:%M:%S")
dt = dt.replace(tzinfo=timezone.utc)
epoch = int(dt.timestamp())
print(epoch) # 1770681600
// Go
t, _ := time.Parse(time.RFC3339, "2026-02-11T00:00:00Z")
fmt.Println(t.Unix()) // 1770681600
// PHP
$epoch = strtotime("2026-02-11 00:00:00 UTC");
echo $epoch; // 1770681600
// Bash
date -d "2026-02-11 00:00:00 UTC" +%s # 1770681600
Converting Between Time Zones
Remember: epoch time is always UTC. Conversion to a local time zone happens only when displaying to a user.
// JavaScript: Display epoch in different time zones
const epoch = 1770681600;
const date = new Date(epoch * 1000);
console.log(date.toLocaleString('en-US', { timeZone: 'America/New_York' }));
// "2/10/2026, 7:00:00 PM"
console.log(date.toLocaleString('en-US', { timeZone: 'Asia/Tokyo' }));
// "2/11/2026, 9:00:00 AM"
console.log(date.toLocaleString('en-US', { timeZone: 'Europe/London' }));
// "2/11/2026, 12:00:00 AM"
// Python: Display in different time zones
from datetime import datetime, timezone
from zoneinfo import ZoneInfo # Python 3.9+
epoch = 1770681600
dt_utc = datetime.fromtimestamp(epoch, tz=timezone.utc)
dt_ny = dt_utc.astimezone(ZoneInfo("America/New_York"))
print(dt_ny) # 2026-02-10 19:00:00-05:00
dt_tokyo = dt_utc.astimezone(ZoneInfo("Asia/Tokyo"))
print(dt_tokyo) # 2026-02-11 09:00:00+09:00
Explore Timestamp Formats
Working with multiple timestamp formats? Our Timestamp Tool lets you convert between Unix timestamps, ISO 8601 strings, and human-readable dates side by side.
5. Epoch Time in Databases
Every major database handles epoch timestamps, but they each have different conventions, functions, and pitfalls. Choosing the right column type and knowing the conversion functions for your database can save you hours of debugging.
PostgreSQL
PostgreSQL has rich timestamp support and makes epoch conversions straightforward:
-- Current epoch time
SELECT EXTRACT(EPOCH FROM NOW());
-- 1770681600.123456
-- Epoch to timestamp
SELECT TO_TIMESTAMP(1770681600);
-- 2026-02-11 00:00:00+00
-- Timestamp to epoch
SELECT EXTRACT(EPOCH FROM TIMESTAMP '2026-02-11 00:00:00 UTC');
-- 1770681600
-- Store as integer column (seconds)
CREATE TABLE events (
id SERIAL PRIMARY KEY,
name TEXT,
created_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT
);
-- Or use native TIMESTAMPTZ (recommended)
CREATE TABLE events_v2 (
id SERIAL PRIMARY KEY,
name TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Query with epoch input
SELECT * FROM events_v2
WHERE created_at > TO_TIMESTAMP(1770681600);
Recommendation: Use TIMESTAMPTZ for storage and convert to/from epoch at the application layer. PostgreSQL stores TIMESTAMPTZ as a 64-bit integer internally (microseconds since 2000-01-01), so you get the storage efficiency of epoch with the query convenience of native timestamps.
MySQL
-- Current epoch time
SELECT UNIX_TIMESTAMP();
-- 1770681600
-- Epoch to datetime
SELECT FROM_UNIXTIME(1770681600);
-- '2026-02-11 00:00:00'
-- Datetime to epoch
SELECT UNIX_TIMESTAMP('2026-02-11 00:00:00');
-- 1770681600
-- TIMESTAMP column (auto-converts, but historically 32-bit limited)
CREATE TABLE events (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- BIGINT column for epoch storage (no Y2038 risk)
CREATE TABLE events_v2 (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
created_at BIGINT DEFAULT (UNIX_TIMESTAMP())
);
-- DATETIME column (no Y2038 risk, range to 9999-12-31)
CREATE TABLE events_v3 (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
MySQL Y2038 Warning: The TIMESTAMP type in MySQL historically used a 32-bit integer, with a range of 1970-01-01 00:00:01 to 2038-01-19 03:14:07. MySQL 8.0.28+ extended this range, but older versions will break. If you are on an older MySQL version, use DATETIME or BIGINT instead.
SQLite
-- SQLite has no native date type; timestamps are stored as text, integer, or real
-- Current epoch
SELECT strftime('%s', 'now');
-- '1770681600'
-- Epoch to readable date
SELECT datetime(1770681600, 'unixepoch');
-- '2026-02-11 00:00:00'
-- Epoch to local time
SELECT datetime(1770681600, 'unixepoch', 'localtime');
-- '2026-02-10 19:00:00' (depends on server TZ)
-- Readable date to epoch
SELECT strftime('%s', '2026-02-11 00:00:00');
-- '1770681600'
-- Create table with integer epoch
CREATE TABLE events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
created_at INTEGER DEFAULT (strftime('%s', 'now'))
);
MongoDB
// MongoDB stores dates as 64-bit millisecond epoch internally
// Current date (stored as ms epoch internally)
db.events.insertOne({
name: "deploy",
createdAt: new Date()
});
// From epoch seconds
db.events.insertOne({
name: "deploy",
createdAt: new Date(1770681600 * 1000) // multiply by 1000!
});
// Query by epoch range
db.events.find({
createdAt: {
$gte: new Date(1770681600 * 1000),
$lt: new Date(1770768000 * 1000)
}
});
// Get epoch from a document
const doc = db.events.findOne();
const epochMs = doc.createdAt.getTime(); // milliseconds
const epochSec = Math.floor(doc.createdAt.getTime() / 1000); // seconds
// Aggregation: extract epoch
db.events.aggregate([
{
$project: {
name: 1,
epochMs: { $toLong: "$createdAt" }
}
}
]);
6. Epoch Time in APIs
Timestamps are a fundamental part of API design. The choice between epoch integers and ISO 8601 strings affects every consumer of your API. Here are the conventions and trade-offs.
REST API Conventions
There is no single standard, but two dominant patterns exist:
// Pattern 1: Epoch seconds (used by Stripe, Slack, Twitter/X)
{
"id": "evt_123",
"created": 1770681600,
"amount": 2000
}
// Pattern 2: ISO 8601 strings (used by GitHub, Google, Microsoft)
{
"id": "evt_123",
"created_at": "2026-02-11T00:00:00Z",
"amount": 2000
}
// Pattern 3: Epoch milliseconds (used by Elasticsearch, Discord)
{
"id": "evt_123",
"timestamp": 1770681600000,
"content": "Hello"
}
The choice depends on your audience:
- Epoch seconds are compact, unambiguous, and easy for machines to process. Use this if your API consumers are primarily backend services.
- ISO 8601 strings are human-readable and self-documenting. Use this if developers will frequently read raw API responses during debugging.
- Epoch milliseconds provide sub-second precision without floating-point issues. Use this if you need millisecond accuracy (event streaming, real-time systems).
Whichever you choose, document it clearly and be consistent. Mixing formats within the same API is the fastest way to create confusion.
GraphQL Conventions
GraphQL does not have a built-in DateTime scalar. Most implementations define a custom scalar:
# Schema definition
scalar DateTime # ISO 8601 string
scalar Timestamp # Epoch integer
type Event {
id: ID!
name: String!
createdAt: DateTime! # "2026-02-11T00:00:00Z"
# or
createdAt: Timestamp! # 1770681600
}
# Common custom scalar implementation (JavaScript)
const { GraphQLScalarType } = require('graphql');
const DateTimeScalar = new GraphQLScalarType({
name: 'DateTime',
description: 'ISO 8601 date-time string',
serialize(value) {
return value instanceof Date ? value.toISOString() : value;
},
parseValue(value) {
return new Date(value);
}
});
The GraphQL community generally favors ISO 8601 strings because they are self-descriptive and work naturally with GraphQL's introspection system. Libraries like graphql-scalars provide pre-built DateTime, Timestamp, and DateTimeISO scalars.
API Best Practices for Timestamps
- Always use UTC: Never send local times in API responses. Let the client convert to local time.
- Document the unit: If using epoch, explicitly state whether values are seconds, milliseconds, or microseconds.
- Use consistent naming:
created_at,updated_at,deleted_atfor ISO strings;created,updatedfor epoch integers. - Include timezone indicator: If using ISO 8601, always include the
Zsuffix or offset (+00:00). Never send2026-02-11T00:00:00without a timezone. - Accept flexible input: Let clients send timestamps in multiple formats and normalize server-side.
Debug JSON API Responses
Inspecting timestamps in API responses? Our JSON Formatter pretty-prints JSON so you can quickly spot and compare timestamp fields across objects.
7. Milliseconds vs Seconds: A Common Source of Bugs
The single most frequent epoch timestamp bug is confusing seconds and milliseconds. This mistake has caused outages at companies of every size, because the error is not always obvious: a millisecond epoch value treated as seconds produces a date millions of years in the future, while a seconds value treated as milliseconds produces a date in January 1970.
The Quick Test
| Unit | Digits (2020-2030) | Example Value | Languages That Default to This |
|---|---|---|---|
| Seconds | 10 digits | 1770681600 |
Python, PHP, Ruby, Go, C, Bash |
| Milliseconds | 13 digits | 1770681600000 |
JavaScript, Java, MongoDB |
| Microseconds | 16 digits | 1770681600000000 |
PostgreSQL (internal), Python time.time_ns() |
| Nanoseconds | 19 digits | 1770681600000000000 |
Go (UnixNano), Python time.time_ns() |
Rule of thumb: Count the digits. 10 digits = seconds. 13 digits = milliseconds. This simple check can prevent hours of debugging.
Common Bug Scenarios
// BUG: JavaScript epoch (ms) sent to Python backend expecting seconds
// Frontend sends: 1770681600000 (ms)
// Backend interprets as seconds: year 58,104
// BUG: Python epoch (seconds) used in JavaScript Date constructor
const date = new Date(1770681600); // Treats as ms!
console.log(date); // 1970-01-21T11:51:21.600Z -- WRONG
// CORRECT: Multiply by 1000
const date = new Date(1770681600 * 1000);
console.log(date); // 2026-02-11T00:00:00.000Z -- RIGHT
// BUG: MongoDB date stored from Python timestamp
// Python: time.time() returns 1770681600.0 (seconds)
// MongoDB expects milliseconds for new Date()
db.events.insertOne({ ts: new Date(1770681600) }); // WRONG: Jan 21, 1970
db.events.insertOne({ ts: new Date(1770681600 * 1000) }); // RIGHT: Feb 11, 2026
Defensive Coding
// Utility function: auto-detect and normalize to milliseconds
function normalizeToMs(timestamp) {
if (timestamp < 1e12) {
// 10 digits or fewer: seconds
return timestamp * 1000;
} else if (timestamp < 1e15) {
// 13 digits: already milliseconds
return timestamp;
} else if (timestamp < 1e18) {
// 16 digits: microseconds
return Math.floor(timestamp / 1000);
} else {
// 19 digits: nanoseconds
return Math.floor(timestamp / 1e6);
}
}
// Python equivalent
def normalize_to_seconds(timestamp):
if timestamp < 1e12:
return timestamp # already seconds
elif timestamp < 1e15:
return timestamp / 1000 # milliseconds to seconds
elif timestamp < 1e18:
return timestamp / 1e6 # microseconds to seconds
else:
return timestamp / 1e9 # nanoseconds to seconds
Convert Between Number Bases
When debugging binary protocols, you may need to see a timestamp's binary or hexadecimal representation. Our Number Base Converter makes it easy to convert epoch values between decimal, hex, octal, and binary.
8. Time Zones and Epoch Time
One of epoch time's greatest strengths is that it is inherently UTC. The value 1770681600 represents the same absolute instant in time regardless of where you are on Earth. Time zone complexity only enters the picture when converting to or from human-readable formats.
The Golden Rule
Store and transmit timestamps as UTC epoch. Convert to local time only at the display layer. If you follow this single rule, you will avoid the vast majority of timezone bugs.
Common Timezone Pitfalls
Pitfall 1: Using local time for epoch conversion.
// WRONG: This uses the browser's local timezone
const epoch = new Date("2026-02-11 00:00:00").getTime() / 1000;
// Result depends on the user's timezone!
// RIGHT: Explicitly specify UTC
const epoch = new Date("2026-02-11T00:00:00Z").getTime() / 1000;
// Always returns the same value regardless of timezone
# Python WRONG: naive datetime uses local time
from datetime import datetime
epoch = datetime(2026, 2, 11).timestamp() # Local timezone!
# Python RIGHT: explicit UTC
from datetime import datetime, timezone
epoch = datetime(2026, 2, 11, tzinfo=timezone.utc).timestamp()
Pitfall 2: Daylight Saving Time (DST) transitions.
# A time that doesn't exist (spring forward)
# In US Eastern, 2026-03-08 02:30:00 doesn't exist
# Clocks jump from 2:00 AM to 3:00 AM
# A time that exists twice (fall back)
# In US Eastern, 2026-11-01 01:30:00 exists twice
# Once in EDT (UTC-4) and once in EST (UTC-5)
# Using epoch avoids this entirely:
# There is no ambiguity in epoch time during DST transitions
Pitfall 3: Server timezone vs database timezone.
-- MySQL: Check your server's timezone setting
SELECT @@global.time_zone, @@session.time_zone;
-- If the server is in UTC but you insert without specifying timezone:
INSERT INTO events (name, created_at) VALUES ('test', NOW());
-- NOW() returns the server's timezone, which may differ from UTC
-- Always store in UTC:
INSERT INTO events (name, created_at) VALUES ('test', UTC_TIMESTAMP());
-- PostgreSQL: AT TIME ZONE conversion
SELECT created_at AT TIME ZONE 'America/New_York' FROM events;
Best Practices for Timezone Handling
- Set servers and databases to UTC: Configure
TZ=UTCon all servers. Set database timezone to UTC. This eliminates an entire class of bugs. - Never store local times: Store epoch or UTC timestamps. Reconstruct local time from the user's timezone preference at display time.
- Store the user's timezone separately: If you need to display dates in the user's timezone, store their timezone as a string (e.g.,
"America/New_York") separately from the timestamp. - Use IANA timezone names: Always use
"America/New_York"rather than"EST"or"UTC-5". Abbreviations are ambiguous (CST = Central Standard Time, China Standard Time, or Cuba Standard Time?) and fixed offsets do not account for DST. - Test with timezone offsets: Include timezone edge cases in your test suite: UTC+14 (Kiribati), UTC-12 (Baker Island), half-hour offsets (India, Nepal), and DST transitions.
9. Epoch Values Every Developer Should Know
Certain epoch values appear frequently in development, debugging, and interview questions. Having these committed to memory (or bookmarked) makes you faster at diagnosing timestamp issues.
| Epoch Value | Date | Significance |
|---|---|---|
0 |
Jan 1, 1970 00:00:00 UTC | The Unix epoch. If you see this date in your data, a timestamp field was initialized to zero or left unset. |
-1 |
Dec 31, 1969 23:59:59 UTC | One second before epoch. Often used as a sentinel value meaning "not set" or "error". |
-62167219200 |
Jan 1, 0001 00:00:00 UTC | The "zero date" in some systems (Go's time.Time{} zero value). |
946684800 |
Jan 1, 2000 00:00:00 UTC | The Y2K boundary. Start of the 21st century. Useful reference point. |
1000000000 |
Sep 9, 2001 01:46:40 UTC | One billion seconds. A fun milestone. The "billennium." |
1234567890 |
Feb 13, 2009 23:31:30 UTC | The ascending-digit epoch. Celebrated by programmers worldwide. |
2000000000 |
May 18, 2033 03:33:20 UTC | Two billion seconds. Near the 32-bit limit. |
2147483647 |
Jan 19, 2038 03:14:07 UTC | Maximum signed 32-bit integer. The Y2K38 boundary. |
4294967295 |
Feb 7, 2106 06:28:15 UTC | Maximum unsigned 32-bit integer. Some systems use unsigned timestamps. |
32503680000 |
Jan 1, 3000 00:00:00 UTC | Sometimes used as a "far future" sentinel value. |
Negative Epoch Values
Epoch timestamps before January 1, 1970, are represented as negative numbers. This is important for historical data:
// Dates before the epoch
const moonLanding = -14182940; // July 20, 1969 20:17:40 UTC
const wwiiEnd = -769395600; // Sep 2, 1945 (approximate)
// JavaScript handles negative epochs
const date = new Date(-14182940 * 1000);
console.log(date.toISOString()); // "1969-07-20T20:17:40.000Z"
# Python handles negative epochs
from datetime import datetime, timezone
dt = datetime.fromtimestamp(-14182940, tz=timezone.utc)
print(dt) # 1969-07-20 20:17:40+00:00
Note that not all systems support negative epoch values. Some databases, APIs, and serialization formats treat timestamps as unsigned integers, making pre-1970 dates impossible to represent. Always test if your stack handles negative epochs before relying on them.
10. Debugging Timestamp Issues
Timestamp bugs are insidious because they can appear to work correctly in one timezone, one locale, or one time of year, and then break when any of those change. Here is a systematic approach to diagnosing and fixing them.
Step 1: Identify the Unit
The very first thing to determine is whether you are dealing with seconds, milliseconds, microseconds, or nanoseconds. Count the digits:
1770681600 // 10 digits = seconds
1770681600000 // 13 digits = milliseconds
1770681600000000 // 16 digits = microseconds
// Quick command-line check (Bash)
date -d @1770681600
# Wed Feb 11 00:00:00 UTC 2026
// If the date looks wrong (year 58104 or Jan 1970), you have a unit mismatch
Step 2: Check for Zero or Sentinel Values
// Common sentinel values that indicate bugs:
0 // Jan 1, 1970 — uninitialized timestamp
-1 // Dec 31, 1969 — error sentinel
NaN // JavaScript: invalid date parsing
null // Missing field
"" // Empty string parsed as date
Step 3: Verify Timezone Assumptions
// Is the timestamp UTC or local?
// Create a date you know (e.g., midnight UTC on a specific day)
// and verify the epoch value matches
// Expected: 2026-02-11 00:00:00 UTC = 1770681600
// If you get a different value, your code is applying a timezone offset
// JavaScript debug
const d = new Date("2026-02-11T00:00:00Z");
console.log(d.getTime() / 1000); // Should be 1770681600
console.log(d.getTimezoneOffset()); // Minutes offset from UTC
Step 4: Trace the Data Flow
When a timestamp is wrong, trace it from origin to display:
- Where was it created? Check the source system's timezone and epoch convention (seconds vs milliseconds).
- How was it stored? Check the database column type (
INT,BIGINT,TIMESTAMP,DATETIME) and its timezone behavior. - How was it transmitted? Check the API serialization format. Is it JSON (number or string)? Is the unit documented?
- How is it parsed? Check the client-side parsing code. Is it multiplying/dividing by 1000 correctly?
- How is it displayed? Check the timezone conversion at display time. Is the user's timezone applied correctly?
Step 5: Common Fixes
// Fix: Date shows as January 1970
// Cause: Seconds value used where milliseconds expected
const epoch = 1770681600;
const wrong = new Date(epoch); // Jan 21, 1970
const right = new Date(epoch * 1000); // Feb 11, 2026
// Fix: Date shows as year 58104
// Cause: Milliseconds value used where seconds expected
const epochMs = 1770681600000;
const wrong = new Date(epochMs * 1000); // Year 58104
const right = new Date(epochMs); // Feb 11, 2026
// Fix: Date is off by a few hours
// Cause: Local timezone applied instead of UTC
const d = new Date(1770681600 * 1000);
const wrong = d.toString(); // Local timezone
const right = d.toISOString(); // UTC
// Fix: Date is off by exactly 1 hour
// Cause: DST transition not accounted for
// Solution: Always store and compare in UTC
// Fix: Date is January 1, 0001 or similar "zero date"
// Cause: Uninitialized date object (Go, .NET)
// Solution: Check for zero values before processing
Debugging Tools
- DevToolbox Epoch Converter — Paste any epoch value and instantly see the human-readable date. Handles both seconds and milliseconds.
date -d @EPOCH(Linux) ordate -r EPOCH(macOS) — Command-line epoch conversion.- Browser DevTools Console:
new Date(EPOCH * 1000).toISOString()— Quick in-browser conversion. - PostgreSQL:
SELECT TO_TIMESTAMP(EPOCH);— Convert directly in SQL queries. - DevToolbox JSON Formatter — Pretty-print API responses to identify timestamp fields and their formats.
Timestamp Testing Checklist
Add these test cases to your suite to catch timestamp bugs before they reach production:
- Test with epoch value
0(boundary condition) - Test with a negative epoch (pre-1970 dates)
- Test with epoch near
2147483647(Y2038 boundary) - Test with millisecond epoch values (13 digits)
- Test during a DST transition (spring forward and fall back)
- Test with timezone offsets of +12, -12, +5:30, +5:45 (non-standard offsets)
- Test with
null,undefined, empty string, andNaNinputs - Test with dates far in the future (year 2100+)
- Test midnight UTC on a day when your local timezone is a different date
Conclusion
Unix epoch timestamps are one of those concepts that seem trivially simple but have surprising depth. The core idea, counting seconds from January 1, 1970, is elegant and powerful. But the real-world complexities of time zones, unit confusion (seconds vs. milliseconds), 32-bit overflow, DST transitions, and database idiosyncrasies mean that timestamp handling requires careful attention.
The key takeaways from this guide:
- Always store and transmit time as UTC epoch. Convert to local time only at the display layer.
- Always know your units. Count the digits: 10 = seconds, 13 = milliseconds. Document which one your API uses.
- Use 64-bit integers for timestamp storage. The Y2038 problem is real and approaching.
- Use your database's native timestamp type when possible (
TIMESTAMPTZin PostgreSQL,DATETIMEin MySQL). Convert to/from epoch at the application layer. - Test timezone edge cases. Your code should work correctly regardless of the server's or user's timezone.
- When debugging, trace the data flow from creation to storage to transmission to display. The bug is in one of those steps.
Epoch time has endured for over 50 years because it solves a hard problem with elegant simplicity. Understand its nuances, respect its quirks, and it will serve you well in every system you build.
Start Converting Timestamps
Put your knowledge into practice with our Epoch Converter. Convert between Unix timestamps and human-readable dates instantly, right in your browser. Supports seconds, milliseconds, and custom formats.