PHP'de 2 tarih arasındaki saat sayısını hesaplayın


107

İki tarih arasındaki farkı saat cinsinden nasıl hesaplarım?

Örneğin:

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

Bu durumda sonuç 47 saat olmalıdır.


1
İlk cevabım, her iki değeri de kullanarak zaman damgalarına dönüştürmek strftime()ve farkı 3600'e bölmek olurdu , ama bu her zaman işe yarayacak mı? Lanet olsun, Gün Işığından Yararlanma Zamanı!
Pekka

@Pekka: hayır her zaman işe yaramayacak sanırım ... Cevabıma bir bakın. Orada saat dilimlerini, artık yılları, artık saniyeleri ve dst'yi dikkate alan bir çözüm yayınladım :)
Fidi

@Pekka, strtotime()kullanıyorsanız, varsayılan saat dilimini kullandığınız veya saat dilimi farkını açıkça belirttiğiniz sürece her zaman çalışacaktır. DST'yi lanetlemek için bir neden yok.
Walter Tross

Yanıtlar:


205

Yeni PHP sürümleri adlandırılan bazı yeni sınıflar sağlamak DateTime, DateInterval, DateTimeZoneve DatePeriod. Bu sınıflarla ilgili harika olan şey, farklı zaman dilimlerini, artık yılları, artık saniyeleri, yaz saatlerini vb. Dikkate almasıdır. Üstelik kullanımı çok kolaydır. İşte bu nesnelerin yardımıyla istediğiniz şey:

// 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');

Döndürülen DateInterval nesnesi ayrıca format. Sonucu yalnızca saatler içinde istiyorsanız, bunun gibi bir şey yapabilirsiniz:

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

Ve işte dokümantasyon bağlantıları:

Tüm bu sınıflar ayrıca tarihlerle çalışmak için prosedürel / işlevsel bir yol sunar. Bu yüzden genel bakışa bir göz atın: http://php.net/manual/book.datetime.php


+1 İyi iş! Bu sağlam görünüyor ve iyi bir genel bakış. Farklı DST kuralları nedeniyle hesaplamaların saat dilimine göre değişebileceğini unutmamak önemlidir, bu nedenle her zaman bölgeyi tanımlamak ve sunucu ayarlarına güvenmemek muhtemelen iyi bir fikirdir.
Pekka

Evet. Bu nesne ile, farklı saat dilimlerindeki tarihler arasında bile hesaplama yapabilirsiniz. $date1 = new DateTime('2006-04-12T12:30:00 Europe/Berlin');ve$date2 = new DateTime('2006-04-14T11:30:00 America/New_York');
Fidi

3
Birisi az önce yaptığımla aynı sorunla karşılaşırsa $diff->d, 0'a eşittir (çünkü tam olarak 2 ay arayla olan iki tarih arasındaki saatleri hesaplamaya çalışıyorum): Koşmak var_dump($diff)bana başka bir parametre gösterdi:, ["days"]=>int(61)bu yüzden kullanmayı bıraktım $hours = $diff->days * 24;ve geldi 2 30 günlük ay verilen 1440 saatlik "ortalama" saate yakın, yani bu 0 sonucundan çok daha iyi görünüyor. (PHP
sürümümün

2
Demek istediğim, dünyanın birçok yerinde bir yılda bir 23 saatlik bir gün ve bir 25 saatlik gün var.
Walter Tross

4
@Amal Murali, yani bu cevaba bonus vermeye karar verdin, bu YANLIŞ mı? DST (yaz saati uygulaması) olan herhangi bir zaman diliminde, Ocak ayının ilk öğleniyle Haziran ayının ilk öğlen arasındaki saat sayısını bu cevapla hesaplamaya çalıştınız mı? Gerçek sonuç tuhaf olsa da, eşit bir sonuç alırsınız.
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
Neden olmasın $diff / 3600?
Alex G

4
@AlexG Bu sadece bir stil meselesi. Aynı çıktı, ancak programcılar genellikle zaman söz konusu olduğunda çarpma kullanır
Kullanıcı

beğenmenizi öneririm: round (($ t1 - 22 $) / 3600); doğru saatleri elde etmek için turu kullanın
Shiv Singh

20

UTC veya GMT saat dilimini DatePeriodkullanırken başka bir yöntem sağlamak için .

Saatleri Sayma 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)';

Sonuç

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

Gün Işığından Yararlanma ile Saatleri Sayma https://3v4l.org/QBQUB

