31

我发现 PHP 中的 DateTime 对象可以与另一个对象进行比较,因为 ">" 和 "<" 运算符被重载。

与 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”时,一切正常。

4

7 回答 7

19

简而言之,DateInterval目前默认不支持对象的比较(从 php 5.6 开始)。

如您所知,DateTime对象是可比较的。

实现所需结果的一种方法是DateIntervalDateTime对象中减去或添加 并比较两者以确定差异。

示例:https ://3v4l.org/kjJPg

$buildDate = new DateTime('2012-02-15');
$releaseDate = clone $buildDate;
$releaseDate->setDate(2012, 2, 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(false)
于 2015-02-24T16:18:42.463 回答
12

看起来有一个相关的错误/功能请求,不确定是否曾经在后备箱中提出过。无论哪种方式都没有记录(我可以找到) - 所以使用起来可能不安全。

也就是说,经过一些测试后,它们似乎可以进行比较,但只有在它们以某种方式被“评估”之后(进行 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;

结果(我省略了区间对象的完整转储):

布尔(假)
布尔(假)
布尔(假)
布尔(假)
对象(日期间隔)#1 (8) {...}
对象(日期间隔)#2 (8) {...}
布尔(假)
布尔(真)
布尔(真)
布尔(假)

布尔(假)
布尔(真)
布尔(真)
布尔(假)
对象(日期间隔)#5 (8) {...}
布尔(假)
布尔(真)
布尔(真)
布尔(假)
于 2012-03-03T17:19:54.013 回答
8

编辑:

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从其他调用返回的createa 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;
    }
}
于 2015-02-09T20:43:23.007 回答
7

不,这现在是不可能的,而且永远不会。比较两个DateInterval's 存在一个基本问题。

ADateInterval是相对的,而 aDateTime是绝对的:P1D表示 1 天,所以你会认为这意味着 (24*60*60) 86.400 秒。但由于闰秒,情况并非总是如此。

这看起来是一种罕见的情况,不要忘记将月份与天数进行比较更加困难:

P1M 和 P30D - 哪个更大?即使我现在在二月,它是 P1M 吗?或者即使我现在在八月,它是 P30D 吗?PT24H30M 和 P1D 怎么样? https://bugs.php.net/bug.php?id=49914#1490336933

于 2017-11-29T16:58:50.110 回答
-1

从哪里来$aujourdhui?当然这和语言上的一样$today,但是 PHP 不知道!更改要使用的代码$today将打印"oK"

如果未定义,$aujourdhui->diff($release)将评估0您的 PHP 解释器是否不会因错误而中止(我的会)。

于 2012-03-03T17:05:02.130 回答
-1

如果您使用的时间间隔不超过一个月,则很容易将 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;
    }
于 2019-04-04T05:05:14.853 回答
-3

我使用以下解决方法比较 DateInterval:

version_compare(join('.', (array) $dateIntervalA), join('.', (array) $dateIntervalB));
于 2012-08-07T21:17:01.950 回答