Является ли PHP DateInterval сопоставимым с DateTime? - PullRequest
26 голосов
/ 03 марта 2012

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

То же самое с 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", все работает.

Ответы [ 7 ]

13 голосов
/ 24 февраля 2015

Короче говоря, сравнение 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 из-за добавленной поддержки для микросекунды s.

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

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

Results (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)
11 голосов
/ 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(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)
8 голосов
/ 09 февраля 2015

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;

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


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

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

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

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

Вот пример. Обратите внимание: если вам нужно сравнить DateInterval, который был возвращен из какого-либо другого вызова, вам сначала нужно будет create a 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;
    }
}
4 голосов
/ 29 ноября 2017

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

A 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#1490336933

0 голосов
/ 04 апреля 2019

Если вы работаете с временными интервалами, которые не длиннее месяца, легко конвертировать 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;
    }
0 голосов
/ 08 августа 2012

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

version_compare(join('.', (array) $dateIntervalA), join('.', (array) $dateIntervalB));
0 голосов
/ 03 марта 2012

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...