2012-01-24 33 views
5

Đây là những gì tôi đã có cho đến nay:Làm thế nào để tính toán sự khác biệt giữa hai ngày như một chuỗi định dạng?

/** 
* Parse a duration between 2 date/times in seconds 
* and to convert that duration into a formatted string 
* 
* @param integer $time_start start time in seconds 
* @param integer $time_end end time in seconds 
* @param string $format  like the php strftime formatting uses %y %m %w %d %h or %i. 
* @param boolean $chop  chop off sections that have 0 values 
*/ 
public static function FormatDateDiff($time_start = 0, $time_end = 0, $format = "%s", $chop = false) { 

     if($time_start > $time_end) list($time_start, $time_end) = array($time_end, $time_start); 

     list($year_start,$month_start,$day_start) = explode('-',date('Y-m-d',$time_start)); 
     list($year_end,$month_end,$day_end) = explode('-',date('Y-m-d',$time_end)); 

     $years = $year_end - $year_start; 
     $months = $month_end - $month_start; 
     $days = $day_start - $day_end; 
     $weeks = 0; 
     $hours = 0; 
     $mins = 0; 
     $secs = 0; 

     if(mktime(0,0,0,$month_end,$day_end) < mktime(0,0,0,$month_start,$day_start)) { 
      $years -= 1; 
     } 
     if($days < 0) { 
      $months -= 1; 
      $days += 30; // this is an approximation...not sure how to figure this out 
     } 
     if($months < 0) $months += 12; 
     if(strpos($format, '%y')===false) { 
      $months += $years * 12; 
     } 
     if(strpos($format, '%w')!==false) { 
      $weeks = floor($days/7); 
      $days %= 7; 
     } 
     echo date('Y-m-d',$time_start).' to '.date('Y-m-d',$time_end).": {$years}y {$months}m {$weeks}w {$days}d<br/>"; 
} 

(Đó là chưa đầy đủ không chính xác)

tôi dường như không thể có được toán đúng. Naively chia nó ra sẽ không hoạt động vì năm nhuận và độ dài khác nhau của tháng.

Logic cũng cần phải thay đổi tùy thuộc vào chuỗi định dạng. Ví dụ: thông qua 04-Feb-2010 đến 28-Jun-2011 (dưới dạng dấu thời gian unix) với chuỗi định dạng %y year %m month %d day sẽ xuất ra 1 year 4 month 24 day nhưng nếu bỏ qua %y year thì cần phải thêm 12 tháng vào tháng, tức là, đầu ra phải là 16 month 24 day.

Nên xử lý thời gian quá ... nhưng tôi chưa đến đó.


Không ai trong số những giải pháp xử lý date_difftuần. Và tôi không biết làm thế nào tôi có thể hack nó vào date_diff, vì vậy đó không thực sự là một giải pháp cho tôi.

Hơn nữa, $diff->format không làm những gì tôi đã yêu cầu ... để cung cấp tổng số tháng và ngày nếu "đơn vị lớn hơn" bị bỏ qua. Ví dụ:

>>> $start = new DateTime('04-Feb-2010') 
>>> $end = new DateTime('28-Jun-2011') 
>>> $diff = $start->diff($end) 
>>> $diff->format('%m months, %d days') 
'4 months, 24 days' 

Nên là 16 months, 24 days, như tôi đã nói trước đó. Vui lòng dừng quá nhanh để đóng câu hỏi của tôi như một bản dupe trước khi bạn hiểu nó đầy đủ. Nếu các giải pháp cho các câu hỏi khác có thể được tinh chỉnh để giải quyết điều này, tốt, nhưng hãy giải thích làm thế nào, bởi vì tôi không nhận được nó.

Để được rõ ràng,

  • nếu %y được bỏ qua, năm nên được triển khai trong những tháng
  • nếu %m được bỏ qua, tháng nên được triển khai vào ngày
  • nếu %w được bỏ qua, tuần nên được cuộn vào các ngày
  • nếu bỏ qua %h, giờ phải được cuộn thành phút
  • nếu %m bị bỏ qua, phút phải được cuộn thành giây

Nếu "đơn vị nhỏ hơn" bị bỏ qua, đơn vị lớn nhất tiếp theo có thể được làm tròn hoặc trải sàn ở nơi có ý nghĩa.

+1

