Является ли 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" все работает.
6 ответов
Похоже, что был связанный запрос об ошибке / функции, не уверен, что это когда-либо было сделано в стволе. Это не задокументировано (что я могу найти) в любом случае - так что, вероятно, небезопасно для использования.
Тем не менее, после некоторого тестирования кажется, что их можно сравнивать, но только после того, как они каким-то образом "оценены" (выполнение дампа 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(true)
Результаты (5.x - 7.0.x, 7.1.3):
bool(false)
Чтобы обойти это поведение, рекомендуется использовать clone
сравнить DateTime
объекты вместо
Пример: https://3v4l.org/CSpV8
$a = new \DateTime;
$b = clone $a;
var_dump($a < $b);
Результаты (5.x - 7.x):
bool(true)
Нет, сейчас это невозможно и никогда не будет. Существует фундаментальная проблема сравнения двух DateInterval
"S.
DateInterval
относительно, в то время как DateTime
абсолютно: P1D
означает 1 день, так что вы думаете, что означает (24*60*60) 86,400 секунд. Но из-за Leap Second это не всегда так.
Это выглядит как редкая ситуация, не забудьте сравнить месяцы с днями еще сложнее:
P1M и P30D - какой из них больше? это P1M, хотя я сейчас нахожусь в феврале? Или это P30D, хотя я сейчас в августе? А как насчет PT24H30M и P1D? https://bugs.php.net/bug.php?id=49914
РЕДАКТИРОВАТЬ:
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-> дней.
Вот пример. Обратите внимание, если вам нужно сравнить DateInterval
который был возвращен из другого звонка, вам придется create
ComparableDateInterval
Исходя из этого, если вы хотите использовать его в качестве источника сравнения.
$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;
}
}
Где же $aujourdhui
родом из? Конечно, это так же, как $today
лингвистически, но PHP этого не знает! Изменение вашего кода для использования $today
распечатает "oK"
!
Если не определено, $aujourdhui->diff($release)
будет оценивать 0
если ваш интерпретатор PHP не прерывает с ошибкой (мой делает).
Если вы работаете с интервалами времени, не превышающими месяц, легко преобразовать 2 интервала в секунды и сравнить. $dateInterval->format("%s")
возвращает только компонент секунд, поэтому я в итоге сделал это:
function intervalToSeconds($dateInterval) {
$s = (
($dateInterval->format("%d")*24*60*60) +
($dateInterval->format("%h")*60*60) +
($dateInterval->format("%i")*60) +
$dateInterval->format("%s")
);
return $s;
}
Я использовал следующий обходной путь, сравнивая DateIntervals:
version_compare(join('.', (array) $dateIntervalA), join('.', (array) $dateIntervalB));