thephpleague / period

PHP's time range API

Home Page:https://period.thephpleague.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

isBefore() and isAfter() comparisons do not take timezone into consideration

jackdpeterson opened this issue · comments

Issue summary

Given three Period's, with two in UTC and one in UTC -07:00, the one at -07:00 is noted as being earlier even though it should be sandwiched between the two UTC Periods.

array(3) {
  [0]=>
  object(League\Period\Period)#1668 (2) {
    ["startDate":protected]=>
    object(DateTimeImmutable)#994 (3) {
      ["date"]=>
      string(26) "2017-05-02 16:00:00.000000"
      ["timezone_type"]=>
      int(1)
      ["timezone"]=>
      string(6) "-07:00"
    }
    ["endDate":protected]=>
    object(DateTimeImmutable)#1637 (3) {
      ["date"]=>
      string(26) "2017-05-02 17:00:00.000000"
      ["timezone_type"]=>
      int(1)
      ["timezone"]=>
      string(6) "-07:00"
    }
  }
  [1]=>
  object(League\Period\Period)#1703 (2) {
    ["startDate":protected]=>
    object(DateTimeImmutable)#1696 (3) {
      ["date"]=>
      string(26) "2017-05-02 21:56:23.315010"
      ["timezone_type"]=>
      int(3)
      ["timezone"]=>
      string(3) "UTC"
    }
    ["endDate":protected]=>
    object(DateTimeImmutable)#1020 (3) {
      ["date"]=>
      string(26) "2017-05-03 21:57:23.000000"
      ["timezone_type"]=>
      int(3)
      ["timezone"]=>
      string(3) "UTC"
    }
  }
  [2]=>
  object(League\Period\Period)#1738 (2) {
    ["startDate":protected]=>
    object(DateTimeImmutable)#1031 (3) {
      ["date"]=>
      string(26) "2017-05-02 21:57:23.315010"
      ["timezone_type"]=>
      int(3)
      ["timezone"]=>
      string(3) "UTC"
    }
    ["endDate":protected]=>
    object(DateTimeImmutable)#1669 (3) {
      ["date"]=>
      string(26) "2017-05-03 21:58:23.000000"
      ["timezone_type"]=>
      int(3)
      ["timezone"]=>
      string(3) "UTC"
    }
  }
}

Proposed solution: when comparing isBefore() and isAfter(), the respective field should be converted into UTC.

@jackdpeterson could you produce the code you used to sort the different period object please. Thanks in advance because I failed to reproduce the bug here's the code I used:

<?php

use League\Period\Period;

class PeriodIterator extends ArrayIterator
{
    /**
     * new instance
     *
     * @param array|Traversable $collection Period object collection
     */
    public function __construct($collection = [])
    {
        if (!is_array($collection) && !$collection instanceof Traversable) {
            throw new InvalidArgumentException(sprintf('Argument passed must be iterable, %s given', is_object($collection) ? get_class($collection) : gettype($collection)));
        }

        parent::__construct($this->filterPeriod(...$collection));
    }

    /**
     * Filter Period objects
     *
     * @param Period ...$collection
     *
     * @return Period[]
     */
    protected function filterPeriod(Period ...$collection): array
    {
        return $collection;
    }

    /**
     * Enable adding new Period objects (used by PeriodIterator::append)
     *
     * @param string $index
     * @param Period $newval
     */
    public function offsetSet($index, $newval)
    {
        $this->filterPeriod($newval);
        parent::offsetSet($index, $newval);
    }
}

//sorting algo using Period::isBefore and Period::isAfter
$sort = function (Period $periodA, Period $periodB): int {
    if ($periodA->isBefore($periodB)) {
        return -1;
    }

    if ($periodA->isAfter($periodB)) {
        return 1;
    }

    return 0;
};

$collection = new PeriodIterator();
$collection->append(Period::createFromDuration(
    date_create_immutable('2017-05-02 15:00:00', new DateTimeZone('-0700')), //TimeZone -0700
    '1 HOUR'
));

$collection->append(Period::createFromDuration(
    date_create_immutable('2017-05-02 23:00:00', new DateTimeZone('UTC')), //UTC Timezone
    '1 HOUR'
));

$collection->append(Period::createFromDuration(
    date_create_immutable('2017-05-02 20:00:00', new DateTimeZone('UTC')), //UTC Timezone
    '1 HOUR'
));

$collection->uasort($sort);
echo '<pre>', var_export($collection->getArrayCopy());

results I got

<?php

array (
  2 => 
  League\Period\Period::__set_state(array(
     'startDate' => 
    DateTimeImmutable::__set_state(array(
       'date' => '2017-05-02 20:00:00.000000',
       'timezone_type' => 3,
       'timezone' => 'UTC',
    )),
     'endDate' => 
    DateTimeImmutable::__set_state(array(
       'date' => '2017-05-02 21:00:00.000000',
       'timezone_type' => 3,
       'timezone' => 'UTC',
    )),
  )),
  0 => 
  League\Period\Period::__set_state(array(
     'startDate' => 
    DateTimeImmutable::__set_state(array(
       'date' => '2017-05-02 15:00:00.000000',
       'timezone_type' => 1,
       'timezone' => '-07:00',
    )),
     'endDate' => 
    DateTimeImmutable::__set_state(array(
       'date' => '2017-05-02 16:00:00.000000',
       'timezone_type' => 1,
       'timezone' => '-07:00',
    )),
  )),
  1 => 
  League\Period\Period::__set_state(array(
     'startDate' => 
    DateTimeImmutable::__set_state(array(
       'date' => '2017-05-02 23:00:00.000000',
       'timezone_type' => 3,
       'timezone' => 'UTC',
    )),
     'endDate' => 
    DateTimeImmutable::__set_state(array(
       'date' => '2017-05-03 00:00:00.000000',
       'timezone_type' => 3,
       'timezone' => 'UTC',
    )),
  )),
)

The timezone is taken into account

See PR --- this is working as expected.

public function testIsBeforeWithMultipleTimeZones()
    {
        $period1 = Period::createFromDuration(\DateTime::createFromFormat(\DateTime::ISO8601,
            '2016-01-01T12:00:00Z'), '1 HOUR');

        $period2 = Period::createFromDuration(\DateTime::createFromFormat(\DateTime::ISO8601,
            '2016-01-01T04:00:00-06:00'), '1 HOUR');

        $this->assertFalse($period1->isBefore($period2));
        $this->assertTrue($period2->isBefore($period1));
        
    }```