使用不正确时区的日期时间


DateTime using incorrect timezone

在我的应用程序中,我正在尝试计算距离格林威治标准时间午夜(英国时间)的时间。目前我正在这样做:

$now = new DateTime();
$timeToMidnight = $now->setTimezone(new DateTimeZone('Europe/London'))->diff(new DateTime('tomorrow'))->format('%h hours, %i minutes and %s seconds');

代码正在工作,但它似乎落后了一个小时(使用 GMT -1)。目前时间是晚上 11:49,输出如下:

1 hours, 10 minutes and 36 seconds

我已经仔细检查了我的php.ini并且还将时区设置为GMT:

date.timezone = Europe/London

这也通过检查phpinfo()来确认。

什么给?为什么我的应用程序没有使用正确的时区?

我在Linux PHP 5.5.5上对此进行了测试,Europe/London设置为php.ini的时区。实际上,我也把时钟拨回了四个小时。我用来重现的最小代码是:

$d = new DateTime('tomorrow');
echo $d->format('c e');

(正确的)输出是:

2013-10-27T00:00:00+01:00 Europe/London

我将寻找PHP中的错误或时区数据中的错误。为了找出哪个,我们将看看今晚伦敦午夜的其他节目。Epoch Converter告诉我这应该有一个1382828400的Unix时间戳。为了仔细检查该时间戳,我在 PHP 中运行:

$d = new DateTime('27-10-2013');
echo $d->format('U'); 

它也返回了1382828400。所以,让我们看看它应该显示什么...

TZ=Europe/London date --date="@1382828400" +%c

输出为:

Sun 27 Oct 2013 12:00:00 AM BST

正确!所以 tzdata 很好。那么让我们看看PHP。

我运行了您的示例代码以及 date 命令,并得到了以下输出:

1 hours, 29 minutes and 53 seconds
Sat Oct 26 21:30:07 UTC 2013
Sat Oct 26 22:30:07 BST 2013

这当然是正确的。

我认为在这一点上,我们已经排除了tzdata和PHP中的错误,并且需要查看配置问题和程序员的期望。


首先,正如我之前提到的,欧洲/伦敦不是UTC,它没有夏令时的概念,因此每年不会改变两次。由于它不会导致此类问题,因此无论用户位于哪个时区,服务器都可以在 UTC 上运行,并且程序在内部使用 UTC,然后转换为本地时区,仅用于显示和用户输入,这是程序在内部使用 UTC 的最佳做法。

我最好的猜测是,您的服务器运行的PHP实际上设置为使用UTC而不是欧洲/伦敦作为其默认时区。这是我可以重现您的问题的唯一配置。该测试的结果是:

date.timezone = UTC
2 hours, 24 minutes and 36 seconds
Sat Oct 26 21:35:24 UTC 2013
Sat Oct 26 22:35:24 BST 2013

展望未来,您应该尽可能使用 UTC(并使用 Unix 时间戳),并在处理用户输入时尽早转换为本地时间,并在显示时尽可能晚地转换为本地时间。像这样的边缘情况,夏令时即将结束,可能是一个例外,但你必须格外小心,以确保你构建的每个新DateTime对象在构造它时都设置了正确的时区,并且还要注意它们会有这样的问题。

另请参阅内容丰富且信息丰富的夏令时和时区最佳实践


最后,为了"修复"你的代码,让我们这样做:

$tz = new DateTimeZone('Europe/London');
$now = new DateTime('now', $tz);
$midnight = new DateTime('tomorrow', $tz);
$timeToMidnight = $now->diff($midnight);
echo $timeToMidnight->format('%h hours, %i minutes and %s seconds');