Являются ли PHP DateInterval сопоставимыми с DateTime?

Я обнаружил, что объект DateTime в PHP можно сравнить с другим, поскольку операторы «>» и «<» перегружены.

То же самое с DateInterval?

Когда я пытался ответить на этот вопрос, я нашел что-то странное:

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

Это всегда повторяет «Просто тест». Выходы var_dump:

 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) 

Когда я пытаюсь использовать DateTime как «01-03-2012», все работает.

Похоже, что есть связанный запрос об ошибке / функции , не уверен, что это когда-либо делалось в багажнике. Это не документировано (что я могу найти) в любом случае – возможно, небезопасно.

Тем не менее, после некоторого тестирования кажется, что их можно сравнить, но только после того, как они были «оценены» каким-то образом (выполнение дампа var меняет результат). Вот мой тест / результат:

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

Результат (я опустил полные дампы интервальных объектов):

 BOOL (ложь)
 BOOL (ложь)
 BOOL (ложь)
 BOOL (ложь)
 object (DateInterval) # 1 (8) {...}
 object (DateInterval) # 2 (8) {...}
 BOOL (ложь)
 BOOL (истина)
 BOOL (истина)
 BOOL (ложь)

 BOOL (ложь)
 BOOL (истина)
 BOOL (истина)
 BOOL (ложь)
 object (DateInterval) # 5 (8) {...}
 BOOL (ложь)
 BOOL (истина)
 BOOL (истина)
 BOOL (ложь)

Короче говоря, сравнение объектов DateInterval по умолчанию не поддерживается (по php 5.6).

Как вы уже знаете, объекты DateTime сопоставимы.

Способ достижения желаемого результата состоит в том, чтобы вычесть или добавить DateInterval из объекта DateTime и сравнить эти два, чтобы определить разницу.

Пример: 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) 

редактировать

Начиная с версии PHP 7.1 результаты отличаются от результатов, полученных с помощью PHP 5.x, благодаря дополнительной поддержке микросекунд .

Пример: https://3v4l.org/rCigC

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

Результаты (7.1+) :

 bool(false) 

Результаты (5.x – 7.0.x, 7.1.3) :

 bool(true) 

Чтобы обойти это поведение, рекомендуется использовать clone для сравнения объектов DateTime .

Пример: https://3v4l.org/CSpV8

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

Результаты (5.x – 7.x) :

 bool(true) 

РЕДАКТИРОВАТЬ:

 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; 

См. Ответ @ fyrye для обоснования (и повышайте его!). Мой первоначальный ответ не касался високосных лет безопасно.


Оригинальный ответ

Хотя я поддержал этот вопрос, я остановил принятый ответ. Это потому, что это не сработало для меня ни на одной из моих установок PHP, и потому, что в основном это зависает от чего-то, что нарушено внутри.

Вместо этого я выполнил миграцию вышеупомянутого патча, который никогда не попадал в багажник. FWIW Я проверил недавний выпуск, PHP 5.6.5 , и патча все еще нет. Код был тривиальным для порта. Единственное, что является предупреждением о том, как это делает сравнение

Если $ this-> days был рассчитан, мы знаем, что это точно, поэтому мы будем использовать это. Если нет, нам нужно сделать предположение о длине месяца и года, что не всегда является хорошей идеей. Я определил месяцы как 30 дней и лет как 365 дней полностью из воздуха, так как у меня нет спецификации ISO 8601, чтобы проверить, есть ли стандартное предположение, но мы можем на самом деле хотеть ошибиться, если мы надеемся У вас есть $ this-> days.

Вот пример. Обратите внимание, что если вам нужно сравнить DateInterval который был возвращен из какого-либо другого вызова, сначала вам необходимо создать DateInterval , если вы хотите использовать его в качестве источника сравнения.

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

Вот код

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

Нет, сейчас это невозможно, и этого никогда не будет. Существует принципиальная проблема при сравнении двух DateInterval .

DateInterval относительна, а DateTime является абсолютной: P1D означает 1 день, поэтому вы думаете, что это означает (24 * 60 * 60) 86,400 секунд. Но из-за прыжка второй это не всегда так.

Это похоже на редкую ситуацию, не забывайте, что сравнивать месяцы с днями еще сложнее:

P1M и P30D – какой из них больше? это P1M, хотя я в настоящее время в феврале? Или это P30D, хотя я в настоящее время в августе? Что относительно PT24H30M и P1D? https://bugs.php.net/bug.php?id=49914#1490336933

Откуда $aujourdhui ? Конечно, это то же самое, что и $today лингвистически, но PHP этого не знает! Изменение кода для использования $today будет печатать "oK" !

Если не определено, $aujourdhui->diff($release) будет оцениваться до 0 если ваш PHP-интерпретатор не прерывается с ошибкой (мой делает).

Я использовал следующее обходное сравнение, сравнивающее DateIntervals:

 version_compare(join('.', (array) $dateIntervalA), join('.', (array) $dateIntervalB));