代码之家  ›  专栏  ›  技术社区  ›  Iter Ator

如何在php中计算日期时间差,它也适用于夏令时?[副本]

  •  4
  • Iter Ator  · 技术社区  · 6 年前

    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时间=3小时-1小时=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时间戳时的手动计算。就像我们用手指计算模拟时钟一样。更重要的是,当我们使用PHP自己的偏移逻辑(!)计算两个UTC时间戳之间的差异时。

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

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

    0 回复  |  直到 11 年前
        1
  •  9
  •   Community Mike Causer    7 年前

    你说得对,PHP目前不处理DST转换。。。

    错误报告 #51051 (仍然开放)和 #55253 (在php5.3.9中修复)描述您遇到的问题。

    丹尼尔·康维索写过 an RFC trying to address the issue 不久前,但更改日志并不表明这一问题已经得到解决。我希望这能在5.4中得到修复,但我没有看到任何证据。

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

    最好的做法是 UTC日期计算 ,从而避免了此问题。

    This DST best practices post 信息量也很大。

        3
  •  1
  •   Martin Andersson    5 年前

    好吧,我有一个包装器类工作。它计算实时传递。首先,它比较UTC的偏移量,并将此时间差与作为参数传递的datetime对象相加或相减。此后,它只需要调用parent::diff就可以了。好吧,我需要引入一个一行程序来破解PHP中的另一个bug(请参阅下面的源代码)。DateTimeDiff:差分法计算实时传递。为了理解这意味着什么,我建议您使用各种不同的日期和时间来测试这个类,并且为了帮助您的工作量,我在这个注释的底部还包括了一个我编写的非常简单的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>