Timezone Handling
Also Known As
UTC
DST
timezone conversion
DateTimeImmutable
TL;DR
Storing dates as UTC and converting to local timezones only for display — prevents DST bugs, ambiguous times, and incorrect date arithmetic.
Explanation
Always store and compute in UTC; convert for display. PHP's DateTimeImmutable with DateTimeZone handles conversions. Database: use DATETIME with UTC application-level enforcement (MySQL), or TIMESTAMPTZ (PostgreSQL, which stores UTC and converts automatically). DST transitions create ambiguous times (1:30am exists twice) and gaps (2:30am may not exist). The IANA timezone database (America/New_York, not EST) handles DST rules correctly; three-letter abbreviations (EST, CET) are ambiguous.
Diagram
flowchart TD
USER_INPUT[User enters: 2026-03-15 14:00] --> AMBIG{Which timezone?}
AMBIG -->|assumed UTC| UTC_STORE[Store as UTC in DB<br/>2026-03-15T14:00:00Z]
AMBIG -->|assumed local| WRONG[Wrong time stored!<br/>timezone lost forever]
UTC_STORE --> DISPLAY[Display to user]
DISPLAY --> CONVERT[Convert UTC to user timezone<br/>Europe/London = 14:00 BST]
subgraph PHP_Best_Practice
DI2[DateTimeImmutable with timezone]
UTC2[Always store UTC]
PREF[User timezone preference<br/>in profile settings]
DI2 --> UTC2 --> PREF
end
style WRONG fill:#f85149,color:#fff
style UTC_STORE fill:#238636,color:#fff
style UTC2 fill:#238636,color:#fff
Common Misconception
✗ Using local server timezone for PHP and MySQL is fine — server timezone changes (DST, migration) silently change stored times; UTC is the only unambiguous storage format.
Why It Matters
Date bugs from incorrect timezone handling cause missed appointments, wrong billing periods, and doubled/skipped notifications during DST transitions — UTC storage prevents all of these.
Common Mistakes
- Storing local timestamps in the database — server TZ changes silently shift all stored times.
- Using date_default_timezone_set() to a non-UTC timezone for storage — always store UTC; convert only for display.
- Three-letter timezone abbreviations (EST, IST) — ambiguous; IST is India Standard, Israel Standard, and Irish Standard Time.
- Not using DateTimeImmutable — DateTime::modify() mutates the object; DateTimeImmutable always returns a new instance.
Code Examples
✗ Vulnerable
// Storing local time — breaks when server TZ changes:
date_default_timezone_set('America/New_York');
$created = date('Y-m-d H:i:s'); // Local time stored in DB
// Server moved to UTC: all existing times are now wrong
// Mutable DateTime — shared reference bug:
$start = new DateTime('2026-01-01');
$end = $start->modify('+30 days'); // $start is ALSO modified!
✓ Fixed
// UTC storage, local display:
date_default_timezone_set('UTC');
$created = (new DateTimeImmutable())->format('Y-m-d H:i:s'); // Stored as UTC
// Display in user's timezone:
$utc = new DateTimeImmutable('2026-01-01 12:00:00', new DateTimeZone('UTC'));
$local = $utc->setTimezone(new DateTimeZone('America/New_York'));
echo $local->format('Y-m-d H:i:s T'); // '2026-01-01 07:00:00 EST'
Tags
🤝 Adopt this term
£79/year · your link shown here
Added
15 Mar 2026
Edited
22 Mar 2026
Views
48
🤖 AI Guestbook educational data only
|
|
Last 30 days
Agents 1
No pings yesterday
Amazonbot 16
Perplexity 10
Google 6
Unknown AI 3
ChatGPT 3
Ahrefs 2
How they use it
crawler 39
pre-tracking 1
⚡
DEV INTEL
Tools & Severity
🟠 High
⚙ Fix effort: Medium
⚡ Quick Fix
Store all timestamps in UTC, display in user's timezone — never store local time; use PHP's DateTimeImmutable with explicit timezones and Carbon for convenience
📦 Applies To
PHP 5.0+
web
cli
queue-worker
🔗 Prerequisites
🔍 Detection Hints
date() without explicit timezone; storing user-visible timestamps without UTC conversion; comparing timestamps from different timezones
Auto-detectable:
✓ Yes
phpstan
psalm
carbonphp
⚠ Related Problems
🤖 AI Agent
Confidence: High
False Positives: Medium
✗ Manual fix
Fix: Medium
Context: File
Tests: Update