Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion src/ChronosDatePeriod.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@

namespace Cake\Chronos;

use DateInterval;
use DatePeriod;
use InvalidArgumentException;
use Iterator;

/**
Expand All @@ -32,15 +34,39 @@ class ChronosDatePeriod implements Iterator
protected Iterator $iterator;

/**
* @param \DatePeriod $period
* @param \DatePeriod $period The DatePeriod to wrap.
* @throws \InvalidArgumentException If the period has a zero interval which would cause an infinite loop.
*/
public function __construct(DatePeriod $period)
{
if (static::isZeroInterval($period->getDateInterval())) {
throw new InvalidArgumentException(
'Cannot create a period with a zero interval. This would cause an infinite loop when iterating.',
);
}

/** @var \Iterator<int, \DateTimeInterface> $iterator */
$iterator = $period->getIterator();
$this->iterator = $iterator;
}

/**
* Check if a DateInterval is effectively zero.
*
* @param \DateInterval $interval The interval to check.
* @return bool True if the interval is zero.
*/
protected static function isZeroInterval(DateInterval $interval): bool
{
return $interval->y === 0
&& $interval->m === 0
&& $interval->d === 0
&& $interval->h === 0
&& $interval->i === 0
&& $interval->s === 0
&& (int)($interval->f * 1_000_000) === 0;
}

/**
* @return \Cake\Chronos\ChronosDate
*/
Expand Down
28 changes: 27 additions & 1 deletion src/ChronosPeriod.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@

namespace Cake\Chronos;

use DateInterval;
use DatePeriod;
use InvalidArgumentException;
use Iterator;

/**
Expand All @@ -32,15 +34,39 @@ class ChronosPeriod implements Iterator
protected Iterator $iterator;

/**
* @param \DatePeriod $period
* @param \DatePeriod $period The DatePeriod to wrap.
* @throws \InvalidArgumentException If the period has a zero interval which would cause an infinite loop.
*/
public function __construct(DatePeriod $period)
{
if (static::isZeroInterval($period->getDateInterval())) {
throw new InvalidArgumentException(
'Cannot create a period with a zero interval. This would cause an infinite loop when iterating.',
);
}

/** @var \Iterator<int, \DateTimeInterface> $iterator */
$iterator = $period->getIterator();
$this->iterator = $iterator;
}

/**
* Check if a DateInterval is effectively zero.
*
* @param \DateInterval $interval The interval to check.
* @return bool True if the interval is zero.
*/
protected static function isZeroInterval(DateInterval $interval): bool
{
return $interval->y === 0
&& $interval->m === 0
&& $interval->d === 0
&& $interval->h === 0
&& $interval->i === 0
&& $interval->s === 0
&& (int)($interval->f * 1_000_000) === 0;
}

/**
* @return \Cake\Chronos\Chronos
*/
Expand Down
29 changes: 28 additions & 1 deletion tests/TestCase/ChronosDatePeriodTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use DateInterval;
use DatePeriod;
use DateTime;
use InvalidArgumentException;

class ChronosDatePeriodTest extends TestCase
{
Expand All @@ -30,11 +31,37 @@ public function testChronosPeriod(): void
$output[$key] = $value;
}
$this->assertCount(4, $output);
$this->assertInstanceOf(ChronosDAte::class, $output[0]);
$this->assertInstanceOf(ChronosDate::class, $output[0]);
$this->assertSame('2025-01-01 00:00:00', $output[0]->format('Y-m-d H:i:s'));
$this->assertInstanceOf(ChronosDate::class, $output[1]);
$this->assertSame('2025-01-02 00:00:00', $output[1]->format('Y-m-d H:i:s'));
$this->assertInstanceOf(ChronosDate::class, $output[3]);
$this->assertSame('2025-01-04 00:00:00', $output[3]->format('Y-m-d H:i:s'));
}

public function testZeroIntervalThrowsException(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Cannot create a period with a zero interval');

$period = new DatePeriod(
new DateTime('2025-01-01'),
new DateInterval('PT0S'),
new DateTime('2025-01-02'),
);
new ChronosDatePeriod($period);
}

public function testZeroIntervalAllZeroComponents(): void
{
$this->expectException(InvalidArgumentException::class);

$interval = new DateInterval('P0D');
$period = new DatePeriod(
new DateTime('2025-01-01'),
$interval,
new DateTime('2025-01-02'),
);
new ChronosDatePeriod($period);
}
}
27 changes: 27 additions & 0 deletions tests/TestCase/ChronosPeriodTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use DateInterval;
use DatePeriod;
use DateTime;
use InvalidArgumentException;

class ChronosPeriodTest extends TestCase
{
Expand All @@ -35,4 +36,30 @@ public function testChronosPeriod(): void
$this->assertInstanceOf(Chronos::class, $output[1]);
$this->assertSame('2025-01-01 01:00:00', $output[1]->format('Y-m-d H:i:s'));
}

public function testZeroIntervalThrowsException(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Cannot create a period with a zero interval');

$period = new DatePeriod(
new DateTime('2025-01-01'),
new DateInterval('PT0S'),
new DateTime('2025-01-02'),
);
new ChronosPeriod($period);
}

public function testZeroIntervalAllZeroComponents(): void
{
$this->expectException(InvalidArgumentException::class);

$interval = new DateInterval('P0D');
$period = new DatePeriod(
new DateTime('2025-01-01'),
$interval,
new DateTime('2025-01-02'),
);
new ChronosPeriod($period);
}
}