PHP Timezones

Timezone identifiers, UTC offsets, and principles for multi-timezone systems.

Intermediate
Key PointsA timezone is not just an offset — DST rules matter. Use identifiers, not `+08:00`.

Common identifiers

IdentifierOffsetNote
UTC+00:00基准,推荐存库
Asia/Shanghai+08:00中国标准时间,无夏令时
Asia/Tokyo+09:00日本,无夏令时
Europe/London+00:00 / +01:00有夏令时
America/New_York-05:00 / -04:00有夏令时
Australia/Sydney+10:00 / +11:00南半球夏令时

Getting and converting

PHP
<?php
$utc = new DateTime("2023-11-15 00:00:00", new DateTimeZone("UTC"));

foreach (["Asia/Shanghai", "Europe/London", "America/New_York"] as $tzName) {
    $d = clone $utc;
    $d->setTimezone(new DateTimeZone($tzName));
    echo str_pad($tzName, 18) . $d->format("Y-m-d H:i:s P") . "\n";
}
Output
Asia/Shanghai     2023-11-15 08:00:00 +08:00
Europe/London     2023-11-15 00:00:00 +00:00
America/New_York  2023-11-14 19:00:00 -05:00

One UTC instant yields three local times, with New York falling back a day. That is why dates need care in multi-timezone systems.

Daylight saving

PHP
<?php
$tz = new DateTimeZone("America/New_York");

// 2024 年夏令时从 3 月 10 日开始
foreach (["2024-03-09", "2024-03-10", "2024-03-11"] as $day) {
    $d = new DateTime($day . " 12:00:00", $tz);
    echo $day . " " . $d->format("P T") . "\n";
}

// 那天的实际长度
$a = new DateTime("2024-03-10 00:00:00", $tz);
$b = new DateTime("2024-03-11 00:00:00", $tz);
echo "这天 " . (($b->getTimestamp() - $a->getTimestamp()) / 3600) . " 小时\n";
Output
2024-03-09 -05:00 EST
2024-03-10 -04:00 EDT
2024-03-11 -04:00 EDT
这天 23 小时
The DST-start day has only 23 hours. Any "a day is 86400 seconds" assumption breaks here — use modify("+1 day"), not +86400.

Offset vs identifier

PHP
<?php
// 只有偏移:没有夏令时信息
$a = new DateTime("2024-03-10 12:00:00", new DateTimeZone("-05:00"));
echo $a->format("P T") . "\n";

// 有时区标识符:能正确处理夏令时
$b = new DateTime("2024-03-10 12:00:00", new DateTimeZone("America/New_York"));
echo $b->format("P T") . "\n";

// 用偏移做加法的陷阱
$a->modify("+1 day");
echo $a->format("P") . "\n";
Output
-05:00 -05:00
-04:00 EDT
-05:00

A date created with -05:00 stays at -05:00 forever, even when the next day should be -04:00. Use identifiers; offsets are for display only.

Principles

  1. Store UTC —— one DATETIME in UTC, or a raw timestamp
  2. Transmit ISO 8601 —— offset included, unambiguous
  3. Convert on display —— per user preference or browser timezone
  4. **Avoid date() for business logic** —— compare DateTime objects
  5. **Set date.timezone** —— to UTC, never rely on the default
Server in Shanghai, database storing local time, user in the US — the recipe behind nearly every "off by 8 hours / one day" bug. Storing UTC eliminates the class.

Friends & Partners