Tính số giờ giữa 2 ngày trong PHP


107

Làm cách nào để tính toán chênh lệch giữa hai ngày theo giờ?

Ví dụ:

day1=2006-04-12 12:30:00
day2=2006-04-14 11:30:00

Trong trường hợp này, kết quả sẽ là 47 giờ.


1
Phản ứng ban đầu của tôi là, biến cả hai giá trị thành dấu thời gian bằng cách sử dụng strftime()và chia sự khác biệt cho 3600, nhưng liệu điều đó có luôn hoạt động không? Chết tiệt, Giờ tiết kiệm ánh sáng ban ngày!
Pekka

@Pekka: không, nó sẽ không luôn hoạt động, tôi đoán ... Hãy xem câu trả lời của tôi. Ở đó tôi đã đăng một giải pháp xem xét, múi giờ, năm nhuận, giây nhuận và dst :)
Fidi

@Pekka, nếu bạn sử dụng strtotime()nó SẼ luôn hoạt động, miễn là bạn sử dụng múi giờ mặc định HOẶC chỉ định rõ ràng độ lệch múi giờ. Không có lý do gì để nguyền rủa DST.
Walter Tross

Câu trả lời:


205

PHP-phiên bản mới hơn cung cấp một số lớp mới gọi là DateTime, DateInterval, DateTimeZoneDatePeriod. Điều thú vị về các lớp này là nó xem xét các múi giờ khác nhau, năm nhuận, giây nhuận, giờ mùa hè, v.v. Và trên hết, nó rất dễ sử dụng. Đây là những gì bạn muốn với sự trợ giúp của các đối tượng này:

// Create two new DateTime-objects...
$date1 = new DateTime('2006-04-12T12:30:00');
$date2 = new DateTime('2006-04-14T11:30:00');

// The diff-methods returns a new DateInterval-object...
$diff = $date2->diff($date1);

// Call the format method on the DateInterval-object
echo $diff->format('%a Day and %h hours');

Đối tượng DateInterval, được trả về cũng cung cấp các phương thức khác với format. Nếu bạn muốn kết quả chỉ trong vài giờ, bạn có thể làm như sau:

$date1 = new DateTime('2006-04-12T12:30:00');
$date2 = new DateTime('2006-04-14T11:30:00');

$diff = $date2->diff($date1);

$hours = $diff->h;
$hours = $hours + ($diff->days*24);

echo $hours;

Và đây là các liên kết cho tài liệu:

Tất cả các lớp này cũng cung cấp một cách thủ tục / chức năng để hoạt động với ngày tháng. Do đó, hãy xem tổng quan: http://php.net/manual/book.datetime.php


+1 Làm tốt lắm! Điều này trông chắc chắn và là một tổng quan tốt. Điều quan trọng cần lưu ý là các phép tính có thể thay đổi theo múi giờ do các quy tắc DST khác nhau, vì vậy, có lẽ bạn nên luôn xác định vùng và không dựa vào cài đặt máy chủ.
Pekka

Vâng. Với đối tượng này, bạn thậm chí có thể calcualte giữa các ngày trong các múi giờ khác nhau. $date1 = new DateTime('2006-04-12T12:30:00 Europe/Berlin');$date2 = new DateTime('2006-04-14T11:30:00 America/New_York');
Fidi

3
Nếu ai đó gặp phải vấn đề tương tự như tôi vừa làm trong đó $diff->dbằng 0 (vì tôi đang cố tính số giờ giữa hai ngày cách nhau chính xác 2 tháng): Running var_dump($diff)đã hiển thị cho tôi một tham số khác:, ["days"]=>int(61)vì vậy tôi đã kết thúc sử dụng $hours = $diff->days * 24;và nó đã đến ra gần với "trung bình" 1440 giờ cho 2 tháng 30 ngày, vì vậy mà nhân tìm kiếm tốt hơn nhiều so với kết quả của 0. (đoán phiên bản PHP của tôi là một chút cũ ...)
semmelbroesel

2
Ý tôi là, ở nhiều nơi trên thế giới, một năm có một ngày 23 giờ và một ngày 25 giờ.
Walter Tross

