質問

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?

As I was trying to answer this question, I found something strange:

<?php 

$today = new DateTime();
$release  = new DateTime('14-02-2012');
$building_time = new DateInterval('P15D');
var_dump($today->diff($release));
var_dump($building_time);
var_dump($today->diff($release)>$building_time);
var_dump($today->diff($release)<$building_time);
if($today->diff($release) < $building_time){
    echo 'oK';
}else{
    echo 'Just a test';
}

It always echoes "Just a test". The var_dump outputs are:

object(DateInterval)#4 (8) {
  ["y"]=>
  int(0)
  ["m"]=>
  int(0)
  ["d"]=>
  int(18)
  ["h"]=>
  int(16)
  ["i"]=>
  int(49)
  ["s"]=>
  int(19)
  ["invert"]=>
  int(1)
  ["days"]=>
  int(18)
}
object(DateInterval)#3 (8) {
  ["y"]=>
  int(0)
  ["m"]=>
  int(0)
  ["d"]=>
  int(15)
  ["h"]=>
  int(0)
  ["i"]=>
  int(0)
  ["s"]=>
  int(0)
  ["invert"]=>
  int(0)
  ["days"]=>
  bool(false)
}
bool(false)
bool(true)

When I try with a DateTime as "01-03-2012" everything works.

役に立ちましたか?

解決

Looks like there was a related bug/feature request, not sure if that ever made it in the trunk. It's not documented (that I Can find) either way - so probably not safe to use.

That said, after some testing it seems that they can be compared, but only after they've been 'evaluated' in some way (doing a var dump changes the outcome). Here's my test/result:

<?php
$int15 = new DateInterval('P15D');
$int20 = new DateInterval('P20D');

var_dump($int15 > $int20); //should be false;
var_dump($int20 > $int15); //should be true;

var_dump($int15 < $int20); //should be true;
var_dump($int20 < $int15); //should be false;

var_dump($int15);
var_dump($int20);

var_dump($int15 > $int20); //should be false;
var_dump($int20 > $int15); //should be true;

var_dump($int15 < $int20); //should be true;
var_dump($int20 < $int15); //should be false;

$date = new DateTime();
$diff = $date->diff(new DateTime("+10 days"));

var_dump($int15 < $diff); //should be false;
var_dump($diff < $int15); //should be true;

var_dump($int15 > $diff); //should be true;
var_dump($diff > $int15); //should be false;

var_dump($diff);

var_dump($int15 < $diff); //should be false;
var_dump($diff < $int15); //should be true;

var_dump($int15 > $diff); //should be true;
var_dump($diff > $int15); //should be false;

Result (I've omitted the full dumps of the interval objects):

bool(false)
bool(false)
bool(false)
bool(false)
object(DateInterval)#1 (8) {...}
object(DateInterval)#2 (8) {...}
bool(false)
bool(true)
bool(true)
bool(false)

bool(false)
bool(true)
bool(true)
bool(false)
object(DateInterval)#5 (8) {...}
bool(false)
bool(true)
bool(true)
bool(false)

他のヒント

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/XeSJe

$buildDate = new DateTime();
$releaseDate  = clone $buildDate;
$releaseDate->modify('2012-02-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(true)

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

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

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).

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

I used the following workaround comparing DateIntervals:

version_compare(join('.', (array) $dateIntervalA), join('.', (array) $dateIntervalB));
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top