Lütfen bunun DatePeriodDST için bir saati hariç tuttuğunu, ancak DST bittiğinde bir saat daha eklemediğini unutmayın. Dolayısıyla kullanımı, istediğiniz sonuca ve tarih aralığına göre özneldir.

Mevcut hata raporuna bakın

//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';

Sonuç

#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
DateInterval yapıcı parametresini görürken kafam karışan herkes için, biçim bir ISO 8601 Süre
TheKarateKid

Diğer bir not, DateIntervalISO 8601 spesifikasyonundaki gibi kesirli değerleri kabul etmemesidir. Yani P1.2YPHP'de geçerli bir süre değil.
fyrye

NOT: iterator_count yalnızca pozitif sonuçlar döndürür. İlk tarih ikinciden daha büyükse, fark sonucu 0 olacaktır.
SubjectDelta

1
@SubjectDelta konu ile ilgili değil iterator_count, bu kaynaklanmaktadır DatePeriodbaşlangıç tarihi bitiş tarihinden itibaren gelecekte olduğunda tarihleri üretmek mümkün olmayan. Bakınız: 3v4l.org/Ypsp1 Negatif bir tarih kullanmak için, DateInterval::createFromDateString('-1 hour');başlangıç ​​tarihi bitiş tarihinden itibaren geçmişte olmak üzere negatif bir aralık belirtmeniz gerekir .
fyrye

1
@SubjectDelta bu başka bir nüanstır DatePeriod, çünkü varsayılan olarak, başlangıç ​​tarihinden küçük veya bu tarihe eşit olmadıkça belirtilen dönemler arasındaki başlangıç ​​tarihini içerecektir. Aslında php'ye iki tarih arasında 1 saniye içinde 1 saatlik bir süre oluşturmasını söylüyorsunuz. Tarih nesnelerinizden dakikaları ve saniyeleri, kullanarak hesaplamayla ilgili olmadıklarından kaldırmanız gerekir DateTime::setTime(date->format('H'), 0). 3v4l.org/K7uss Bu şekilde, aralığı 1 saniye geçerseniz, başka bir tarih oluşturulmaz.
fyrye

17

cevabınız:

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


5
Arada 2 saat 30 dakika varsa ne olur? Cevabınız 3 saat içinde sonuçlanacak. Zemini kullanmanın 2 saat randıman vermesi için daha iyi olacağını düşünüyorum. Gerçi gerçekten duruma bağlı.
Kapitein Witbaard

14

Gün ışığından yararlanma saati değişikliklerinde bile iki tarih (tarih saatleri) arasında doğru saat sayısını elde etmenin en kolay yolu, Unix zaman damgalarındaki farkı kullanmaktır. Unix zaman damgaları, 1970-01-01T00: 00: 00 UTC'den bu yana geçen saniyelerdir, artık saniyeleri göz ardı ederek (bu tamamdır çünkü muhtemelen bu hassasiyete ihtiyacınız yoktur ve artık saniyeleri hesaba katmak oldukça zordur).

İsteğe bağlı saat dilimi bilgilerine sahip bir tarih saat dizesini Unix zaman damgasına dönüştürmenin en esnek yolu, bir DateTime nesnesi (isteğe bağlı olarak yapıcıda ikinci bağımsız değişken olarak bir DateTimeZone ile ) oluşturmak ve ardından bunun getTimestamp yöntemini çağırmaktır .

$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
Kılavuzdaki bir yorumdan, çağ öncesi tarihlerle uyumluluk format("U")açısından şu tercih edilir:getTimestamp()
Arth

1
@Arth, durumun ne zaman olduğunu bilmiyorum ama benim PHP 5.5.9'da artık doğru değil. getTimestamp()şimdi tam olarak aynı değeri döndürür format("U"). İlki bir tamsayı iken, ikincisi bir dizedir (burada daha az etkilidir).
Walter Tross

Harika, belki daha önceki bir sürümde doğruydu .. Evet bir tam sayı daha temiz olurdu, bu yüzden getTimestamp()emin olsaydım tercih ederim .
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);

Sanırım strtotime () işlevi bu tarih biçimini kabul ediyor.


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;

?>

Bu aynı zamanda 2010 cevap formunun bir kopyasıdır.
Daniel W.

2

Ne yazık ki FaileN tarafından sağlanan çözüm Walter Tross'un belirttiği gibi çalışmıyor .. günler 24 saat olmayabilir!