4
@Amal Murali, vì vậy bạn đã quyết định trao phần thưởng cho câu trả lời này, điều này SAI? Bạn đã thử tính toán với câu trả lời này số giờ từ trưa ngày 1 tháng 1 đến trưa ngày 1 tháng 6, ở bất kỳ múi giờ nào có DST (giờ tiết kiệm ánh sáng ban ngày) chưa? Bạn sẽ nhận được kết quả chẵn, trong khi kết quả đúng là số lẻ.
Walter Tross

78
$t1 = strtotime( '2006-04-14 11:30:00' );
$t2 = strtotime( '2006-04-12 12:30:00' );
$diff = $t1 - $t2;
$hours = $diff / ( 60 * 60 );

4
Tại sao không $diff / 3600?
Alex G

4
@AlexG Nó chỉ là một phong cách. Cùng công suất, nhưng các lập trình viên thường sử dụng phép nhân khi nói đến thời gian
tài

tôi đề nghị bạn thích: round (($ t1 - $ 22) / 3600); sử dụng vòng để có giờ chính xác
Shiv Singh

20

Để cung cấp một phương pháp khác DatePeriodkhi sử dụng múi giờ UTC hoặc GMT .

Đếm giờ https://3v4l.org/Mu3HD

$start = new \DateTime('2006-04-12T12:30:00');
$end = new \DateTime('2006-04-14T11:30:00');

//determine what interval should be used - can change to weeks, months, etc
$interval = new \DateInterval('PT1H');

//create periods every hour between the two dates
$periods = new \DatePeriod($start, $interval, $end);

//count the number of objects within the periods
$hours = iterator_count($periods);
echo $hours . ' hours'; 

//difference between Unix Epoch
$diff = $end->getTimestamp() - $start->getTimestamp();
$hours = $diff / ( 60 * 60 );
echo $hours . ' hours (60 * 60)';

//difference between days
$diff = $end->diff($start);
$hours = $diff->h + ($diff->days * 24);
echo $hours . ' hours (days * 24)';

Kết quả

47 hours (iterator_count)
47 hours (60 * 60)
47 hours (days * 24)

Đếm giờ với Tiết kiệm ánh sáng ban ngày https://3v4l.org/QBQUB

Xin lưu ý rằng DatePeriodloại trừ một giờ cho DST nhưng không thêm một giờ khác khi DST kết thúc. Vì vậy, việc sử dụng nó phụ thuộc vào kết quả mong muốn và phạm vi ngày của bạn.

Xem báo cáo lỗi hiện tại

//set timezone to UTC to disregard daylight savings
date_default_timezone_set('America/New_York');

$interval = new \DateInterval('PT1H');

//DST starts Apr. 2nd 02:00 and moves to 03:00
$start = new \DateTime('2006-04-01T12:00:00');  
$end = new \DateTime('2006-04-02T12:00:00');

$periods = new \DatePeriod($start, $interval, $end);
$hours = iterator_count($periods);
echo $hours . ' hours';

//DST ends Oct. 29th 02:00 and moves to 01:00
$start = new \DateTime('2006-10-28T12:00:00');
$end = new \DateTime('2006-10-29T12:00:00'); 

$periods = new \DatePeriod($start, $interval, $end);
$hours = iterator_count($periods);
echo $hours . ' hours';

Kết quả

#2006-04-01 12:00 EST to 2006-04-02 12:00 EDT
23 hours (iterator_count)
//23 hours (60 * 60)
//24 hours (days * 24)

#2006-10-28 12:00 EDT to 2006-10-29 12:00 EST
24 hours (iterator_count)
//25 hours (60 * 60)
//24 hours (days * 24)

#2006-01-01 12:00 EST to 2007-01-01 12:00 EST
8759 hours (iterator_count)
//8760 hours (60 * 60)
//8760 hours (days * 24)

//------

#2006-04-01 12:00 UTC to 2006-04-02 12:00 UTC
24 hours (iterator_count)
//24 hours (60 * 60)
//24 hours (days * 24)

#2006-10-28 12:00 UTC to 2006-10-29 12:00 UTC
24 hours (iterator_count)
//24 hours (60 * 60)
//24 hours (days * 24)

