new - ¿PHP DateInterval es comparable como DateTime?
new datetime php (6)
Descubrí que un objeto DateTime en PHP puede compararse con otro ya que los operadores ">" y "<" están sobrecargados.
¿Es lo mismo con DateInterval?
Mientras intentaba responder a esta pregunta, encontré algo extraño:
<?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'';
}
Siempre se hace eco de "Sólo una prueba". Las salidas var_dump son:
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)
Cuando intento con un DateTime como "01-03-2012", todo funciona.
¿De dónde viene $aujourdhui
? Claro que es lingüísticamente igual a $today
, ¡pero PHP no lo sabe! ¡Cambiar su código para usar $today
imprimirá "oK"
!
Si no se define, $aujourdhui->diff($release)
se evaluará a 0
si su intérprete de PHP no cancela con un error (el mío sí lo hace).
En resumen, la comparación de objetos DateInterval
actualmente no es compatible de forma predeterminada (a partir de php 5.6).
Como ya sabes, los objetos DateTime
son comparables.
Una forma de lograr el resultado deseado, es restar o agregar el DateInterval
de un objeto DateTime
y comparar los dos para determinar la diferencia.
Ejemplo: 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)
Editar
A partir del lanzamiento de PHP 7.1, los resultados son diferentes a los de PHP 5.x, debido al soporte agregado para microsegundos .
Ejemplo: https://3v4l.org/rCigC
$a = new /DateTime;
$b = new /DateTime;
var_dump($a < $b);
Resultados (7.1+) :
bool(true)
Resultados (5.x - 7.0.x, 7.1.3) :
bool(false)
Para evitar este comportamiento, se recomienda que use clone
para comparar los objetos DateTime
lugar.
Ejemplo: https://3v4l.org/CSpV8
$a = new /DateTime;
$b = clone $a;
var_dump($a < $b);
Resultados (5.x - 7.x) :
bool(true)
No, esto no es posible ahora y nunca lo será. Hay un problema fundamental con la comparación de dos DateInterval
''s.
Un DateInterval
es relativo, mientras que DateTime
es absoluto: P1D
significa 1 día, por lo que pensaría que eso significa (24 * 60 * 60) 86.400 segundos. Pero debido a Leap Second no siempre es así.
Parece una situación rara, no olvides que comparar meses con días es aún más difícil:
P1M y P30D, ¿cuál es el mayor? ¿Es P1M aunque actualmente estoy en febrero? ¿O es P30D aunque estoy actualmente en agosto? ¿Qué pasa con PT24H30M y P1D? https://bugs.php.net/bug.php?id=49914#1490336933
Parece que hubo una solicitud de error / característica relacionada , no estoy seguro si alguna vez se hizo en el troncal. No está documentado (lo que puedo encontrar) de ninguna manera, por lo que probablemente no sea seguro de usar.
Dicho esto, después de algunas pruebas parece que pueden compararse, pero solo después de que hayan sido "evaluados" de alguna manera (hacer un volcado de var cambia el resultado). Aquí está mi prueba / resultado:
<?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;
Resultado (omití los volcados completos de los objetos de intervalo):
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)
Utilicé la siguiente solución comparando DateIntervals:
version_compare(join(''.'', (array) $dateIntervalA), join(''.'', (array) $dateIntervalB));
EDITAR:
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;
Vea la respuesta de @fyrye para el razonamiento (¡y avíselo!). Mi respuesta original no trataba con los años bisiestos de forma segura.
Respuesta original
Mientras subía la votación a esta pregunta, anulaba la respuesta aceptada. Esto se debe a que no funcionó para mí en ninguna de mis instalaciones de PHP y porque, fundamentalmente, depende de algo roto internamente.
Lo que hice en cambio es migrar el parche mencionado que nunca se convirtió en troncal. FWIW Revisé una versión reciente, PHP 5.6.5 , y el parche aún no está allí. El código era trivial a puerto. Lo único es una advertencia de cómo se hace la comparación.
Si $ this-> days se ha calculado, sabemos que es preciso, así que lo usaremos. Si no es así, debemos hacer una suposición sobre el mes y la duración del año, lo cual no es necesariamente una buena idea. He definido los meses como 30 días y los años como 365 días completamente agotados, ya que no tengo la especificación ISO 8601 disponible para verificar si existe una suposición estándar, pero de hecho, es posible que queramos cometer errores si no lo hacemos. No tengo $ este-> días disponibles.
Aquí hay un ejemplo. Tenga en cuenta que si necesita comparar un DateInterval
que fue devuelto por alguna otra llamada, primero tendrá que create
un ComparableDateInterval
desde él, si desea usarlo como la fuente de la comparación.
$int15 = new ComparableDateInterval(''P15D'');
$int20 = new ComparableDateInterval(''P20D'');
var_dump($int15->compare($int20) == -1); // should be true;
Aquí está el código
/**
* 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;
}
}