Why Timezones Break Your Applications (And How Timestamps Help)
Most developers don't think much about timezones when they first start building software.
Dates seem simple. Store a date, display a date, compare a date. Done.
Then one day, a customer in Sydney schedules a meeting for 9:00 AM and someone in New York receives it at the wrong time. Or a user submits a payment just before midnight, but the system records it on the next day. Or an analytics dashboard suddenly shows duplicate events during a daylight saving time transition.
That's when you discover an uncomfortable truth: time is one of the hardest problems in software engineering.
Over the years, I've seen timezone bugs affect scheduling systems, payment platforms, booking applications, CRON jobs, e-commerce reports, authentication services, and enterprise systems. The good news is that most of them share a common solution: store time as timestamps, convert to local time only at the display layer.
Why Timezones Are So Difficult
At first glance, timezones seem like a solved problem. Tokyo is UTC+9. London is UTC+0. New York is UTC-5. Simple.
The problem is that real-world time isn't static. Governments change rules. Daylight Saving Time (DST) shifts clocks. Countries adopt new standards. Applications serving global users must constantly adapt.
A date like 2025-01-01 12:00 means completely different moments depending on whether it's UTC, JST, or PST. Without timezone information, dates become ambiguous. And computers handle ambiguity poorly.
A Real Production Bug
A SaaS platform allowed users to schedule reports. A customer in California scheduled a report for 2025-06-01 09:00. The application stored exactly that string:
{
"scheduled_time": "2025-06-01 09:00"
}
Notice what's missing? Timezone information.
Months later, the company expanded internationally. Users in Germany started using the platform. Suddenly, reports were running at unexpected times. The application knew the date and the time. It didn't know the timezone.
A single missing piece of information created a production issue affecting thousands of users. The fix required data migration, code changes, and customer communication. All preventable by storing UTC timestamps from day one.
The Ambiguous Date Problem
Consider 2025-01-01 12:00. What does it mean?
It could be 2025-01-01 12:00 UTC, 2025-01-01 12:00 JST, 2025-01-01 12:00 PST. These are completely different moments in time, separated by hours. Without timezone metadata, you can't know which one was intended.
Computers are precise machines. Ambiguity is a bug waiting to happen.
Daylight Saving Time Makes Everything Worse
Daylight Saving Time is responsible for countless production incidents. Imagine scheduling something at 2025-03-09 02:30 America/New_York.
That time never exists. When DST begins, clocks jump from 01:59 directly to 03:00. Any scheduled event during the "missing hour" is fundamentally broken.
The opposite happens when DST ends. 2025-11-02 01:30 America/New_York occurs twice — once before the clock falls back, once after. Schedulers relying on local time can execute jobs twice or skip them entirely.
The only reliable defense is to operate on UTC timestamps and convert at the last possible moment.
How Timestamps Solve the Problem
A Unix timestamp represents a specific moment in time. The value 1735689600 always means 2025-01-01 00:00:00 UTC.
No ambiguity. No timezone confusion. No DST complications. No country-specific format interpretation. A timestamp is simply "seconds since January 1, 1970 UTC." Every system interprets it identically.
If you're unfamiliar with Unix time, read what is a Unix timestamp for the full background.
Store UTC, Display Local Time
This principle appears in almost every modern application.
Bad Approach
{
"created_at": "2025-01-01 09:00"
}
Problems: timezone unknown, DST complications, difficult comparisons, locale-dependent parsing.
Better Approach
{
"created_at": 1735712400
}
Benefits: universal format, easy calculations, consistent across systems. Then convert for display:
UTC: 2025-01-01 00:00
Tokyo: 2025-01-01 09:00
New York: 2024-12-31 19:00
Same timestamp. Different presentation based on the user's location.
Why APIs Prefer Timestamps
Most large-scale APIs exchange timestamps rather than local dates.
{ "created_at": 1735689600 }
Instead of:
{ "created_at": "2025-01-01 09:00" }
The timestamp version avoids locale differences, date formatting issues, and timezone mismatches. This is why timestamps are the standard in REST APIs, GraphQL APIs, event systems, message queues, and analytics platforms.
Common Timezone Bugs
Bug 1: Off-by-One-Day
A user selects 2025-01-01 in a date picker. The frontend sends it as a local midnight timestamp. The backend stores it in UTC. The next day, the date displays as 2024-12-31.
The culprit is almost always timezone conversion during storage. Converting at the wrong layer shifts the date.
Bug 2: Scheduled Jobs Run Twice or Not at All
During DST transitions, a job scheduled at 01:30 either runs twice or never runs. CRON expressions that rely on local time are vulnerable. The fix: schedule in UTC.
Bug 3: Analytics Reports Don't Match
The backend aggregates in UTC. The frontend displays in local time. Daily reports show different totals depending on who views them — a single transaction can land on different "days" for different viewers.
Bug 4: Authentication Expiration Errors
JWT tokens use numeric timestamps for expiration:
{ "exp": 1735689600 }
Comparing timestamps avoids timezone confusion entirely. The comparison currentTimestamp > exp works regardless of where the server or user is located.
Working with Timestamps and Timezones in Code
JavaScript
// Current timestamp in seconds
Math.floor(Date.now() / 1000);
// Convert timestamp to local time
const date = new Date(1735689600 * 1000);
console.log(date.toString());
// Convert timestamp to UTC display
console.log(date.toUTCString());
// → Wed, 01 Jan 2025 00:00:00 GMT
Remember: JavaScript's Date constructor expects milliseconds, not seconds. The * 1000 is mandatory. If this trips you up, our seconds vs milliseconds guide explains why and how to avoid it.
Python
from datetime import datetime, timezone
timestamp = 1735689600
dt = datetime.fromtimestamp(timestamp, tz=timezone.utc)
print(dt)
# → 2025-01-01 00:00:00+00:00
Always specify the timezone explicitly. Relying on the system's local timezone produces different results on different machines.
Why Databases Should Store UTC
Imagine storing 2025-01-01 09:00 without timezone metadata. Future developers won't know which timezone generated it, whether DST applied, or whether it was already converted.
Instead, use:
created_at BIGINT -- stores Unix timestamp
Or a timezone-aware type:
TIMESTAMP WITH TIME ZONE -- PostgreSQL
Most modern systems either store UTC timestamps as integers or use timezone-aware datetime columns. Mixing approaches within the same codebase creates subtle, hard-to-detect bugs.
The International Team Problem
Many developers test applications from a single location. Problems only surface when teams become global.
Imagine: a developer in Tokyo, QA in London, a customer in New York, and a server in Frankfurt. The value 2025-01-01 09:00 means something different to every person involved.
A timestamp eliminates the ambiguity. Everyone refers to the same moment, regardless of their local timezone.
Timestamps and Logging
Logs are another common source of confusion.
Bad: 01/01/2025 09:00:00 — Which timezone? Which locale? What does this mean during merge debugging?
Better: 1735689600 or 2025-01-01T00:00:00Z — Both represent an exact, unambiguous moment. When debugging across distributed systems, consistent log timestamps prevent confusion about event ordering.
Best Practices
Store UTC
Never store local times unless you have a very specific reason. Default to UTC for everything.
Convert at the UI Layer
Users care about local time. Databases and backend services do not. Convert only when displaying information.
Use Timestamps for APIs
Numeric timestamps eliminate interoperability issues between services written in different languages.
Include Timezone Information
If you must store formatted dates, use ISO 8601 with timezone offset: 2025-01-01T09:00:00+09:00. This is much safer than a bare 2025-01-01 09:00.
Test Around DST Transitions
Many timezone bugs only appear twice per year. Those are often the hardest to reproduce. Include DST boundary dates in your test suite.
Related Resources
- What Is a Unix Timestamp? — the fundamentals
- Timestamp in Seconds vs Milliseconds — the other most common timestamp bug
- How to Convert Unix Timestamps in JavaScript, Python, PHP, and SQL — practical code examples
Frequently Asked Questions
Why do timezones break applications?
Because the same local date and time can represent different moments depending on location, DST rules, and timezone settings. Without explicit timezone metadata, dates are ambiguous.
Should I store timestamps or local dates?
For most applications, store timestamps or UTC values and convert to local time only when displaying information to users.
Are Unix timestamps timezone-independent?
Yes. Unix timestamps represent a specific moment in UTC. They don't contain timezone information, which is exactly what makes them reliable as a storage and exchange format.
What is the safest date format for APIs?
Unix timestamps (1735689600) and ISO 8601 UTC strings (2025-01-01T00:00:00Z) are the most reliable choices. Both are unambiguous and widely supported.
How does Daylight Saving Time affect timestamps?
It doesn't. DST affects how dates are displayed, not the underlying timestamp. That's the entire point of using timestamps.
Should databases store local time?
Generally no. Store UTC and convert when presenting data to users. This prevents entire categories of bugs related to server timezone changes, DST transitions, and multi-region deployments.
Why do timestamps simplify distributed systems?
Because every service interprets the same timestamp as the same exact moment, regardless of its physical location, operating system timezone, or local timekeeping conventions.
Most timezone bugs aren't caused by complex code. They're caused by assumptions — assuming a date includes timezone information, assuming servers and users share the same timezone, assuming DST rules never change.
In practice, time becomes much easier to manage when applications treat timestamps as the source of truth. Store UTC. Exchange timestamps between services. Convert to local time only when information reaches the user interface.
That approach won't eliminate every date-related bug, but it removes most of the issues that frustrate developers and confuse users.
If you regularly work with APIs, logs, JWT tokens, scheduling systems, or database records, the DevFormatters Timestamp Converter makes it easy to inspect timestamps, verify UTC values, and quickly see how a moment appears across different timezones during debugging.