#2006-01-01 12:00 UTC to 2007-01-01 12:00 UTC
8760 hours (iterator_count)
//8760 hours (60 * 60)
//8760 hours (days * 24)

1
Đối với bất cứ ai như nhầm lẫn như tôi đã nhìn thấy những tham số constructor DateInterval, định dạng là một tiêu chuẩn ISO 8601 Thời gian
TheKarateKid

Một lưu ý khác là DateIntervalkhông chấp nhận giá trị phân số như trong tiêu chuẩn ISO 8601. Vì vậy, P1.2Ykhông phải là thời hạn hợp lệ trong PHP.
fyrye

LƯU Ý: iterator_count sẽ chỉ trả về kết quả tích cực. Nếu ngày đầu tiên lớn hơn ngày thứ hai, kết quả khác biệt sẽ là 0.
SubjectDelta

1
@SubjectDelta vấn đề không liên quan đến iterator_count, đó là do DatePeriodkhông thể tạo ngày khi ngày bắt đầu trong tương lai từ ngày kết thúc. Xem: 3v4l.org/Ypsp1 để sử dụng ngày âm, bạn cần chỉ định khoảng âm, DateInterval::createFromDateString('-1 hour');với ngày bắt đầu trong quá khứ của ngày kết thúc.
fyrye

1
@SubjectDelta đây là một sắc thái khác DatePeriod, vì theo mặc định, nó sẽ bao gồm ngày bắt đầu giữa các khoảng thời gian được chỉ định trừ khi chúng nhỏ hơn hoặc bằng ngày bắt đầu. Trên thực tế, bạn đang yêu cầu php tạo khoảng thời gian 1 giờ giữa hai ngày, trong vòng 1 giây thời gian. Bạn sẽ cần xóa phút và giây khỏi các đối tượng ngày của mình, vì chúng không liên quan trong tính toán, bằng cách sử dụng DateTime::setTime(date->format('H'), 0). 3v4l.org/K7uss Bằng cách này nếu bạn vượt quá phạm vi 1 giây, thì ngày khác sẽ không được tạo.
fyrye

17

câu trả lời của bạn là:

round((strtotime($day2) - strtotime($day1))/(60*60))


5
Điều gì sẽ xảy ra nếu có 2 giờ 30 phút giữa các chuyến bay? Câu trả lời của bạn sẽ có kết quả sau 3 giờ. Tôi nghĩ sẽ tốt hơn nếu sử dụng sàn để có thể mang lại 2 giờ. Thực sự phụ thuộc vào tình hình mặc dù.
Kapitein Witbaard

14

Cách dễ nhất để có được số giờ chính xác giữa hai ngày (lịch ngày), ngay cả khi thay đổi giờ tiết kiệm ánh sáng ban ngày, là sử dụng sự khác biệt trong dấu thời gian Unix. Dấu thời gian Unix là giây đã trôi qua kể từ 1970-01-01T00: 00: 00 UTC, bỏ qua giây nhuận (điều này không sao vì bạn có thể không cần độ chính xác này và vì khá khó tính đến giây nhuận).

Cách linh hoạt nhất để chuyển đổi một chuỗi datetime với thông tin múi giờ tùy chọn thành dấu thời gian Unix là tạo đối tượng DateTime (tùy chọn với DateTimeZone làm đối số thứ hai trong hàm tạo), rồi gọi phương thức getTimestamp của nó .

$str1 = '2006-04-12 12:30:00'; 
$str2 = '2006-04-14 11:30:00';
$tz1 = new DateTimeZone('Pacific/Apia');
$tz2 = $tz1;
$d1 = new DateTime($str1, $tz1); // tz is optional,
$d2 = new DateTime($str2, $tz2); // and ignored if str contains tz offset
$delta_h = ($d2->getTimestamp() - $d1->getTimestamp()) / 3600;
if ($rounded_result) {
   $delta_h = round ($delta_h);
} else if ($truncated_result) {
   $delta_h = intval($delta_h);
}
echo "Δh: $delta_h\n";

