2012-03-14 102 views
11

DateTime :: Diff應計算適當的時間間隔並考慮夏令時(DST)和閏年。雖然顯然不是這樣。恐怖編號:PHP的日期時間::差異得到它錯誤?

$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時間= 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還是讓我的大腦停止正常工作?來自你們任何人的譴責,這個網站上存在的所有神靈都會讓我如此開心!

我在Apache服務器上使用PHP 5.4(VC9)。不幸的是我使用Windows 7 x64作爲操作系統。我已經測試了我的設置,針對PHP日期/時間類中的所有錯誤聲明(有幾個與Windows相關),並且可以確認我的系統沒有它們。除了上述代碼,我還沒有發現任何其他錯誤。我幾乎驗證了所有的代碼,並輸出了「PHP架構的日期和時間編程指南」所提供的書。因此,我必須得出結論,它必須是我的大腦女巫默認,但我想我會先在這裏拍攝。

+1

同上5.3.6在OS X和5.3.9在Ubuntu – Phil 2012-03-14 05:31:43

+0

同樣效果'的DateTime ::子()'和'日期時間:: add()' – Phil 2012-03-14 05:46:11

回答

9

你說得對,PHP目前不處理DST轉換...

錯誤報告#51051(仍處於打開狀態)和#55253(固定在PHP 5.3.9)描述您遇到的問題。

丹尼爾Convissor已經寫了an RFC trying to address the issue一段時間後,但更改日誌並不表明這已被解決。我希望這會在5.4中確定,但我沒有看到任何證據。

當/如果它被實現,看起來你必須在時間字符串中附加「DST」或「ST」。

最佳做法是按照UTC來完成所有日期的計算,即,這可以避免此問題。

This DST best practices post也是非常翔實的。

+1

非常感謝Jonathan,非常感謝。在你連接的RFC中,它說:「在5.4之前解決這些問題似乎是明智的」。因爲我已經使用5.4完成了所有測試,所以我必須得出結論:它從未發生過。 我已經郵寄丹尼爾和德里克,並且正在最大限度地追求這個問題。對於我正在處理的應用程序來說,它正確地獲得時間差異非常重要。我會繼續寫下我自己的課程,它擴展了DateTime並覆蓋了diff方法以產生準確的結果。 無論何時我有新信息,我都會回覆! – 2012-03-14 14:04:04

+0

請注意,我們正在將DateTime擴展到這裏,以便將大量的hack寫入modify()等。 – taiganaut 2012-05-17 00:06:45

+1

...並且由於https://bugs.php.net/bug.php?id=61955等原因,我又回到了一年。 PHP的熵是單向的;它只會變得更糟。 – taiganaut 2013-03-19 20:42:09

1

好吧我有一個包裝類工作。它計算實時通過。首先,它會比較UTC的偏移量,並將該時間差加到或減去作爲參數傳遞的datetime對象。此後,它不需要做任何事情就可以調用parent :: diff。好吧,我需要引入一行代碼來攻擊PHP中可能又一個bug(請參閱下面的源代碼)。 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> 
4