我在 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告诉我这应该有一个 Unix 时间戳 1382828400。为了仔细检查那个时间戳,我在 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 然后转换为本地时区/从本地时区转换为仅显示和用户输入。
我最好的猜测是,您的服务器运行的 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');