在某些相对时间不考虑 DST
DST not accounted for in certain relative times
上周日,欧盟从 CET (+0100) 切换到 CEST (+0200)。我正在编写代码以将增量应用到日期,但它无法正常工作,因为时区转换仅在某些相关格式中得到适当考虑:
'+x minutes'
省略缺失的小时
'+x hours'
没有
这是我的测试代码:
echo 'Time zone database: ' . timezone_version_get() . PHP_EOL;
echo PHP_EOL;
date_default_timezone_set('Europe/Madrid');
$start = new DateTime('2017-03-26 01:59:00');
$increments = array(
'+2 minutes' => '2017-03-26 03:01:00',
'+2 hours' => '2017-03-26 04:59:00',
);
echo 'Start: ' . $start->format('r') . PHP_EOL;
foreach ($increments as $increment => $expected_string) {
echo '>>> ' . $increment . PHP_EOL;
$expected_end = new DateTime($expected_string);
$actual_end = clone $start;
$actual_end->modify($increment);
echo 'Expected end: ' . $expected_end->format('r') . PHP_EOL;
echo 'Actual end: ' . $actual_end->format('r') . PHP_EOL;
echo ($expected_end->format('c')===$actual_end->format('c') ? 'OK' : 'ERROR') . PHP_EOL;
echo PHP_EOL;
}
Time zone database: 2016.3
Start: Sun, 26 Mar 2017 01:59:00 +0100
>>> +2 minutes
Expected end: Sun, 26 Mar 2017 03:01:00 +0200
Actual end: Sun, 26 Mar 2017 03:01:00 +0200
OK
>>> +2 hours
Expected end: Sun, 26 Mar 2017 04:59:00 +0200
Actual end: Sun, 26 Mar 2017 03:59:00 +0200
ERROR
由于 relative formats 通常如此违反直觉,我不确定我是否得到了一些记录在案的行为或者这是一个错误。
你能解释一下吗?
这不可能是相对格式的误解,因为在相同格式下行为是不稳定的:
date_default_timezone_set('Europe/Madrid');
$start = new DateTime('2017-03-26 01:59:00');
$increments = array(
'+60 minutes' => '2017-03-26 03:59:00',
'+61 minutes' => '2017-03-26 04:00:00',
);
echo 'Start: ' . $start->format('r') . PHP_EOL;
foreach ($increments as $increment => $expected_string) {
echo '>>> ' . $increment . PHP_EOL;
$expected_end = new DateTime($expected_string);
$actual_end = clone $start;
$actual_end->modify($increment);
echo 'Expected end: ' . $expected_end->format('r') . PHP_EOL;
echo 'Actual end: ' . $actual_end->format('r') . PHP_EOL;
echo ($expected_end->format('c')===$actual_end->format('c') ? 'OK' : 'ERROR') . PHP_EOL;
echo PHP_EOL;
}
Start: Sun, 26 Mar 2017 01:59:00 +0100
>>> +60 minutes
Expected end: Sun, 26 Mar 2017 03:59:00 +0200
Actual end: Sun, 26 Mar 2017 03:59:00 +0200
OK
>>> +61 minutes
Expected end: Sun, 26 Mar 2017 04:00:00 +0200
Actual end: Sun, 26 Mar 2017 03:00:00 +0200
ERROR
换句话说,相加 61 分钟比相加 60 得到更早的日期。
简而言之,PHP 没有正确处理时区转换。 2011 年的 issue ticket that acknowledges it and even an RFC 分析了可能的修复方法。
(此信息归功于 @Alex Blex。)
值得注意的是,基于 Unix 时间戳的旧函数也受到影响:
<?php
date_default_timezone_set('Europe/Madrid');
$start = strtotime('2017-03-26 01:59:00');
$increments = array(
'+60 minutes' => '2017-03-26 03:59:00',
'+61 minutes' => '2017-03-26 04:00:00',
);
echo 'Start: ' . date('r', $start) . PHP_EOL;
foreach ($increments as $increment => $expected_string) {
echo '>>> ' . $increment . PHP_EOL;
$expected_end = strtotime($expected_string);
$actual_end = strtotime($increment, $start);
echo 'Expected end: ' . date('r', $expected_end) . PHP_EOL;
echo 'Actual end: ' . date('r', $actual_end) . PHP_EOL;
echo ($expected_end===$actual_end ? 'OK' : 'ERROR') . PHP_EOL;
echo PHP_EOL;
}
Start: Sun, 26 Mar 2017 01:59:00 +0100
>>> +60 minutes
Expected end: Sun, 26 Mar 2017 03:59:00 +0200
Actual end: Sun, 26 Mar 2017 03:59:00 +0200
OK
>>> +61 minutes
Expected end: Sun, 26 Mar 2017 04:00:00 +0200
Actual end: Sun, 26 Mar 2017 03:00:00 +0200
ERROR
解决方法
当然是使用 UTC :)
您可以在内部使用 UTC 进行所有计算,也可以在执行日期数学运算之前切换到 UTC。后者(最冗长的情况)意味着类似:
<?php
date_default_timezone_set('Europe/Madrid');
$start = new DateTime('2017-03-26 01:59:00');
$increments = array(
'+60 minutes' => '2017-03-26 03:59:00',
'+61 minutes' => '2017-03-26 04:00:00',
);
echo 'Start: ' . $start->format('r') . PHP_EOL;
$local = $start->getTimezone();
$utc = new DateTimeZone('UTC');
foreach ($increments as $increment => $expected_string) {
echo '>>> ' . $increment . PHP_EOL;
$expected_end = new DateTime($expected_string);
$actual_end = clone $start;
$actual_end->setTimezone($utc);
$actual_end->modify($increment);
$actual_end->setTimezone($local);
echo 'Expected end: ' . $expected_end->format('r') . PHP_EOL;
echo 'Actual end: ' . $actual_end->format('r') . PHP_EOL;
echo ($expected_end->format('c')===$actual_end->format('c') ? 'OK' : 'ERROR') . PHP_EOL;
echo PHP_EOL;
}
Start: Sun, 26 Mar 2017 01:59:00 +0100
>>> +60 minutes
Expected end: Sun, 26 Mar 2017 03:59:00 +0200
Actual end: Sun, 26 Mar 2017 03:59:00 +0200
OK
>>> +61 minutes
Expected end: Sun, 26 Mar 2017 04:00:00 +0200
Actual end: Sun, 26 Mar 2017 04:00:00 +0200
OK
如果您在任何地方都使用 UTC,则不需要任何这些,只需要在向最终用户显示时最后 ->setTimezone()
。
上周日,欧盟从 CET (+0100) 切换到 CEST (+0200)。我正在编写代码以将增量应用到日期,但它无法正常工作,因为时区转换仅在某些相关格式中得到适当考虑:
'+x minutes'
省略缺失的小时'+x hours'
没有
这是我的测试代码:
echo 'Time zone database: ' . timezone_version_get() . PHP_EOL;
echo PHP_EOL;
date_default_timezone_set('Europe/Madrid');
$start = new DateTime('2017-03-26 01:59:00');
$increments = array(
'+2 minutes' => '2017-03-26 03:01:00',
'+2 hours' => '2017-03-26 04:59:00',
);
echo 'Start: ' . $start->format('r') . PHP_EOL;
foreach ($increments as $increment => $expected_string) {
echo '>>> ' . $increment . PHP_EOL;
$expected_end = new DateTime($expected_string);
$actual_end = clone $start;
$actual_end->modify($increment);
echo 'Expected end: ' . $expected_end->format('r') . PHP_EOL;
echo 'Actual end: ' . $actual_end->format('r') . PHP_EOL;
echo ($expected_end->format('c')===$actual_end->format('c') ? 'OK' : 'ERROR') . PHP_EOL;
echo PHP_EOL;
}
Time zone database: 2016.3
Start: Sun, 26 Mar 2017 01:59:00 +0100
>>> +2 minutes
Expected end: Sun, 26 Mar 2017 03:01:00 +0200
Actual end: Sun, 26 Mar 2017 03:01:00 +0200
OK
>>> +2 hours
Expected end: Sun, 26 Mar 2017 04:59:00 +0200
Actual end: Sun, 26 Mar 2017 03:59:00 +0200
ERROR
由于 relative formats 通常如此违反直觉,我不确定我是否得到了一些记录在案的行为或者这是一个错误。
你能解释一下吗?
这不可能是相对格式的误解,因为在相同格式下行为是不稳定的:
date_default_timezone_set('Europe/Madrid');
$start = new DateTime('2017-03-26 01:59:00');
$increments = array(
'+60 minutes' => '2017-03-26 03:59:00',
'+61 minutes' => '2017-03-26 04:00:00',
);
echo 'Start: ' . $start->format('r') . PHP_EOL;
foreach ($increments as $increment => $expected_string) {
echo '>>> ' . $increment . PHP_EOL;
$expected_end = new DateTime($expected_string);
$actual_end = clone $start;
$actual_end->modify($increment);
echo 'Expected end: ' . $expected_end->format('r') . PHP_EOL;
echo 'Actual end: ' . $actual_end->format('r') . PHP_EOL;
echo ($expected_end->format('c')===$actual_end->format('c') ? 'OK' : 'ERROR') . PHP_EOL;
echo PHP_EOL;
}
Start: Sun, 26 Mar 2017 01:59:00 +0100
>>> +60 minutes
Expected end: Sun, 26 Mar 2017 03:59:00 +0200
Actual end: Sun, 26 Mar 2017 03:59:00 +0200
OK
>>> +61 minutes
Expected end: Sun, 26 Mar 2017 04:00:00 +0200
Actual end: Sun, 26 Mar 2017 03:00:00 +0200
ERROR
换句话说,相加 61 分钟比相加 60 得到更早的日期。
简而言之,PHP 没有正确处理时区转换。 2011 年的 issue ticket that acknowledges it and even an RFC 分析了可能的修复方法。
(此信息归功于 @Alex Blex。)
值得注意的是,基于 Unix 时间戳的旧函数也受到影响:
<?php
date_default_timezone_set('Europe/Madrid');
$start = strtotime('2017-03-26 01:59:00');
$increments = array(
'+60 minutes' => '2017-03-26 03:59:00',
'+61 minutes' => '2017-03-26 04:00:00',
);
echo 'Start: ' . date('r', $start) . PHP_EOL;
foreach ($increments as $increment => $expected_string) {
echo '>>> ' . $increment . PHP_EOL;
$expected_end = strtotime($expected_string);
$actual_end = strtotime($increment, $start);
echo 'Expected end: ' . date('r', $expected_end) . PHP_EOL;
echo 'Actual end: ' . date('r', $actual_end) . PHP_EOL;
echo ($expected_end===$actual_end ? 'OK' : 'ERROR') . PHP_EOL;
echo PHP_EOL;
}
Start: Sun, 26 Mar 2017 01:59:00 +0100
>>> +60 minutes
Expected end: Sun, 26 Mar 2017 03:59:00 +0200
Actual end: Sun, 26 Mar 2017 03:59:00 +0200
OK
>>> +61 minutes
Expected end: Sun, 26 Mar 2017 04:00:00 +0200
Actual end: Sun, 26 Mar 2017 03:00:00 +0200
ERROR
解决方法
当然是使用 UTC :)
您可以在内部使用 UTC 进行所有计算,也可以在执行日期数学运算之前切换到 UTC。后者(最冗长的情况)意味着类似:
<?php
date_default_timezone_set('Europe/Madrid');
$start = new DateTime('2017-03-26 01:59:00');
$increments = array(
'+60 minutes' => '2017-03-26 03:59:00',
'+61 minutes' => '2017-03-26 04:00:00',
);
echo 'Start: ' . $start->format('r') . PHP_EOL;
$local = $start->getTimezone();
$utc = new DateTimeZone('UTC');
foreach ($increments as $increment => $expected_string) {
echo '>>> ' . $increment . PHP_EOL;
$expected_end = new DateTime($expected_string);
$actual_end = clone $start;
$actual_end->setTimezone($utc);
$actual_end->modify($increment);
$actual_end->setTimezone($local);
echo 'Expected end: ' . $expected_end->format('r') . PHP_EOL;
echo 'Actual end: ' . $actual_end->format('r') . PHP_EOL;
echo ($expected_end->format('c')===$actual_end->format('c') ? 'OK' : 'ERROR') . PHP_EOL;
echo PHP_EOL;
}
Start: Sun, 26 Mar 2017 01:59:00 +0100
>>> +60 minutes
Expected end: Sun, 26 Mar 2017 03:59:00 +0200
Actual end: Sun, 26 Mar 2017 03:59:00 +0200
OK
>>> +61 minutes
Expected end: Sun, 26 Mar 2017 04:00:00 +0200
Actual end: Sun, 26 Mar 2017 04:00:00 +0200
OK
如果您在任何地方都使用 UTC,则不需要任何这些,只需要在向最终用户显示时最后 ->setTimezone()
。