【问题标题】:Are PHP DateInterval comparable like DateTime?PHP DateInterval 是否与 DateTime 类似?
【发布时间】:2012-03-03 16:50:25
【问题描述】:

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

【问题讨论】:

    标签: php datetime dateinterval


    【解决方案1】:

    不,现在不可能,以后也不会。比较两个DateInterval 存在一个根本问题。

    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

    【讨论】:

    • 从其他答案中了解到这是不可能的,从这个答案中了解到为什么不可能,这更好!
    【解决方案2】:

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

    如您所知,DateTime 对象具有可比性。

    实现所需结果的一种方法是从DateTime 对象中减去或加上DateInterval,然后比较两者以确定差异。

    示例: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)
    

    编辑

    由于added support for microseconds 的原因,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)
    

    【讨论】:

    • 正如您所说,为什么不使用 ImmutableDateTime 和对象取消引用来使其更快:if((new ImmutableDateTime)-&gt;add($firstInt) &lt; (new ImmutableDateTime)-&gt;add($secInt))
    • DateTimeImmutable 直到php 5.5 才可用,它为每个方法调用返回一个新的 DateTime 对象,而不是更新它(较慢)。 DateTime 将在每个方法调用失败时返回自身或 false。此外,代码中的其他地方可能需要 buildDate 和 releaseDate - 例如存储在数据库中、其他计算中或作为显示值。如果你希望它在一行中,你可以这样做:if((($b = (new DateTime)-&gt;modify('+15 day')) &amp;&amp; ($r = new DateTime('2012-02-14'))) &amp;&amp; $b &gt; $r) 但是为什么要牺牲可读性呢?
    • 您说的是 php 5.6,所以 DateTimeImmutable 可用。在我看来,对于这样的计算,我们不修改实例,而是创建一个全新的实例,因为它是为 C#、Java 和许多其他语言创建的,这似乎是合法的。关于可读性,如果你的观点是“你可以做得更糟”,我不在乎。 AFAIK,创建一个名为compareIntervals 的函数,它将调用我的行是可能的,并且真正独立于行号可读。
    • 就对象而言,我使用了您的 OP - 我不认为您在 2012 年发布日期之后拥有 PHP 版本。DateTimeImmutable 于 2013 年在(PHP 5 &gt;= 5.5.0) 中引入。我的重点是总体上的可重用性和可读性。这取决于您的用例。例如,如果它是 OOP,则修改 DateTime 对象更可行。例如:Order-&gt;buildDate-&gt;modify('+15 day'); 用于 DateTime,而 Order-&gt;buildDate = Order-&gt;buildDate-&gt;modify('+15 day'); 用于 DateTimeImmutable。然后使用Order-&gt;isBuildDateValid(); 在 Order->buildDate > Order->releaseDate 上进行比较。
    • 我明白你的意思。只是您说“截至 5.6”...顺便说一句,您在上一条评论中添加的所有内容都是真实的。
    【解决方案3】:

    看起来有一个related bug/feature request,不确定它是否曾出现在后备箱中。无论哪种方式都没有记录(我可以找到) - 所以使用起来可能不安全。

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

    【讨论】:

    • 是的。我也不会。但现在我明白发生了什么,这就是重点。我将使用 sub/add 和 DateTime 比较。它们更可靠。
    • 查看我的新答案,它可靠且易于使用。
    • @quickshiftin 正如 artragis 的评论所示,问题是关于实际行为,以及为什么它很奇怪。这回答了这个问题。
    【解决方案4】:

    编辑:

    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 安装都不起作用,而且从根本上说它取决于内部损坏的东西。

    我所做的是迁移the aforementioned patch,它从未进入主干。 FWIW 我检查了最近的版本,PHP 5.6.5,但补丁仍然不存在。代码很容易移植。唯一的问题是它如何进行比较的警告

    如果已经计算了 $this->days,我们知道它是准确的,所以我们将 用那个。如果不是,我们需要对月份和年份进行假设 长度,这不一定是一个好主意。我将月份定义为 30 天和年就像 365 天完全凭空而来,因为我不 有可用的 ISO 8601 规范来检查是否有标准 假设,但实际上如果我们没有 $this->可用天数。

    这是一个例子。请注意,如果您需要比较从其他调用返回的 DateInterval,如果您想将其用作比较的来源,您必须首先从它中找到 createComparableDateInterval

    $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;
        }
    }
    

    【讨论】:

    • 什么情况下不计算日期?
    • 如果 $this-&gt;daysnull 并且当年没有 365 天。
    • 即使有很多关于不是 365 天的年份的 cmets,我认为这个答案鼓励使用不适用于闰年、闰秒等的函数。这可能会引入微妙但严重的问题错误。请改用 fyrye 的解决方案。
    • @LarsNyström 这是一个有趣的解决方案,可以肯定的是更安全。我唯一的抱怨是它没有从 2 个DateInterval 实例的上下文中回答。
    • 我应该指出我已经更新了我的答案以使用@fyrye 引入的技术,并且我的原始答案在不到一年的时间间隔内工作正常。
    【解决方案5】:

    $aujourdhui 来自哪里?当然它在语言上与$today 相同,但PHP 不知道!将代码更改为使用 $today 将打印 "oK"!

    如果未定义,$aujourdhui-&gt;diff($release) 将评估为 0 如果您的 PHP 解释器没有因错误而中止(我的)。

    【讨论】:

    • 对不起,这只是一个失败的翻译。因为只是个人测试,我没有用英文写。我编辑了我的帖子,问题仍然存在。
    • 这很奇怪...您使用的是什么版本的 PHP?我在我的机器上尝试了 5.3.6 版本,它回显了 oK!
    【解决方案6】:

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

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

    【讨论】:

    • 不起作用:$int20 = new DateInterval('P20D'); $int20_2 = new DateInterval('P20D'); var_dump(join('.', (array) $int20), join('.', (array) $int20_2));给出两个“等效版本”,但 version_compare 发送 (-1),因此不处理相等性。此外,我记得当您使用减法方法时,会在 DateInterval 内创建一个新字段,使其无法与手动创建的 DateInterval 相比。
    • @artragis 我无法复制 version_compare 给相同的字符串 -1;也就是说,在不是有效版本字符串确实的字符串上使用它显然是不明智的。也许 PHP 7 中的行为与您 5 年前测试的任何版本都不同。
    【解决方案7】:

    如果您使用的时间间隔不超过一个月,则可以轻松地将 2 个间隔转换为秒并进行比较。 $dateInterval-&gt;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;
        }
    

    【讨论】:

    • 应该是 %a 而不是 %d - 但这需要 DIDT 对象之间的实际差异。
    猜你喜欢
    • 1970-01-01
    • 2018-02-02
    • 2014-02-25
    • 1970-01-01
    • 2014-12-26
    • 1970-01-01
    • 1970-01-01
    • 2012-07-24
    • 1970-01-01
    相关资源
    最近更新 更多