Nếu bạn có PHP 5.3.0 hoặc cao hơn, điều này có thể giúp bạn: [DateInterval :: định dạng] (http://www.php.net/manual/en/dateinterval.format.php) – drew010

+2

trùng lặp của [bài đăng này] (http: // stackoverflow.com/questions/676824/how-to-calculate-the-sự khác biệt-giữa-hai-ngày-sử dụng-php) – bowlerae

+0

@ drew010: Không ... không xử lý hàng tuần. – mpen

Trả lời

1

Tôi không mong đợi câu trả lời được chấp nhận, nhưng dưới đây là cách lấy date_diff để làm tuần.

<?php 

$january = new DateTime('2010-01-01'); 
$february = new DateTime('2011-02-20 3:35:28'); 
$interval = $february->diff($january); 

$parts = $interval->format('%y %m %d %h %i %s %a'); 

$weeks = 0; 
list($years, $months, $days, $hours, $minutes, $seconds, $total_days) = explode(' ', $parts); 

if ($days >= 7) { 
    $weeks = (int)($days/7); 
    $days %= 7; 
} 

echo "$years years, $months months, $weeks weeks, $days days, $hours hours, $minutes minutes $seconds seconds"; 
// 1 years, 1 months, 2 weeks, 5 days, 3 hours, 35 minutes 28 seconds 

Có thể bạn có thể tích hợp nó vào chức năng của bạn để thực hiện và xử lý định dạng do người dùng cung cấp.

Nếu các đơn vị lớn hơn không được đưa ra, bạn có thể bắt đầu từ đơn vị lớn nhất và áp dụng chúng trở lại đơn vị nhỏ hơn tiếp theo. (ví dụ: 1 năm 1 tháng không có năm nên thêm 12 tháng trở lại). Nếu "tháng" không được bao gồm trong định dạng, thì bạn có thể sử dụng tổng số ngày để xử lý thực tế là các tháng có số ngày khác nhau.

+0

Đẹp! Tôi sẽ cố gắng chơi với điều này và xem tôi có thể làm cho nó để làm những gì tôi muốn. Trông đầy hứa hẹn. – mpen

+0

Hy vọng rằng bạn có thể nhận được một nơi nào đó với nó. Tôi luôn luôn cố gắng dựa vào các phương pháp ngày "tích hợp" như vậy bây giờ vì có quá nhiều vấn đề khi giao dịch với việc quan sát ranh giới DST, năm nhuận/giây và các ngày khác nhau trong mỗi tháng, v.v. – drew010

+0

Vâng .... Tôi đã nghịch với 'DateTime' trước đây, nhưng vẫn còn một số vấn đề với nó, vì vậy tôi nói" Vít nó! Tôi sẽ viết nó bản thân mình! ". Rõ ràng đó là cách tiếp cận sai lầm. Của bạn là tốt đẹp và sạch mặc dù. Tôi sẽ chấp nhận điều này :) – mpen

1
$absolute = false; //default, returns years, months, days, etc. True would return seconds 
$difference = date_diff($dateTimeStart, $dateTimeEnd, $absolute); 
echo $difference->format("%y Year(s) %m Month(s) ..."); 

Tôi sẽ thử điều này trước tiên, nếu nó không hoạt động cho mục đích của bạn thì bạn có thể thử sử dụng các chức năng Lịch để tìm đúng số ngày trong mỗi tháng.

Tôi nghĩ bạn sẽ cần phải quyết định logic của riêng mình để tính tuần. Cá nhân, tôi sẽ nói $weeks = $difference->d/7; nhưng không có cách chính xác để tính số tuần trôi qua trong một phần của một tháng. Hãy nghĩ về lịch, chúng ta thường nói về tuần thứ nhất, thứ hai, tuần thứ ba, nhưng trừ khi tháng bắt đầu vào ngày chủ nhật (hoặc thứ hai hoặc thứ bảy, tùy thuộc vào công ty, tôn giáo, v.v) thì thực sự chúng ta không tuyệt đối trong các mô tả này .Bạn có thể nói hoàn toàn rằng đã có 3 ngày chủ nhật (ví dụ) kể từ đầu tháng, nhưng không phải là ba tuần. Vào ngày 28, có bốn tuần trôi qua không? Điều gì xảy ra nếu tháng bắt đầu vào ngày Thứ Tư, vậy thì có phải là năm không?

Ngoài ra, nếu bạn muốn định dạng tùy chỉnh (35 tháng), bạn luôn có thể truy cập trực tiếp các thành viên và nối một chuỗi định dạng.

+0

'date_diff' không xử lý tuần. Bạn có nghĩa là tính toán số ngày trong mỗi tháng giữa 2 ngày và tổng hợp chúng hoặc một cái gì đó như thế? Hoặc chỉ là tháng bắt đầu và kết thúc ..? – mpen

+1

Bạn nói đúng, nó không. Tôi xin lỗi tôi đã không nhận thấy yêu cầu đó. – Tim

+0

Không sao đâu. Yêu cầu đó đã được chôn trong mã; Tôi nên làm cho nó rõ ràng hơn. Nó sẽ không xử lý các yêu cầu "đơn vị lớn hơn". – mpen

1

Tôi sẽ sử dụng DateTime::diff() cho việc này:

$start = new DateTime('2012-01-01 12:00:00'); 
$end = new DateTime('2012-01-20 06:59:59'); 
$diff = $start->diff($end); 

echo $diff->format('%d days, %h hours, %m minutes, %s seconds'); 

Xem DateInterval::format() để biết thêm về các định dạng.

+0

Điều đó không xử lý hàng tuần. – mpen

1

tôi nghĩ Tôi đã có nó:

if($time_start > $time_end) list($time_start, $time_end) = array($time_end, $time_start); 

$start_dt = new DateTime(); 
$end_dt = new DateTime(); 
$start_dt->setTimestamp($time_start); 
$end_dt->setTimestamp($time_end); 
$has_time = preg_match('`%[his]`',$format) > 0; 
if(!$has_time) { 
    $start_dt->setTime(0,0,0); 
    $end_dt->setTime(0,0,0); 
} 
$interval = $end_dt->diff($start_dt); 
$parts = $interval->format('%y %m %d %h %i %s %a'); 
$weeks = 0; 
list($years, $months, $days, $hours, $mins, $secs, $total_days) = explode(' ',$parts); 
if(strpos($format,'%y')===false) { 
    $months += $years * 12; 
} 
if(strpos($format,'%m')===false) { 
    $days = $total_days; 
    if(strpos($format,'%y')!==false) { 
     $start_dt->add(new DateInterval('P'.$years.'Y')); 
     $interval = $end_dt->diff($start_dt); 
     $days = $interval->days; 
    } 
} 
if(strpos($format,'%w')!==false) { 
    $weeks = (int)($days/7); 
    $days %= 7; 
} 
if(strpos($format,'%d')===false) { 
    $hours += $days * 24; 
} 
if(strpos($format,'%h')===false) { 
    $mins += $hours * 60; 
} 
if(strpos($format,'%i')===false) { 
    $secs += $mins * 60; 
} 

(FYI, tôi chỉ làm một số str_replacing cơ bản ở cuối để ném nó vào chuỗi định dạng tùy chỉnh của tôi)

Edit: Vẫn còn 1 kịch bản nữa tôi không biết cách xử lý ... khi năm và ngày được yêu cầu nhưng không phải hàng tháng, đầu ra sẽ sai. Tôi tự hỏi nếu tôi chỉ nên mod tổng số ngày với 365 .... đó là loại của một kịch bản hiếm.

Chỉnh sửa2: Giải quyết! Chúng tôi sẽ chỉ thêm nhiều năm đó vào ngày bắt đầu và sau đó tính toán lại tổng số ngày.

+1

Giải pháp tốt đẹp Đánh dấu, sạch sẽ và dễ đọc tất cả các cách thức thông qua. Có vẻ như bạn gần như ở đó. – drew010

1

Đây là nỗ lực của tôi về một vấn đề thú vị. Nó không hoàn toàn hoạt động 100%, nhưng nó rất gần.

Hàm bắt đầu bằng cách tính tổng số giây và tháng mà chênh lệch ngày yêu cầu. Sau đó, sử dụng mảng $tokens được sắp xếp theo thứ tự tăng dần, nó lặp qua các mã thông báo đó và cố gắng khớp nó với đầu vào $format. Nếu tìm thấy số $format, giá trị thích hợp sẽ được trừ khỏi tổng số tháng hoặc giây.

Tuy nhiên, có khả năng có giá trị lớn hơn 0 trong nhiều tháng hoặc nhiều năm nhưng chuỗi $format không chỉ định một trong hai thông số đó. Nếu có, hàm sẽ cuộn qua số ngày thích hợp để tìm ra các phép tính chính xác.

Tôi đã thử nghiệm nó một thời gian ngắn và nó hoạt động cho tất cả các ví dụ trong chuỗi này. Bạn có thể kiểm tra nó ra trên codepad để xem bạn có thể phá vỡ nó! :) Tôi muốn được cải thiện về điều này.

