2012-03-14 28 views
11

DateTime :: Diff cần tính toán một khoảng thời gian thích hợp và tính đến thời gian tiết kiệm ánh sáng ban ngày (DST) và năm nhuận. Mặc dù rõ ràng nó không phải như vậy. Mã kinh dị:DateTime của PHP :: Diff hiểu sai?

$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); 

In '2'! Hãy nhớ rằng thời gian UTC = 1h - 2h = 23:05:00 ngày hôm trước.

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

In '1'. DST đã xảy ra. Thời gian UTC = 3h - 1h = 02:05:00.

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

In '2'! Sai rồi?

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

In '3'! Chính xác?

Khi đồng hồ quay 03:00:00 tại ngày đã cho, tất cả người Thụy Điển đã quay đồng hồ của họ sau một giờ đến 02:00:00. Điều đó có nghĩa là tổng số tiền được chuyển từ 01:05 đến 03:05 là ba giờ, giống như việc tính toán thủ công được lặp lại khi sử dụng UNIX TimeStamp. Và giống như chúng ta tính toán trên ngón tay của mình nếu chúng ta sử dụng đồng hồ tương tự. Thậm chí nhiều hơn như vậy khi chúng tôi tính toán sự khác biệt giữa hai dấu thời gian UTC tôi đã sử dụng logic riêng của PHP của các offset (!).

Có phải PHP hoặc bộ não của tôi đã ngừng hoạt động đúng không? Một sự khiển trách từ bất cứ ai trong các bạn, tất cả các vị thần tồn tại trên trang web này sẽ làm tôi rất hạnh phúc!

Tôi đang sử dụng PHP 5.4 (VC9) trên máy chủ Apache. Thật không may, tôi sử dụng Windows 7 x64 làm hệ điều hành. Tôi đã thử nghiệm thiết lập của tôi chống lại tất cả các tuyên bố lỗi trong các lớp Date/Time của PHP (có một vài liên quan đến Windows) và có thể xác nhận rằng hệ thống của tôi không có chúng. Ngoại trừ đoạn mã trên tôi đã không tìm thấy bất kỳ lỗi nào khác. Tôi đã kiểm tra khá nhiều mã và xuất ra cuốn sách "Hướng dẫn của Kiến trúc sư PHP về lập trình ngày tháng và thời gian" đã cung cấp. Vì vậy, tôi phải kết luận nó phải là phù thủy não của tôi đã mặc định nhưng tôi nghĩ rằng tôi muốn cho nó một shoot ở đây đầu tiên.

+1

Cùng trên 5.3.6 trên OS X và 5.3.9 trên Ubuntu – Phil

+0

Ngoài ra hiệu ứng 'DateTime :: sub() 'và' DateTime :: add() ' – Phil

Trả lời

9

Bạn nói đúng, PHP hiện không xử lý chuyển tiếp DST ...

báo cáo Bug #51051 (vẫn mở) và #55253 (cố định trong PHP 5.3.9) mô tả các vấn đề bạn đang gặp phải.

Daniel Convissor đã viết an RFC trying to address the issue một lúc trước nhưng nhật ký thay đổi không đề xuất điều này đã được giải quyết. Tôi đã hy vọng điều này sẽ cố định trong 5.4 nhưng tôi không thấy bất kỳ bằng chứng nó đã được.

Khi/nếu được triển khai, có vẻ như bạn sẽ phải thêm "DST" hoặc "ST" vào chuỗi thời gian.

Thực tiễn tốt nhất là thực hiện tất cả các tính toán ngày ngày của bạn ở UTC, tránh vấn đề này.

This DST best practices post cũng rất thông tin.

+1

Cảm ơn bạn rất nhiều Jonathan, thực sự đánh giá cao nó. Trong RFC bạn liên kết, nó nói "Bắt những vấn đề này thẳng ra trước khi 5.4 [..] có vẻ khôn ngoan". Bởi vì tôi đã thực hiện tất cả các thử nghiệm của tôi bằng cách sử dụng 5.4 Tôi phải kết luận rằng nó không bao giờ xảy ra. Tôi đã gửi cả hai Daniel và Derick, và đang theo đuổi vấn đề này đến mức tối đa của nó. Nó là vô cùng quan trọng đối với ứng dụng tôi đang làm việc ngay bây giờ mà nó nhận được sự khác biệt thời gian đúng cách. Tôi sẽ tiếp tục và viết lớp của riêng tôi để mở rộng DateTime và ghi đè phương thức khác để tạo ra các kết quả chính xác. Tôi sẽ đăng lại bất cứ khi nào tôi có thông tin mới! –

+0

Hãy làm, chúng tôi đang trên bờ vực mở rộng DateTime ở đây để viết một tấn hack vào sửa đổi(), trong số những người khác. – taiganaut

+1

... và tôi quay lại sau một năm do https://bugs.php.net/bug.php?id=61955 trong số những người khác. Entropy của PHP là một chiều; nó chỉ trở nên tồi tệ hơn. – taiganaut

1

Được rồi tôi có một lớp trình bao bọc hoạt động. Nó tính toán thời gian thực trôi qua. Đầu tiên nó so sánh các offset từ UTC và cộng hoặc trừ chênh lệch thời gian này với đối tượng datetime-được truyền vào như một đối số. Sau đó, nó không cần phải làm gì hơn là gọi cha mẹ :: diff. Vâng ok tôi cần phải giới thiệu một lớp lót để hack những gì có thể là một lỗi khác trong PHP (xem mã nguồn dưới đây). Phương thức DateTimeDiff: diff tính thời gian REAL.Để hiểu điều đó có nghĩa là gì, tôi khuyên bạn nên kiểm tra lớp này bằng nhiều ngày và thời gian khác nhau và để hỗ trợ khối lượng công việc của bạn. Tôi cũng đưa vào phần dưới của nhận xét này một trang HTML khá đơn giản mà tôi đã viết. Liên kết này có thể là một điểm khởi đầu tốt để có được một số ý tưởng cho ngày và thời gian kết hợp:

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

Hơn nữa, hãy lưu ý rằng khi chúng ta có một sự chuyển đổi ngược trong DST, một số kết hợp ngày/giờ có thể thuộc về cả hai Múi giờ. Sự mơ hồ này có thể làm cho kết quả của lớp này khác với những gì được mong đợi. Vì vậy, nếu bạn đang suy nghĩ nghiêm túc về việc sử dụng lớp này, hãy phát triển nó hơn nữa và yêu cầu làm rõ người dùng trong những trường hợp này.

Ở đây bạn đang có, các lớp:

<?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); 
    } 
} 
?> 

Và một trang để thử nghiệm (sẽ hiển thị kết quả sử dụng cả DateTime :: diff và 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
Các vấn đề liên quan