1
Từ một lời nhận xét trong tay nó xuất hiện rằng, đối với khả năng tương thích với các ngày trước thời đại, format("U")là một lợi thế đểgetTimestamp()
Arth

1
@Arth, tôi không biết trường hợp này xảy ra khi nào, nhưng trong PHP 5.5.9 của tôi thì nó không còn đúng nữa. getTimestamp()bây giờ trả về chính xác giá trị giống như format("U"). Tuy nhiên, cái trước là một số nguyên, trong khi cái sau là một chuỗi (ở đây kém hiệu quả hơn).
Walter Tross

Tuyệt, có lẽ nó đúng trong một phiên bản trước đó .. Có một số nguyên sẽ rõ ràng hơn, vì vậy tôi muốn getTimestamp()nếu tôi có thể chắc chắn.
Arth

4
//Calculate number of hours between pass and now
$dayinpass = "2013-06-23 05:09:12";
$today = time();
$dayinpass= strtotime($dayinpass);
echo round(abs($today-$dayinpass)/60/60);

3
$day1 = "2006-04-12 12:30:00"
$day1 = strtotime($day1);
$day2 = "2006-04-14 11:30:00"
$day2 = strtotime($day2);

$diffHours = round(($day2 - $day1) / 3600);

Tôi đoán hàm strtotime () chấp nhận định dạng ngày này.


3
<?
     $day1 = "2014-01-26 11:30:00";
     $day1 = strtotime($day1);
     $day2 = "2014-01-26 12:30:00";
     $day2 = strtotime($day2);

   $diffHours = round(($day2 - $day1) / 3600);

   echo $diffHours;

?>

Đây cũng là một bản sao của các hình thức trả lời 2010.
Daniel W.

2

Rất tiếc, giải pháp do FaileN cung cấp không hoạt động như Walter Tross đã nêu .. ngày có thể không quá 24 giờ!

Tôi thích sử dụng Đối tượng PHP nếu có thể và để linh hoạt hơn một chút, tôi đã nghĩ ra hàm sau:

/**
 * @param DateTimeInterface $a
 * @param DateTimeInterface $b
 * @param bool              $absolute Should the interval be forced to be positive?
 * @param string            $cap The greatest time unit to allow
 *
 * @return DateInterval The difference as a time only interval
 */
function time_diff(DateTimeInterface $a, DateTimeInterface $b, $absolute=false, $cap='H'){

  // Get unix timestamps, note getTimeStamp() is limited
  $b_raw = intval($b->format("U"));
  $a_raw = intval($a->format("U"));

  // Initial Interval properties
  $h = 0;
  $m = 0;
  $invert = 0;

  // Is interval negative?
  if(!$absolute && $b_raw<$a_raw){
    $invert = 1;
  }

  // Working diff, reduced as larger time units are calculated
  $working = abs($b_raw-$a_raw);

  // If capped at hours, calc and remove hours, cap at minutes
  if($cap == 'H') {
    $h = intval($working/3600);
    $working -= $h * 3600;
    $cap = 'M';
  }

  // If capped at minutes, calc and remove minutes
  if($cap == 'M') {
    $m = intval($working/60);
    $working -= $m * 60;
  }

  // Seconds remain
  $s = $working;

  // Build interval and invert if necessary
  $interval = new DateInterval('PT'.$h.'H'.$m.'M'.$s.'S');
  $interval->invert=$invert;

  return $interval;
}

Điều này giống như date_diff()tạo ra một DateTimeInterval, nhưng với đơn vị cao nhất là giờ thay vì năm .. nó có thể được định dạng như bình thường.

$interval = time_diff($date_a, $date_b);
echo $interval->format('%r%H'); // For hours (with sign)

NB Tôi đã sử dụng format('U')thay getTimestamp()vì vì chú thích trong sách hướng dẫn . Cũng lưu ý rằng 64-bit là bắt buộc đối với các ngày hậu kỷ nguyên và trước kỷ nguyên âm!


0

Chức năng này giúp bạn tính toán chính xác năm và tháng giữa hai ngày nhất định $doj1$doj. Nó trả về ví dụ 4.3 nghĩa là 4 năm 3 tháng.

