PHP Timezones
Timezone identifiers, UTC offsets, and principles for multi-timezone systems.
Key PointsA timezone is not just an offset — DST rules matter. Use identifiers, not `+08:00`.
Common identifiers
| Identifier | Offset | Note |
|---|---|---|
| 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:00One 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:00A 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
- Store UTC —— one
DATETIMEin UTC, or a raw timestamp - Transmit ISO 8601 —— offset included, unambiguous
- Convert on display —— per user preference or browser timezone
- **Avoid
date()for business logic** —— compareDateTimeobjects - **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.
常用时区标识符
| 标识符 | 偏移 | 说明 |
|---|---|---|
| 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 | 南半球夏令时 |
获取与转换
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同一个 UTC 时刻,三个时区给出三个不同的本地时间,其中纽约还退到了前一天。这就是「日期」在跨时区系统里必须谨慎对待的原因。
夏令时
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 小时夏令时开始那天只有 23 小时。任何「一天 = 86400 秒」的假设都会在这里出错——按天加减要用
modify("+1 day"),不要 +86400。偏移与标识符的区别
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用 -05:00 创建的日期永远停在 -05:00,第二天本应是 -04:00 也不会变。**时区必须用标识符(Area/City),偏移只用于显示。**
处理原则
- 存 UTC —— 数据库统一
DATETIME存 UTC 或直接存时间戳 - 传 ISO 8601 ——
2023-11-15T06:13:20+08:00,带偏移不含糊 - 显示时转换 —— 按用户偏好或浏览器时区渲染
- **别用
date()做业务判断** —— 用DateTime对象比较 - **配置
date.timezone** —— 设成 UTC,别依赖默认
服务器在
Asia/Shanghai、数据库存本地时间、用户在美国——这套组合几乎是所有「时间显示错 8 小时 / 差一天」bug 的根源。统一存 UTC 能一次性消灭这一类问题。