PHP's DateTime::Diff gets it wrong?


PHP's DateTime::Diff gets it wrong?

DateTime::Diff应计算一个适当的时间间隔,并考虑夏令时(DST)和闰年。尽管显然不是这样。恐怖代码:

$d1 = new DateTime("2011-10-30 01:05:00", new DateTimeZone("Europe/Stockholm"));
$d2 = new DateTime("2011-10-30 03:05:00", new DateTimeZone("Europe/Stockholm"));
echo $d1->getOffset() / (60 * 60);

打印'2'!请记住,UTC时间=1小时-2小时=前一天的23:05:00。

echo $d2->getOffset() / (60 * 60);

打印"1"。夏令时发生了。UTC时间=3h-1h=02:05:00。

$di = $d1->diff($d2);
echo "Hours of DateInterval: " . $di->h;

打印'2'!错误的

$hoursofdiff = ($d2->getTimeStamp() - $d1->getTimeStamp()) / 60 / 60;
echo "Calculated difference in hours: $hoursofdiff";

打印"3"!对的

当时钟在指定的日期转为03:00:00时,所有瑞典人都将时钟拨回一小时,至02:00:00。这意味着01:05到03:05之间传递的总量是三个小时,很像使用UNIX TimeStamp时的手动计算回声。就像我们用手指计算一样,如果我们使用模拟时钟。当我们使用PHP自己的偏移逻辑(!)计算两个UTC时间戳之间的差异时,情况更是如此。

是PHP还是我的大脑停止正常工作了?任何一个谴责你们所有存在于这个网站上的神都会让我很高兴!

我在Apache服务器上使用PHP 5.4(VC9)。不幸的是,我使用Windows 7 x64作为操作系统。我已经针对PHP的Date/Time类中的所有错误声明测试了我的设置(有几个与Windows相关),并且可以确认我的系统没有这些错误。除了上述代码,我没有发现任何其他错误。我几乎验证了所有的代码,并输出了《PHP架构师的日期和时间编程指南》一书。因此,我必须得出结论,这一定是我的大脑女巫违约了,但我想我应该先在这里试一试。

你是对的,PHP目前不处理DST转换。。。

错误报告#51051(仍然打开)和#52553(在PHP 5.3.9中修复)描述了您遇到的问题。

Daniel Convissor不久前写了一份RFC,试图解决这个问题,但更改日志并没有表明这个问题已经得到解决。我希望这能在5.4中得到解决,但我看不到任何证据。

当/如果实现了它,看起来您必须将"DST"或"ST"附加到时间字符串中。

最佳做法是在UTC中执行所有日期计算,这样可以避免此问题。

这篇夏令时最佳实践的帖子内容也很丰富。

请欣赏PHP DateTime类中的相关bug列表,其中大多数已经开放了两年多:

  • https://bugs.php.net/bug.php?id=40743
  • https://bugs.php.net/bug.php?id=49914
  • https://bugs.php.net/bug.php?id=51051
  • https://bugs.php.net/bug.php?id=51557
  • https://bugs.php.net/bug.php?id=52480
  • https://bugs.php.net/bug.php?id=54340
  • https://bugs.php.net/bug.php?id=54655
  • https://bugs.php.net/bug.php?id=55253
  • https://bugs.php.net/bug.php?id=60873
  • https://bugs.php.net/bug.php?id=60960
  • https://bugs.php.net/bug.php?id=61022
  • https://bugs.php.net/bug.php?id=61311
  • https://bugs.php.net/bug.php?id=61530
  • https://bugs.php.net/bug.php?id=61955

好吧,我有一个包装类在工作。它计算经过的实时时间。首先,它比较与UTC的偏移量,并将此时间差与作为参数传递的日期时间对象相加或相减。此后,它只需要调用parent::diff即可。好吧,好吧,我需要引入一个一行代码来破解PHP中可能存在的另一个错误(请参阅下面的源代码)。DateTimeDiff:diff方法计算通过的REAL时间。为了理解这意味着什么,我建议您使用不同的日期和时间来测试这个类,并帮助您完成工作量。我还在评论的底部添加了一个我写的相当简单的HTML页面。这个链接可能是一个很好的起点,可以获得一些关于日期和时间组合的想法:

https://wiki.php.net/rfc/datetime_and_daylight_saving_time

此外,请注意,当我们在夏令时中进行向后转换时,一些日期/时间组合可能同时属于两个时区。这种模糊性可能会使此类的结果与预期的结果不同。因此,如果您正在认真考虑使用这个类,请进一步开发它,并在这些情况下要求用户澄清。

给你,班级:

<?php
class DateTimeDiff extends DateTime
{
    public function diff($datetime, $absolute = false)
    {
    // Future releases could fix this bug and if so, this method would become counterproductive.
    if (version_compare(PHP_VERSION, '5.4.0') > 0)
        trigger_error("You are using a PHP version that might have adressed the problems of DateTime::diff", E_USER_WARNING);
    // Have the clock changed?
    $offset_start = $this->getOffset();
    $offset_end   = $datetime->getOffset();
    if ($offset_start != $offset_end)
    {
        // Remember the difference.
        $clock_moved = $offset_end - $offset_start;
        // We wouldn't wanna mess things up for our caller; thus work on a clone.
        $copy = clone $datetime;

        if ($clock_moved > 0)
        {
            $timestamp_beforesub = $copy->getTimestamp();
            // Subtract timedifference from end-datetime should make parent::diff produce accurate results.
            $copy->sub( DateInterval::createFromDateString("$clock_moved seconds") );
            // No change occured; sometimes sub() fails. This is a workable hack.
            if ($timestamp_beforesub == $copy->getTimestamp())
                $copy->setTimezone(new DateTimeZone("UTC"));
        }
        else // ..else < 0 and its a negative.
        {
            $clock_moved *= -1;
            // Adding that timedifference to end-datetime should make parent::diff produce accurate results.
            $copy->add( DateInterval::createFromDateString("$clock_moved seconds") );
        }
        return parent::diff($copy, $absolute);
    } // <-- END "if ($offset_start != $offset_end)"
    return parent::diff($datetime, $absolute);
    }
}
?>

和一个用于测试的页面(将同时使用DateTime::diff和DateTimeDiff::diff显示结果):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>DateTimeDiff-class</title>
<?php
if (! (empty($_GET['identifier']) && empty($_GET['start']) && empty($_GET['end'])))
{
    $dt1_new = new DateTimeDiff("{$_GET['start']} {$_GET['identifier']}");
    $dt1_old = new DateTime("{$_GET['start']} {$_GET['identifier']}");
    $dt2 = new DateTime("{$_GET['end']} {$_GET['identifier']}");
    $di_new = $dt1_new->diff($dt2);
    $di_old = $dt1_old->diff($dt2);

    // Extract UNIX timestamp and transitional data
    $timezone_start = $dt1_new->getTimezone();
    $timezone_end = $dt2->getTimezone();
    $timestamp_start = $dt1_new->getTimeStamp();
    $timestamp_end = $dt2->getTimeStamp();
    $transitions_start = $timezone_start->getTransitions($timestamp_start, $timestamp_start);
    $transitions_end = $timezone_end->getTransitions($timestamp_end, $timestamp_end);
    echo <<<BUILDCONTAINER
    <script type='text/javascript'>
        function Container() { }
        var c_new = new Container;
        var c_old = new Container;
        var t_start = new Container;
        var t_end = new Container;
    </script>
BUILDCONTAINER;
    echo <<<SETTRANSITIONS
    <script type='text/javascript'>
        t_start.ts = '{$transitions_start[0]['ts']}';
        t_start.time = '{$transitions_start[0]['time']}';
        t_start.offset = '{$transitions_start[0]['offset']}';
        t_end.ts = '{$transitions_end[0]['ts']}';
        t_end.time = '{$transitions_end[0]['time']}';
        t_end.offset = '{$transitions_end[0]['offset']}';
    </script>
SETTRANSITIONS;
    foreach ($di_new as $property => $value)
        echo "<script type='text/javascript'>c_new.$property = $value</script>";
    foreach ($di_old as $property => $value)
        echo "<script type='text/javascript'>c_old.$property = $value</script>";
}
?>
<script type='text/javascript'>
window.onload = function()
{
    if (c_new != null) // <-- em assume everything else is valid too.
    {
        // Update page with the results
        for (var prop in c_new)
            addtext(prop + ": " + c_new[prop] + " (" + c_old[prop] + ")");
        addtext("Read like so..");
        addtext("PROPERTY of DateInterval: VALUE using DateTimeDiff::diff  (  VALUE using DateTime::diff  )");
        // Restore values sent/recieved
        <?php
            foreach ($_GET as $key => $value)
                echo "document.getElementById('$key').value = '$value';";
        ?>
        // Display transitiondata (For DateTime start)
        var p_start = document.getElementById('p_start');
        var appendstring = "TS: " + t_start.ts + ", Time: " + t_start.time + ", Offset: " + t_start.offset;
        p_start.appendChild(document.createTextNode(appendstring));
        // Display transitiondata (For DateTime end)
        var p_end = document.getElementById('p_end');
        appendstring = "TS: " + t_end.ts + ", Time: " + t_end.time + ", Offset: " + t_end.offset;
        p_end.appendChild(document.createTextNode(appendstring));
    }
}
function addtext()
{
    var p = document.createElement("p");
    p.appendChild(document.createTextNode(arguments[0]));
    document.forms[0].appendChild(p);
}
</script>
</head>
<body>
<form action="test2.php" method="get">
    <p>Identifier: <input type="text" name="identifier" id="identifier" value="Europe/Stockholm" /></p>
    <p id="p_start">Start: <input type="text" name="start" id="start" /></p>
    <p id="p_end">End: <input type="text" name="end" id="end" /></p>
    <p><input type="submit" /></p>
</form>
</body>
</html>