<?php
    function cal_exp($doj1)
    {
        $doj1=strtotime($doj1);
        $doj=date("m/d/Y",$doj1); //till date or any given date

        $now=date("m/d/Y");
        //$b=strtotime($b1);
        //echo $c=$b1-$a2;
        //echo date("Y-m-d H:i:s",$c);
        $year=date("Y");
        //$chk_leap=is_leapyear($year);

        //$year_diff=365.25;

        $x=explode("/",$doj);
        $y1=explode("/",$now);

        $yy=$x[2];
        $mm=$x[0];
        $dd=$x[1];

        $yy1=$y1[2];
        $mm1=$y1[0];
        $dd1=$y1[1];
        $mn=0;
        $mn1=0;
        $ye=0;
        if($mm1>$mm)
        {
            $mn=$mm1-$mm;
            if($dd1<$dd)
            {
                $mn=$mn-1;
            }
            $ye=$yy1-$yy;
        }
        else if($mm1<$mm)
        {
            $mn=12-$mm;
            //$mn=$mn;

            if($mm!=1)
            {
                $mn1=$mm1-1;
            }

            $mn+=$mn1;
            if($dd1>$dd)
            {
                $mn+=1;
            }

            $yy=$yy+1;
            $ye=$yy1-$yy;
        }
        else
        {
            $ye=$yy1-$yy;
            $ye=$ye-1;

            $mn=12-1;

            if($dd1>$dd)
            {
                $ye+=1;
                $mn=0;
            }
        }

        $to=$ye." year and ".$mn." months";
        return $ye.".".$mn;

        /*return daysDiff($x[2],$x[0],$x[1]);
         $days=dateDiff("/",$now,$doj)/$year_diff;
        $days_exp=explode(".",$days);
        return $years_exp=$days; //number of years exp*/
    }
?>

Chỉnh sửa được đề xuất quá nhỏ, nhưng <phpcần được thay đổi thành <?phpHoặc phê duyệt chỉnh sửa được đề xuất, chỉnh sửa này sẽ xóa toàn bộ lỗi.
anishsane

0

Điều này đang làm việc trong dự án của tôi. Tôi nghĩ, Điều này sẽ hữu ích cho bạn.

Nếu Ngày trong quá khứ thì đảo ngược sẽ 1.
Nếu Ngày trong tương lai thì đảo ngược sẽ là 0.

$defaultDate = date('Y-m-d');   
$datetime1   = new DateTime('2013-03-10');  
$datetime2   = new DateTime($defaultDate);  
$interval    = $datetime1->diff($datetime2);  
$days        = $interval->format('%a');
$invert      = $interval->invert;

0

Để vượt qua dấu thời gian unix, hãy sử dụng ký hiệu này

$now        = time();
$now        = new DateTime("@$now");

1
Lưu ý Múi giờ sẽ được chuyển và xuất như +0:00khi sử dụng @trong hàm tạo DateTime. Trong khi sử dụng DateTime::modify()phương pháp sẽ chuyển dấu thời gian +0:00và xuất ra múi giờ hiện tại. Ngoài ra sử dụng $date = new DateTime(); $date->setTimestamp($unix_timestamp);thấy: 3v4l.org/BoAWI
fyrye

0

Carbon cũng có thể là một cách tốt để đi.

Từ trang web của họ:

Một phần mở rộng API PHP đơn giản cho DateTime. http://carbon.nesbot.com/

Thí dụ:

use Carbon\Carbon;

//...

$day1 = Carbon::createFromFormat('Y-m-d H:i:s', '2006-04-12 12:30:00');
$day2 = Carbon::createFromFormat('Y-m-d H:i:s', '2006-04-14 11:30:00');

echo $day1->diffInHours($day2); // 47

//...

Carbon mở rộng lớp DateTime để kế thừa các phương thức bao gồm diff(). Nó cho biết thêm đường đẹp như diffInHours, diffInMintutes, diffInSecondsvv


0