<?php 

function calc($start, $end, $format = '%s', $chop = false) 
{ 
    $tokens = array('%y', '%m', '%w', '%d', '%h', '%i', '%s'); 

    if(!is_a($start, 'DateTime') || !is_a($end, 'DateTime')) 
    { 
     return; 
    } 
    $diff = $start->diff($end); 

    $months = ($diff->y * 12) + $diff->m; 
    $secs = ($diff->d * 24 * 3600) + 
      ($diff->h * 3600) + 
      ($diff->i * 60) + 
      ($diff->s); 

    $output = array(); 
    while($token = array_shift($tokens)) 
    { 
     $token_present = !(strpos($format, $token) === false); 

     switch($token) 
     { 
      case '%y': 
       if(($months/12) > 0 && $token_present) 
       { 
        $output[$token] = floor($months/12); 
        $months -= $output[$token] * 12; 
       } 
      break; 
      case '%m': 
       if($months > 0 && $token_present) 
       { 
        $output[$token] = $months; 
        $months = 0; 
       } 
      break; 
      case '%w': 
       // Rollover between (months or years) and seconds 
       if((!isset($output['%y']) || !isset($output['%m'])) && $months > 0) 
       { 
        $days = $diff->format('%a'); 
       // Need a fix for leap year probably. 
       $days -= (isset($output['%y'])) ? ($output['%y'] * 365) : 0; 
       $days -= $diff->d; 
       $secs += ($days * 24 * 60 * 60); 
       } 

       $val = (7 * 24 * 60 * 60); 
       if(($secs/$val) > 0 && $token_present) 
       { 
        $output[$token] = floor($secs/$val); 
        $secs -= $output[$token] * $val; 
       } 
      break; 
      case '%d': 
       $val = (24 * 60 * 60); 
       if(($secs/$val) > 0 && $token_present) 
       { 
        $output[$token] = floor($secs/$val); 
        $secs -= $output[$token] * $val; 
       } 
      break; 
      case '%h': 
       $val = (60 * 60); 
       if(($secs/$val) > 0 && $token_present) 
       { 
        $output[$token] = floor($secs/$val); 
        $secs -= $output[$token] * $val; 
       } 
      break; 
      case '%i': 
       $val = (60); 
       if(($secs/$val) > 0 && $token_present) 
       { 
        $output[$token] = floor($secs/$val); 
        $secs -= $output[$token] * $val; 
       } 
      break; 
      case '%s': 
       if($secs > 0 && $token_present) 
       { 
        $output[$token] = $secs; 
       } 
      break; 
     } 
    } 

    // Filter out blank keys and replace their tokens in the $format string 
    $filtered = $chop ? array_filter($output) : $output; 
    $format = str_replace(array_diff(array_keys($output), array_keys($filtered)), '', $format); 

    return str_replace(array_keys($filtered), array_values($filtered), $format); 
} 

$start = new DateTime('04-Feb-2010'); 
$end = new DateTime('28-Jun-2011'); 
echo calc($start, $end, "%m months %d days\n"); // 16 months 24 days 

$january = new DateTime('2010-01-01'); 
$february = new DateTime('2011-02-20 3:35:28'); 
echo calc($january, $february, '%y years %m months %w weeks %d days %h hours %i minutes %s seconds'); // 1 years 1 months 2 weeks 5 days 3 hours 35 minutes 28 seconds 
+0

Tôi đã phá vỡ nó: http://codepad.viper-7.com/lc16ez – mpen

+0

@Mark Tôi nhận thấy rằng lỗi khi tôi đăng câu trả lời ... Tôi nghĩ rằng điều này sửa chữa nó: http://codepad.viper-7.com/T7qehe – nickb

+0

Yeah..Đó là cùng một kết quả tôi nhận được ngay bây giờ, tuy nhiên tôi nghĩ rằng bạn vẫn còn có một số lỗi làm tròn, đặc biệt là xung quanh năm nhuận. Tôi không thích rằng "365" trong đó;) Giải pháp của tôi là hiện tại ở dưới cùng của trang này BTW. – mpen

Các vấn đề liên quan