PHP DateTime :: Diff得到错了?

时间:2012-03-14 04:19:40

标签: php datetime diff dts

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时间= 1h - 2h = 23:05:00。

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

打印'1'。 DST发生了。 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的日期/时间类中的所有错误声明测试了我的设置(有一些与Windows相关),并且可以确认我的系统没有。除了上述代码,我没有发现任何其他错误。我几乎验证了所有代码并输出了“PHP Architect的日期和时间编程指南”这本书。因此,我必须得出结论,它必须是我的大脑女巫违约,但我想我先在这里拍摄。

3 个答案:

答案 0 :(得分:9)

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

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

丹尼尔·康维索尔(Daniel Convissor)曾经写过an RFC trying to address the issue,但改变日志并没有暗示这已经得到了解决。我希望这可以在5.4中修复,但我没有看到任何证据。

当/如果它被实现,看起来你必须将“DST”或“ST”附加到时间字符串。

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

This DST best practices post也非常有用。

答案 1 :(得分:4)

答案 2 :(得分:1)

好吧,我有一个包装类正在工作。它计算实时传递。首先,它比较UTC的偏移量,并将此时间差加到或减去作为参数传递的datetime-object。此后,除了调用parent :: diff之外,它不需要做任何事情。好吧,我需要引入一个单行程来破解PHP中的另一个错误(参见下面的源代码)。 DateTimeDiff:diff方法计算传递的实际时间。为了理解这意味着什么,我建议你使用各种不同的日期和时间测试这个类,并帮助你的工作量我在这个评论的底部还包括一个我写的相当简单的HTML页面。这个链接可以是一个很好的起点,可以获得日期和时间组合的一些想法:

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

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

你在这里,班级:

<?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>