Trước tiên, bạn nên tạo một đối tượng khoảng thời gian từ một phạm vi ngày. Chỉ bằng cách sử dụng từ ngữ trong câu này, người ta có thể dễ dàng xác định những điều trừu tượng cơ bản cần thiết. Có một khoảng thời gian như một khái niệm, và một vài cách khác để triển khai nó, bao gồm một khoảng thời gian đã được đề cập - từ một phạm vi ngày. Do đó, một khoảng thời gian trông như thế:

$interval =
    new FromRange(
        new FromISO8601('2017-02-14T14:27:39+00:00'),
        new FromISO8601('2017-03-14T14:27:39+00:00')
    );

FromISO8601có cùng ngữ nghĩa: đó là một đối tượng datetime được tạo ra from iso8601-formatted string, do đó có tên.

Khi bạn có một khoảng thời gian, bạn có thể định dạng nó theo cách bạn muốn. Nếu bạn cần một số giờ đầy đủ, bạn có thể có

(new TotalFullHours($interval))->value();

Nếu bạn muốn có tổng số giờ làm việc, hãy làm như sau:

(new TotalCeiledHours($interval))->value();

Để biết thêm về cách tiếp cận này và một số ví dụ, hãy xem mục này .


0

Ngoài câu trả lời rất hữu ích của @ fyrye, đây là một cách giải quyết ổn thỏa cho lỗi được đề cập (lỗi này ), DatePeriod giảm bớt một giờ khi bước vào mùa hè, nhưng không thêm một giờ khi rời khỏi thời điểm mùa hè (và do đó, tháng 3 của Châu Âu / Berlin có đúng 743 giờ nhưng tháng 10 có 744 thay vì 745 giờ):

Đếm số giờ trong tháng (hoặc bất kỳ khoảng thời gian nào), xem xét chuyển đổi DST theo cả hai hướng

function getMonthHours(string $year, string $month, \DateTimeZone $timezone): int
{
    // or whatever start and end \DateTimeInterface objects you like
    $start = new \DateTimeImmutable($year . '-' . $month . '-01 00:00:00', $timezone);
    $end = new \DateTimeImmutable((new \DateTimeImmutable($year . '-' . $month . '-01 23:59:59', $timezone))->format('Y-m-t H:i:s'), $timezone);
    
    // count the hours just utilizing \DatePeriod, \DateInterval and iterator_count, hell yeah!
    $hours = iterator_count(new \DatePeriod($start, new \DateInterval('PT1H'), $end));
    
    // find transitions and check, if there is one that leads to a positive offset
    // that isn't added by \DatePeriod
    // this is the workaround for https://bugs.php.net/bug.php?id=75685
    $transitions = $timezone->getTransitions((int)$start->format('U'), (int)$end->format('U'));
    if (2 === count($transitions) && $transitions[0]['offset'] - $transitions[1]['offset'] > 0) {
        $hours += (round(($transitions[0]['offset'] - $transitions[1]['offset'])/3600));
    }
    
    return $hours;
}

$myTimezoneWithDST = new \DateTimeZone('Europe/Berlin');
var_dump(getMonthHours('2020', '01', $myTimezoneWithDST)); // 744
var_dump(getMonthHours('2020', '03', $myTimezoneWithDST)); // 743
var_dump(getMonthHours('2020', '10', $myTimezoneWithDST)); // 745, finally!

$myTimezoneWithoutDST = new \DateTimeZone('UTC');
var_dump(getMonthHours('2020', '01', $myTimezoneWithoutDST)); // 744
var_dump(getMonthHours('2020', '03', $myTimezoneWithoutDST)); // 744
var_dump(getMonthHours('2020', '10', $myTimezoneWithoutDST)); // 744

Tái bút Nếu bạn kiểm tra khoảng thời gian (dài hơn), dẫn đến nhiều hơn hai lần chuyển đổi đó, thì giải pháp của tôi sẽ không chạm vào số giờ đã tính để giảm khả năng xảy ra các tác dụng phụ buồn cười. Trong những trường hợp như vậy, một giải pháp phức tạp hơn phải được thực hiện. Người ta có thể lặp lại tất cả các chuyển đổi được tìm thấy và so sánh hiện tại với chuyển đổi cuối cùng và kiểm tra xem nó có phải là chuyển tiếp với DST true-> false hay không.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.