The Fundamental Truth About Unix Timestamps and Timezones
Here's the most important thing to understand about Unix timestamps and timezones: Unix timestamps are always in UTC. Always. Without exception.
A Unix timestamp represents a specific moment in time—the number of seconds since January 1, 1970, 00:00:00 UTC—and that moment is the same everywhere on Earth, regardless of local timezone. When it's timestamp 1735689600 in New York, it's also 1735689600 in Tokyo, London, Sydney, and every other location on the planet.
This universal time representation is one of Unix timestamps' greatest strengths, but it's also a source of considerable confusion for developers. Let's explore how timezone handling works with Unix timestamps and how to convert them correctly.
Understanding UTC: The Universal Time Reference
What is UTC?
UTC (Coordinated Universal Time) is the primary time standard by which the world regulates clocks and time. It's the modern successor to GMT (Greenwich Mean Time) and serves as the zero-point reference for all timezones worldwide.
Key characteristics of UTC:
- Never observes Daylight Saving Time - UTC remains constant year-round
- Scientifically maintained - Based on International Atomic Time with occasional leap seconds
- Global standard - Used by aviation, navigation, science, and computing
- Timezone offset reference - All timezones are expressed relative to UTC
Timezone Offsets from UTC
Every timezone on Earth is defined by its offset from UTC:
UTC+0: London (UK), Reykjavik, Lisbon
UTC-5: New York (EST), Toronto, Lima
UTC-8: Los Angeles (PST), Vancouver, Tijuana
UTC+1: Paris, Berlin, Rome, Madrid
UTC+5:30: New Delhi, Mumbai (note: 30-minute offset)
UTC+9: Tokyo, Seoul
UTC+10: Sydney (AEDT), Melbourne
When we say "New York is UTC-5," we mean that when it's 12:00 PM (noon) in New York, it's 5:00 PM (17:00) in UTC.
How Unix Timestamps Handle Timezones
Storage: Always UTC
Unix timestamps are always stored in UTC. This is not negotiable or configurable—it's fundamental to how Unix time works:
// These all represent the SAME moment in time
const timestamp = 1735689600; // January 1, 2025, 00:00:00 UTC
// Display in different timezones
UTC: 2025-01-01 00:00:00
EST: 2024-12-31 19:00:00 (UTC-5)
PST: 2024-12-31 16:00:00 (UTC-8)
CET: 2025-01-01 01:00:00 (UTC+1)
JST: 2025-01-01 09:00:00 (UTC+9)
Display: Convert to Local Time
When you need to show a timestamp to users, you convert it from UTC to their local timezone. This conversion happens only for display purposes—the underlying timestamp remains unchanged:
const timestamp = 1735689600; // UTC timestamp
// Convert to user's local timezone
const date = new Date(timestamp * 1000);
// Different users see different local times:
// User in New York sees: 12/31/2024, 7:00 PM
// User in London sees: 1/1/2025, 12:00 AM
// User in Tokyo sees: 1/1/2025, 9:00 AM
The Golden Rule
Store in UTC, display in local time. Never store local time as a timestamp without timezone information.
Converting Unix Timestamps to Local Timezones
JavaScript Date Object
JavaScript's Date object automatically handles timezone conversion:
const timestamp = 1735689600; // Unix timestamp in seconds
// Convert to Date object (milliseconds required)
const date = new Date(timestamp * 1000);
// These methods return values in LOCAL timezone
date.getFullYear(); // Local year
date.getMonth(); // Local month (0-11)
date.getDate(); // Local day of month
date.getHours(); // Local hour
// These methods return values in UTC
date.getUTCFullYear(); // UTC year
date.getUTCMonth(); // UTC month
date.getUTCHours(); // UTC hour
Converting to Specific Timezones
Modern JavaScript provides the Intl.DateTimeFormat API for timezone-aware formatting:
const timestamp = 1735689600;
const date = new Date(timestamp * 1000);
// Format for New York timezone
const nyFormatter = new Intl.DateTimeFormat('en-US', {
timeZone: 'America/New_York',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
});
console.log(nyFormatter.format(date));
// Output: 12/31/2024, 19:00:00
// Format for Tokyo timezone
const tokyoFormatter = new Intl.DateTimeFormat('en-US', {
timeZone: 'Asia/Tokyo',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
});
console.log(tokyoFormatter.format(date));
// Output: 01/01/2025, 09:00:00
Using moment-timezone Library
For more complex timezone operations, libraries like moment-timezone are invaluable:
const moment = require('moment-timezone');
const timestamp = 1735689600;
// Convert to specific timezones
const nyTime = moment.unix(timestamp).tz('America/New_York');
const londonTime = moment.unix(timestamp).tz('Europe/London');
const tokyoTime = moment.unix(timestamp).tz('Asia/Tokyo');
console.log(nyTime.format('YYYY-MM-DD HH:mm:ss z'));
// Output: 2024-12-31 19:00:00 EST
console.log(londonTime.format('YYYY-MM-DD HH:mm:ss z'));
// Output: 2025-01-01 00:00:00 GMT
console.log(tokyoTime.format('YYYY-MM-DD HH:mm:ss z'));
// Output: 2025-01-01 09:00:00 JST
Converting Local Time to Unix Timestamps
When converting from local time to Unix timestamps, you must explicitly specify the timezone:
JavaScript: Specifying Timezone
// ❌ WRONG: Ambiguous - what timezone?
const date = new Date('2025-01-01 12:00:00');
const timestamp = date.getTime() / 1000;
// Interprets as local timezone of the system
// ✅ CORRECT: Explicit UTC time
const dateUTC = Date.UTC(2025, 0, 1, 12, 0, 0);
const timestampUTC = dateUTC / 1000;
// ✅ CORRECT: Using ISO 8601 with Z suffix (UTC)
const dateISO = new Date('2025-01-01T12:00:00Z');
const timestampISO = dateISO.getTime() / 1000;
// ✅ CORRECT: Using moment-timezone for specific timezone
const moment = require('moment-timezone');
const dateNY = moment.tz('2025-01-01 12:00:00', 'America/New_York');
const timestampNY = dateNY.unix();
Python: Timezone-Aware Conversion
from datetime import datetime
import pytz
# ❌ WRONG: Naive datetime (no timezone info)
dt = datetime(2025, 1, 1, 12, 0, 0)
timestamp = dt.timestamp() # Assumes local timezone
# ✅ CORRECT: Timezone-aware datetime
ny_tz = pytz.timezone('America/New_York')
dt_ny = ny_tz.localize(datetime(2025, 1, 1, 12, 0, 0))
timestamp_ny = int(dt_ny.timestamp())
# ✅ CORRECT: UTC datetime
dt_utc = datetime(2025, 1, 1, 12, 0, 0, tzinfo=pytz.UTC)
timestamp_utc = int(dt_utc.timestamp())
PHP: DateTimeZone Class
// ❌ WRONG: No timezone specified
$date = new DateTime('2025-01-01 12:00:00');
$timestamp = $date->getTimestamp();
// ✅ CORRECT: Explicit timezone
$nyTz = new DateTimeZone('America/New_York');
$date = new DateTime('2025-01-01 12:00:00', $nyTz);
$timestamp = $date->getTimestamp();
// ✅ CORRECT: UTC timezone
$utcTz = new DateTimeZone('UTC');
$date = new DateTime('2025-01-01 12:00:00', $utcTz);
$timestamp = $date->getTimestamp();
Common Timezone Pitfalls and Solutions
Pitfall 1: Assuming Local Time is Universal
// ❌ WRONG: Developer in New York creates timestamp
const meetingTime = new Date('2025-06-15 14:00:00').getTime() / 1000;
// This creates a timestamp for 2:00 PM New York time
// User in Tokyo retrieves it
const date = new Date(meetingTime * 1000);
console.log(date.getHours()); // Shows 3:00 AM Tokyo time
// User is confused - meeting is at 3 AM?
// ✅ CORRECT: Always specify timezone explicitly
const meetingTimeUTC = Date.UTC(2025, 5, 15, 18, 0, 0) / 1000;
// 14:00 New York = 18:00 UTC
// User in any timezone can convert correctly
Pitfall 2: Storing Timezone Separately
// ❌ WRONG: Storing timezone as separate field
database.save({
timestamp: 1735689600,
timezone: 'America/New_York'
});
// Redundant and confusing - timestamp is already UTC
// ✅ CORRECT: Store only UTC timestamp
database.save({
timestamp: 1735689600 // Always UTC
});
// Convert to user's timezone when displaying
Pitfall 3: Daylight Saving Time Complications
// Scenario: Scheduling a meeting 6 months in advance
// ❌ WRONG: Adding hours without considering DST
const now = Date.now() / 1000;
const sixMonthsLater = now + (6 * 30 * 24 * 60 * 60);
// This doesn't account for DST transitions
// ✅ CORRECT: Use date libraries that handle DST
const moment = require('moment-timezone');
const meeting = moment()
.tz('America/New_York')
.add(6, 'months')
.unix();
// Correctly handles DST transitions
Pitfall 4: Parsing User Input
// User enters: "January 15, 2025, 3:00 PM"
// Question: Which timezone?
// ❌ WRONG: Assume local system timezone
const userDate = new Date('January 15, 2025 15:00:00');
// ✅ CORRECT: Ask user for timezone or detect it
const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const moment = require('moment-timezone');
const userDate = moment.tz(
'2025-01-15 15:00:00',
userTimezone
).unix();
Best Practices for Timezone Handling
1. Always Store UTC Timestamps
// ✅ Database schema
CREATE TABLE events (
id INT PRIMARY KEY,
name VARCHAR(255),
event_time INT, -- Unix timestamp (always UTC)
-- DON'T store separate timezone field
);
2. Convert Only for Display
// ✅ API response (server returns UTC timestamp)
{
"event": {
"id": 123,
"name": "Product Launch",
"event_time": 1735689600 // Unix timestamp (UTC)
}
}
// ✅ Client converts to local time for display
const eventTime = new Date(event.event_time * 1000);
const localTime = eventTime.toLocaleString(undefined, {
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
});
3. Use ISO 8601 for Human-Readable Formats
When you need human-readable timestamps (logs, debugging), use ISO 8601 with Z suffix:
const timestamp = 1735689600;
const date = new Date(timestamp * 1000);
// ISO 8601 with UTC indicator
console.log(date.toISOString());
// Output: 2025-01-01T00:00:00.000Z
// ^ Z means UTC
4. Document Timezone Expectations
/**
* Creates a new event
* @param {string} name - Event name
* @param {number} eventTime - Unix timestamp in SECONDS (UTC)
* @returns {Object} Created event with UTC timestamp
*/
function createEvent(name, eventTime) {
// Implementation
}
5. Test Across Timezones
// Unit tests should cover multiple timezones
describe('Event scheduling', () => {
const timezones = [
'America/New_York',
'Europe/London',
'Asia/Tokyo',
'Australia/Sydney'
];
timezones.forEach(tz => {
it(`should handle ${tz} correctly`, () => {
// Test implementation
});
});
});
Tools and Libraries for Timezone Handling
JavaScript
- date-fns-tz: Lightweight, modern, tree-shakeable
- moment-timezone: Feature-rich, widely adopted (maintenance mode)
- luxon: Modern alternative to moment, better timezone support
- day.js: Ultra-lightweight with timezone plugin
Python
- pytz: Standard timezone database for Python
- python-dateutil: Parser and timezone utilities
- arrow: User-friendly dates and times
PHP
- Carbon: Expressive datetime library
- DateTimeZone: Built-in PHP class
Database
- PostgreSQL:
TIMESTAMP WITH TIME ZONEtype - MySQL: Store as INT (Unix timestamp) or use TIMESTAMP
- MongoDB: Stores dates as UTC milliseconds
Conclusion
Unix timestamps handle timezones through a simple but powerful principle: store everything in UTC, convert to local time only when displaying to users. This approach:
- Eliminates timezone confusion in storage
- Simplifies time calculations and comparisons
- Ensures consistency across distributed systems
- Makes it easy to support users in any timezone
Remember these key points:
- Unix timestamps are always UTC—this is not configurable
- Never store local time as a Unix timestamp without proper conversion
- Convert to local timezone only for display, never for storage or calculations
- Use proper libraries and APIs for timezone conversion—don't calculate manually
- Daylight Saving Time is handled automatically when you convert to local time
- Test your application with users in different timezones and during DST transitions
By following these principles, you'll build applications that correctly handle time across all timezones, preventing the all-too-common bugs that plague timezone-naive code.
Need to convert Unix timestamps between different timezones? Try our Unix Timestamp Converter with support for hundreds of timezones worldwide, or use our World Clock tool to coordinate times across multiple locations.