isBefore() and isAfter() comparisons do not take timezone into consideration
jackdpeterson opened this issue · comments
Jack Peterson commented
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.
ignace nyamagana butera commented
@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
Jack Peterson commented
See PR --- this is working as expected.
Jack Peterson commented
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));
}```