PHP DateTime:: Diff ошибочно?

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'. Прошло DST. UTC time = 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. И, как мы рассчитываем на наших пальцах, если мы используем аналоговые часы. Более того, когда мы вычисляем разницу между двумя временными метками UTC, я получил PHP собственную логику смещений (!).

Это PHP или мой мозг перестает работать правильно? Выговор любого из вас всех богов, которые существуют на этом сайте, сделает меня такой счастливой!

Я использую PHP 5.4 (VC9) на Apache-сервере. К сожалению, я использую Windows 7 x64 в качестве ОС. Я тестировал свою настройку против всех утверждений об ошибках в PHP-классах Date/Time (есть пара, связанная с Windows) и может подтвердить, что в моей системе нет ни одного из них. За исключением вышеуказанного кода, я не обнаружил никаких других ошибок. Я в значительной степени подтвердил весь код и вывел книгу "Руководство по PHP Architect для даты и времени". Поэтому я должен заключить, что моя мозговая ведьма дефолтна, но я подумал, что сначала отдам ее первой.

Ответ 1

Вы правы, PHP в настоящее время не обрабатывает переходы DST...

Отчеты об ошибках # 51051 (все еще открыт) и # 55253 (исправлено в PHP 5.3.9) описывают проблемы, которые у вас есть.

Даниэль Конвисор написал RFC, пытающийся решить проблему некоторое время назад, но журналы изменений не предполагают, что это было рассмотрено, Я надеялся, что это будет зафиксировано в 5.4, но я не вижу никаких доказательств.

Когда/если оно реализовано, похоже, что вам нужно будет добавить "DST" или "ST" к строке времени.

Лучшей практикой является выполнение всех ваших вычислений даты в формате UTC, что позволяет избежать этой проблемы.

Это сообщение о лучшей практике DST также очень информативно.

Ответ 2

Получайте удовольствие от этого списка связанных ошибок в классе PHP DateTime, большинство из которых были открыты в течение двух лет:

Ответ 3

Хорошо, что у меня класс обертки. Он вычисляет пройденное в реальном времени. Сначала он сравнивает смещения от UTC и добавляет или вычитает эту временную разницу в объект datetime, переданный в качестве аргумента. После этого не нужно ничего делать, кроме как вызвать parent:: diff. Ну ладно, мне нужно было ввести один лайнер, чтобы взломать то, что может быть еще одной ошибкой в ​​PHP (см. Исходный код ниже). Метод DateTimeDiff: diff вычисляет пройденное время REAL. Чтобы понять, что это значит, я советую вам протестировать этот класс с использованием разных дат и времени и помочь вашей рабочей нагрузке. Я также включил в нижнюю часть этого комментария довольно простую 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 fuck 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>