Mümkün olduğunda PHP Nesnelerini kullanmayı seviyorum ve biraz daha fazla esneklik için aşağıdaki işlevi buldum:

/**
 * @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;
}

Bu date_diff(), a oluşturur DateTimeInterval, ancak en yüksek birimi yıllar yerine saat olarak oluşturur .. her zamanki gibi biçimlendirilebilir.

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

NB Kılavuzdaki yorum nedeniyle format('U')yerine kullandım . Ayrıca 64 bitin, post-epoch ve pre-negatif-epoch tarihleri ​​için gerekli olduğunu unutmayın!getTimestamp()


0

Bu işlev, verilen iki tarih arasındaki tam yıl ve ayları hesaplamanıza yardımcı olur $doj1ve $doj. Örnek 4.3'ü döndürür, 4 yıl ve 3 ay anlamına gelir.

<?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*/
    }
?>

Önerilen düzenleme çok küçük, ancak Ya da önerilen düzenlemeyi onayla <phpolarak değiştirilmesi gerekiyor <?php, bu da hatayı tamamen ortadan kaldırıyor.
anishsane

0

Bu benim projemde çalışıyor. Bunun size yardımcı olacağını düşünüyorum.

Tarih geçmişteyse, ters çevirme 1 olacaktır.
Tarih gelecekte ise ters çevir 0 olacaktır.

$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

Bir unix zaman damgası geçirmek için bu gösterimi kullanın

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

1
Not Saat dilimi , DateTime yapıcısında +0:00kullanılırken olduğu gibi aktarılır ve çıktılanır @. DateTime::modify()Yöntemi kullanırken zaman damgası olarak geçecek +0:00ve geçerli saat dilimini çıktı olarak verecektir . Alternatif olarak $date = new DateTime(); $date->setTimestamp($unix_timestamp);bkz: 3v4l.org/BoAWI
fyrye

0

Karbon ayrıca gitmek için güzel bir yol olabilir.

Web sitelerinden:

DateTime için basit bir PHP API uzantısı. http://carbon.nesbot.com/

Misal:

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, DateTime sınıfını diff(). Bu gibi güzel şekerleri ekler diffInHours, diffInMintutes, diffInSecondsvb


0

İlk olarak, bir tarih aralığından bir aralık nesnesi oluşturmalısınız. Sadece bu cümlede kullanılan ifadelerle, ihtiyaç duyulan temel soyutlamalar kolayca belirlenebilir. Kavram olarak bir aralık vardır ve onu uygulamanın birkaç yolu daha vardır, daha önce bahsedileni bir tarih aralığından içerir. Böylece, bir aralık şuna benzer:

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

FromISO8601aynı semantiğe sahiptir: oluşturulmuş bir datetime nesnesidir from iso8601-formatted string, dolayısıyla adıdır.

Bir aralığınız olduğunda, onu istediğiniz gibi biçimlendirebilirsiniz. Birkaç tam saate ihtiyacınız varsa, sahip olabilirsiniz

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

Toplam çalışma saatleri istiyorsanız, işte gidin:

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

Bu yaklaşım ve bazı örnekler hakkında daha fazla bilgi için bu girişe bakın .


0

@ Fyrye'nin çok yararlı cevabına ek olarak, bu, belirtilen hata ( bu ) için tamamlayıcı bir çözümdür, DatePeriod yaz saatine girerken bir saat kullanır, ancak yaz saatinden ayrılırken bir saat eklemez (ve dolayısıyla Avrupa / Berlin'in Mart ayının doğru 743 saat, ancak Ekim'de 745 saat yerine 744 saat var):

Her iki yöndeki DST geçişlerini göz önünde bulundurarak bir ayın (veya herhangi bir zaman aralığının) saatlerini saymak

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

Not: Bu iki geçişten daha fazlasına yol açan (daha uzun) bir zaman aralığını kontrol ederseniz, benim geçici çözümüm, komik yan etkilerin potansiyelini azaltmak için sayılan saatlere dokunmayacaktır. Bu gibi durumlarda daha karmaşık bir çözüm uygulanmalıdır. Bulunan tüm geçişler üzerinde yineleme yapılabilir ve sonuncusu ile akım karşılaştırılabilir ve DST true-> false ile bir olup olmadığı kontrol edilebilir.


0
$diff_min = ( strtotime( $day2 ) - strtotime( $day1 ) ) / 60 / 60;
$total_time  = $diff_min;

Bunu deneyebilirsin.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.