The Most Common Timezone Bugs
After years of developer pain, certain timezone bugs appear again and again. Here are the most common ones with their fixes.
Bug 1: The Naive Datetime Trap (Python)
# Bug: comparing naive and aware datetimes
from datetime import datetime, timezone
deadline = datetime(2024, 3, 1, 0, 0) # naive — no timezone!
now = datetime.now(timezone.utc) # aware UTC
print(now > deadline) # TypeError: can't compare!
# Worse: storing naive datetimes in DB then comparing
created = datetime.now() # naive — assumed local, but which server?
# Fix: always use timezone-aware datetimes
deadline = datetime(2024, 3, 1, 0, 0, tzinfo=timezone.utc)
now = datetime.now(timezone.utc)
print(now > deadline) # Works correctly
Bug 2: DST Transition — The Missing or Doubled Hour
# Bug: scheduling something at "2:30 AM" when clocks spring forward
# On the second Sunday in March (US), 2:00 AM → 3:00 AM
# There is NO 2:30 AM — it doesn't exist!
import zoneinfo
from datetime import datetime
tz = zoneinfo.ZoneInfo("America/New_York")
# This datetime does not exist — zoneinfo handles it, but your intent is wrong
dt = datetime(2024, 3, 10, 2, 30, tzinfo=tz)
# Fix: always compute in UTC, convert for display
# Schedule at 7:30 AM UTC instead of "2:30 AM local"
Bug 3: JavaScript Date Parsing Inconsistency
// Bug: date-only strings parsed as UTC, but displayed as local time
const d = new Date("2024-03-01");
// Parsed as: 2024-03-01T00:00:00Z (midnight UTC)
// Displayed as: March 1 in UTC+9 = but 2024-02-29T15:00:00 in UTC-9!
console.log(d.toLocaleDateString()); // "2/29/2024" in US Western timezone!
// Fix: always include time and timezone in date strings
const d = new Date("2024-03-01T00:00:00Z"); // UTC
const d = new Date("2024-03-01T00:00:00+09:00"); // KST
Bug 4: Database Storing Local Time
-- Bug: MySQL TIMESTAMP stored in local time
-- Server timezone changes → all historical data is wrong!
-- Fix: use DATETIME with UTC-only writes
-- In Django:
USE_TZ = True # ensures all datetimes go in as UTC
Bug 5: Hardcoded UTC Offset Instead of IANA Timezone
# Bug: hardcoding UTC+9 for Korea Standard Time
KST = timezone(timedelta(hours=9))
dt = datetime.now(KST) # Will be wrong during DST (if KST ever had DST)
# Fix: use IANA timezone names
from zoneinfo import ZoneInfo
dt = datetime.now(ZoneInfo("Asia/Seoul")) # Correct, handles any future changes
Debugging Techniques
- Print UTC everywhere: When debugging, always log
datetime.now(timezone.utc).isoformat() - Test in multiple timezones: Set
TZ=America/New_YorkorTZ=Asia/Tokyoenv var to test your code in a different timezone without changing your machine - Test around DST transitions: Use
libfaketime(Linux) orfreezegun(Python) to simulate specific times - Check database timezone:
SHOW timezone;(PostgreSQL) andSELECT @@global.time_zone;(MySQL)
# Python: test code in different timezone without changing system
TZ=America/New_York python my_script.py
# pytest with freezegun
from freezegun import freeze_time
@freeze_time("2024-03-10 06:59:00", tz_offset=-5)
def test_dst_spring_forward():
# Test behavior at DST transition
result = schedule_task("America/New_York", hour=2, minute=30)
assert result.hour != 2 # 2:30 AM doesn't exist, should handle gracefully
The Golden Checklist
- All datetimes are timezone-aware (no naive datetimes in production)
- Database stores UTC (not local time)
- Server timezone is set to UTC
- API responses include timezone offset in all timestamp fields
- Cron jobs either use UTC or explicitly set CRON_TZ
- IANA timezone names used everywhere (never raw UTC offsets like +09:00)
- DST transitions tested in automated tests