← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Timezone Handling

i18n PHP 5.0+ Intermediate
debt(d7/e7/b7/t7)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). The detection_hints list phpstan, psalm, and carbonphp as tools — these are specialist static analysis tools that can catch some patterns (e.g. date() without explicit timezone), but many timezone bugs (storing local time, ambiguous abbreviations, DST transitions) only manifest at runtime when the server timezone changes or DST kicks in. They won't catch semantic errors like storing local time that happens to look valid.

e7 Effort Remediation debt — work required to fix once spotted

Closest to 'cross-cutting refactor across the codebase' (e7). The quick_fix says 'store all timestamps in UTC' but the common_mistakes reveal that stored local timestamps must be migrated in the database, date_default_timezone_set() calls must be audited across web/cli/queue contexts, and DateTime must be replaced with DateTimeImmutable throughout. This touches database schemas, application logic, and all display layers — a cross-cutting fix spanning multiple files and components.

b7 Burden Structural debt — long-term weight of choosing wrong

Closest to 'strong gravitational pull' (e7). The applies_to covers all three PHP contexts (web, cli, queue-worker) and the tags include both database and i18n. Every feature involving dates — billing, scheduling, notifications — must navigate the UTC storage/local display split. The choice shapes how every timestamp-related operation is written and reviewed, imposing a persistent tax across all work streams.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap' (t7). The misconception field states explicitly: 'Using local server timezone for PHP and MySQL is fine — server timezone changes (DST, migration) silently change stored times.' This contradicts common developer intuition (storing what users see seems natural), and the failure is silent in production. It doesn't reach t9 because UTC-first is a well-known best practice that most experienced developers eventually learn, but the 'obvious' naive approach is reliably wrong.

About DEBT scoring →

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'

Added 15 Mar 2026
Edited 22 Mar 2026
Views 73
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 2 pings S 2 pings S 1 ping M 0 pings T 0 pings W 0 pings T 0 pings F 1 ping S 0 pings S 0 pings M 1 ping T 0 pings W 0 pings T 0 pings F 1 ping S 0 pings S 0 pings M 1 ping T 0 pings W
No pings yet today
SEMrush 1
Amazonbot 16 Perplexity 10 Google 7 ChatGPT 5 Ahrefs 4 Scrapy 4 Unknown AI 3 Claude 1 Bing 1 Sogou 1 PetalBot 1 SEMrush 1
crawler 52 crawler_json 1 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


✓ schema.org compliant