Are PHP DateInterval comparable like DateTime?

前端 未结 7 2072
孤城傲影
孤城傲影 2020-12-03 13:36

I discovered that a DateTime object in PHP can be compared to another as the \">\" and \"<\" operators are overloaded.

Is it the same with DateInterval?

A

相关标签:
7条回答
  • 2020-12-03 13:57

    Where does $aujourdhui come from? Sure it's the same as $today linguistically, but PHP doesn't know that! Changing your code to use $today will print "oK"!

    If not defined, $aujourdhui->diff($release) will evaluate to 0 if your PHP interpreter does not abort with an error (mine does).

    0 讨论(0)
  • 2020-12-03 14:02

    I used the following workaround comparing DateIntervals:

    version_compare(join('.', (array) $dateIntervalA), join('.', (array) $dateIntervalB));
    
    0 讨论(0)
  • 2020-12-03 14:04

    EDIT:

    class ComparableDateInterval extends DateInterval
    {
        /** 
         * Leap-year safe comparison of DateInterval objects.
         */
        public function compare(DateInterval $oDateInterval)
        {   
            $fakeStartDate1 = date_create();
            $fakeStartDate2 = clone $fakeStartDate1;
            $fakeEndDate1   = $fakeStartDate1->add($this);
            $fakeEndDate2   = $fakeStartDate2->add($oDateInterval);
    
            if($fakeEndDate1 < $fakeEndDate2) {
                return -1; 
            } elseif($fakeEndDate1 == $fakeEndDate2) {
                return 0;
            }   
            return 1;
        }   
    }
    
    $int15 = new ComparableDateInterval('P15D');
    $int20 = new ComparableDateInterval('P20D');
    
    var_dump($int15->compare($int20) == -1); // should be true;
    

    See @fyrye's answer for the rationale (and upvote it!). My original answer did not deal with leap years safely.


    Original Answer

    While I upvoted this question, I downvoted the accepted answer. That's because it didn't work for me on any of my PHP installations and because fundamentally it's hinging on something broken internally.

    What I did instead is migrate the aforementioned patch which never made it into trunk. FWIW I checked a recent release, PHP 5.6.5, and the patch still isn't there. The code was trivial to port. The only thing is a warning in how it makes the comparison

    If $this->days has been calculated, we know it's accurate, so we'll use that. If not, we need to make an assumption about month and year length, which isn't necessarily a good idea. I've defined months as 30 days and years as 365 days completely out of thin air, since I don't have the ISO 8601 spec available to check if there's a standard assumption, but we may in fact want to error out if we don't have $this->days available.

    Here's an example. Note, if you need to compare a DateInterval that was returned from some other call, you'll have to create a ComparableDateInterval from it first, if you want to use it as the source of the comparison.

    $int15 = new ComparableDateInterval('P15D');
    $int20 = new ComparableDateInterval('P20D');
    
    var_dump($int15->compare($int20) == -1); // should be true;
    

    Here's the code

    /**
     * The stock DateInterval never got the patch to compare.
     * Let's reimplement the patch in userspace.
     * See the original patch at http://www.adamharvey.name/patches/DateInterval-comparators.patch
     */
    class ComparableDateInterval extends DateInterval
    {
        static public function create(DateInterval $oDateInterval)
        {
            $oDi         = new ComparableDateInterval('P1D');
            $oDi->s      = $oDateInterval->s;
            $oDi->i      = $oDateInterval->i;
            $oDi->h      = $oDateInterval->h;
            $oDi->days   = $oDateInterval->days;
            $oDi->d      = $oDateInterval->d;
            $oDi->m      = $oDateInterval->m;
            $oDi->y      = $oDateInterval->y;
            $oDi->invert = $oDateInterval->invert;
    
            return $oDi;
        }
    
        public function compare(DateInterval $oDateInterval)
        {
            $oMyTotalSeconds   = $this->getTotalSeconds();
            $oYourTotalSeconds = $oDateInterval->getTotalSeconds();
    
            if($oMyTotalSeconds < $oYourTotalSeconds)
                return -1;
            elseif($oMyTotalSeconds == $oYourTotalSeconds)
                return 0;
            return 1;
        }
    
        /**
         * If $this->days has been calculated, we know it's accurate, so we'll use
         * that. If not, we need to make an assumption about month and year length,
         * which isn't necessarily a good idea. I've defined months as 30 days and
         * years as 365 days completely out of thin air, since I don't have the ISO
         * 8601 spec available to check if there's a standard assumption, but we
         * may in fact want to error out if we don't have $this->days available.
         */
        public function getTotalSeconds()
        {
            $iSeconds = $this->s + ($this->i * 60) + ($this->h * 3600);
    
            if($this->days > 0)
                $iSeconds += ($this->days * 86400);
    
            // @note Maybe you prefer to throw an Exception here per the note above
            else
                $iSeconds += ($this->d * 86400) + ($this->m * 2592000) + ($this->y * 31536000);
    
            if($this->invert)
                $iSeconds *= -1;
    
            return $iSeconds;
        }
    }
    
    0 讨论(0)
  • 2020-12-03 14:05

    In short, comparing of DateInterval objects is not currently supported by default (as of php 5.6).

    As you already know, the DateTime Objects are comparable.

    A way to achieve the desired result, is to subtract or add the DateInterval from a DateTime object and compare the two to determine the difference.

    Example: https://3v4l.org/kjJPg

    $buildDate = new DateTime('2012-02-15');
    $releaseDate = clone $buildDate;
    $releaseDate->setDate(2012, 2, 14);
    $buildDate->add(new DateInterval('P15D'));
    
    var_dump($releaseDate < $buildDate); //bool(true)
    

    Edit

    As of the release of PHP 7.1 the results are different than with PHP 5.x, due to the added support for microseconds.

    Example: https://3v4l.org/rCigC

    $a = new \DateTime;
    $b = new \DateTime;
    
    var_dump($a < $b);
    

    Results (7.1+):

    bool(true)
    

    Results (5.x - 7.0.x, 7.1.3):

    bool(false)
    

    To circumvent this behavior, it is recommended that you use clone to compare the DateTime objects instead.

    Example: https://3v4l.org/CSpV8

    $a = new \DateTime;
    $b = clone $a;
    var_dump($a < $b);
    

    Results (5.x - 7.x):

    bool(false)
    
    0 讨论(0)
  • 2020-12-03 14:07

    If you're working with time intervals that are not longer than a month, it's easy to convert 2 intervals to seconds and compare. $dateInterval->format("%s") only returns the seconds component so I ended up doing this:

    function intervalToSeconds($dateInterval) {
            $s = (
                ($dateInterval->format("%d")*24*60*60) + 
                ($dateInterval->format("%h")*60*60) + 
                ($dateInterval->format("%i")*60) + 
                $dateInterval->format("%s")
            );
            return $s;
        }
    
    0 讨论(0)
  • 2020-12-03 14:08

    No, this is not possible right now and it never will be. There is a fundamental problem with comparing two DateInterval's.

    A DateInterval is relative, while a DateTime is absolute: P1D means 1 day, so you would think that means (24*60*60) 86.400 seconds. But due to the Leap Second it isn't always the case.

    That looks like a rare situation, don't forget comparing months with days is even harder:

    P1M and P30D - which one is the greater one? is it P1M even though I'm currently in february? Or is it P30D even though I'm currently in August? What about PT24H30M and P1D? https://bugs.php.net/bug.php?id=49914#1490336933

    0 讨论(0)
提交回复
热议问题