From 89779174907515c5cbe3ce8a9fb3476b264357e5 Mon Sep 17 00:00:00 2001 From: Artem Henvald Date: Tue, 27 Apr 2021 21:26:28 +0300 Subject: [PATCH] Add intersects method for date range (#10) --- README.md | 33 ++- docs/images/does_not_intersect.png | Bin 0 -> 22029 bytes docs/images/intersect.png | Bin 0 -> 25133 bytes src/DateTimeRange.php | 124 ++++++++++ src/DateTimeRangeInterface.php | 55 +++++ tests/DateTimeRangeTest.php | 381 +++++++++++++++++++++++++++++ 6 files changed, 590 insertions(+), 3 deletions(-) create mode 100644 docs/images/does_not_intersect.png create mode 100644 docs/images/intersect.png create mode 100644 src/DateTimeRange.php create mode 100644 src/DateTimeRangeInterface.php create mode 100644 tests/DateTimeRangeTest.php diff --git a/README.md b/README.md index b381764..86e619c 100644 --- a/README.md +++ b/README.md @@ -74,11 +74,12 @@ $dateTimeZone2 = $dateTimeHelper->createDateTimeZone('Europe/Kiev'); // Or with $dateTimeZone3 = $dateTimeHelper->createDateTimeZoneUtc(); // Another method to get UTC timezone ``` -### Immutable ValueObject `DateRange` +### Immutable `DateRange` ValueObject -You often need to manipulate with since/till dates, so-called date ranges. +You often needed to manipulate with since/till dates, so-called date ranges. By its nature, date range is a `ValueObject`, it can be reused many times for different purposes. -This library provides a `DateRange` immutable class, which is not able to be changed after its creation. +This library provides a `DateRange` immutable class, which is not able to be changed after its creation. +`DateRange` operates only with dates and ignore time. ```php use Fresh\DateTime\DateRange; @@ -90,6 +91,32 @@ $dateRange2 = new DateRange(new \DateTime('yesterday'), new \DateTime('tomorrow' $dateRange1->isEqual($dateRange2); // Returns FALSE, because date ranges have different timezones ``` +### Immutable `DateTimeRange` ValueObject + +This library provides also immutable class `DateTimeRange`, instead of `DateRange` it checks date and time. + +```php +use Fresh\DateTime\DateTimeRange; + +$dateTimeRange1 = new DateTimeRange(new \DateTime('2000-01-01 19:00:00'), new \DateTime('2000-01-01 21:00:00')); +$dateTimeRange2 = new DateTimeRange(new \DateTime('2000-01-01 19:00:00'), new \DateTime('2000-01-01 21:00:00', new \DateTimeZone('Europe/Kiev'))); +$dateTimeRange3 = new DateTimeRange(new \DateTime('2000-01-01 20:00:00'), new \DateTime('2000-01-01 22:00:00')); + +// There is also the `isEqual` method to compare two DateTimeRange objects. +$dateTimeRange1->isEqual($dateTimeRange2); // Returns FALSE, because datetime ranges have different timezones + +// There is also the `intersects` method to check if datetime range intersected each other. +$dateTimeRange1->intersects($dateTimeRange3); // Returns TRUE, because datetime ranges are intersected +``` + +#### Examples of date ranges with intersection + +![Example of intersection](docs/images/intersect.png "Example of intersection") + +#### Examples of date ranges without intersection + +![Example of no intersection](docs/images/does_not_intersect.png "Example of no intersection") + ### Getting array of objects/strings of all dates in date range ```php diff --git a/docs/images/does_not_intersect.png b/docs/images/does_not_intersect.png new file mode 100644 index 0000000000000000000000000000000000000000..c199595e5859a7f691627a7acced298f218f0090 GIT binary patch literal 22029 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

3u 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 ALI3~& literal 0 HcmV?d00001 diff --git a/docs/images/intersect.png b/docs/images/intersect.png new file mode 100644 index 0000000000000000000000000000000000000000..ff4d8ec0d51485fdd3c248d269b87e1b2876cc37 GIT binary patch literal 25133 zcmbrm1yoyIv@S}$#abMSm0-o4;It6jiaWHlIF#ZpEu{n}xKkjw6>qQ8o&MzTvRORrnpJQWTVc{vrgEX+P?mJ*% z-Fg1_F6PLK^<8I7^W>AfKBmQQzkT0{XTzt&9DL-Wpd#~V?LH+T8D5(96dEIqr2u-Z z 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 F{~sLkE%yKb literal 0 HcmV?d00001 diff --git a/src/DateTimeRange.php b/src/DateTimeRange.php new file mode 100644 index 0000000..d911ccb --- /dev/null +++ b/src/DateTimeRange.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fresh\DateTime; + +use Fresh\DateTime\Exception\LogicException; + +/** + * DateTimeRange. + * + * @author Artem Henvald + */ +final class DateTimeRange implements DateTimeRangeInterface +{ + /** @var \DateTimeImmutable */ + private $since; + + /** @var \DateTimeImmutable */ + private $till; + + /** + * @param \DateTimeInterface $since + * @param \DateTimeInterface $till + */ + public function __construct(\DateTimeInterface $since, \DateTimeInterface $till) + { + $this->assertSameTimezones($since, $till); + + $this->since = DateTimeCloner::cloneIntoDateTimeImmutable($since); + $this->till = DateTimeCloner::cloneIntoDateTimeImmutable($till); + } + + /** + * {@inheritdoc} + */ + public function getTimezone(): \DateTimeZone + { + return $this->since->getTimezone(); // Since and till timezones are equal + } + + /** + * {@inheritdoc} + */ + public function getTimezoneName(): string + { + return $this->since->getTimezone()->getName(); // Since and till timezones are equal + } + + /** + * {@inheritdoc} + */ + public function getSince(): \DateTimeImmutable + { + return $this->since; + } + + /** + * {@inheritdoc} + */ + public function getTill(): \DateTimeImmutable + { + return $this->till; + } + + /** + * {@inheritdoc} + */ + public function isEqual(DateTimeRangeInterface $dateTimeRange): bool + { + $dateRangeSince = $dateTimeRange->getSince(); + $dateRangeTill = $dateTimeRange->getTill(); + + return $this->since->getTimestamp() === $dateRangeSince->getTimestamp() + && $this->till->getTimestamp() === $dateRangeTill->getTimestamp() + && $this->since->getTimezone()->getName() === $dateRangeSince->getTimezone()->getName() + && $this->till->getTimezone()->getName() === $dateRangeTill->getTimezone()->getName(); + } + + /** + * {@inheritdoc} + */ + public function intersects(DateTimeRangeInterface $dateTimeRange): bool + { + if ($this->getTimezoneName() !== $dateTimeRange->getTimezoneName()) { + throw new LogicException('Timezones of datetime ranges are different'); + } + + $givenDateRangeSince = $dateTimeRange->getSince(); + $givenDateRangeTill = $dateTimeRange->getTill(); + + switch (true) { + case $this->since === $givenDateRangeSince && $this->till === $givenDateRangeTill: // Current date range is equal to the given date range + case $givenDateRangeSince < $this->since && $this->till < $givenDateRangeTill: // Current date range is fully inside the given date range + case $this->since < $givenDateRangeSince && $givenDateRangeTill < $this->till: // Given date range is fully inside the current date range + case $givenDateRangeSince <= $this->since && $this->since < $givenDateRangeTill: // Current date range beginning is inside the given date range + case $givenDateRangeSince < $this->till && $this->till <= $givenDateRangeTill: // Current date range ending is inside the given date range + return true; + default: + return false; + } + } + + /** + * @param \DateTimeInterface $since + * @param \DateTimeInterface $till + * + * @throws LogicException + */ + private function assertSameTimezones(\DateTimeInterface $since, \DateTimeInterface $till): void + { + if ($since->getTimezone()->getName() !== $till->getTimezone()->getName()) { + throw new LogicException('Datetimes have different timezones'); + } + } +} diff --git a/src/DateTimeRangeInterface.php b/src/DateTimeRangeInterface.php new file mode 100644 index 0000000..7e4ab0c --- /dev/null +++ b/src/DateTimeRangeInterface.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fresh\DateTime; + +/** + * DateTimeRangeInterface. + * + * @author Artem Henvald + */ +interface DateTimeRangeInterface +{ + /** + * @return \DateTimeZone + */ + public function getTimezone(): \DateTimeZone; + + /** + * @return string + */ + public function getTimezoneName(): string; + + /** + * @return \DateTimeImmutable + */ + public function getSince(): \DateTimeImmutable; + + /** + * @return \DateTimeImmutable + */ + public function getTill(): \DateTimeImmutable; + + /** + * @param DateTimeRangeInterface $dateTimeRange + * + * @return bool + */ + public function isEqual(self $dateTimeRange): bool; + + /** + * @param DateTimeRangeInterface $dateTimeRange + * + * @return bool + */ + public function intersects(self $dateTimeRange): bool; +} diff --git a/tests/DateTimeRangeTest.php b/tests/DateTimeRangeTest.php new file mode 100644 index 0000000..09c59bb --- /dev/null +++ b/tests/DateTimeRangeTest.php @@ -0,0 +1,381 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fresh\DateTime\Tests; + +use Fresh\DateTime\DateTimeRange; +use Fresh\DateTime\DateTimeRangeInterface; +use Fresh\DateTime\Exception\LogicException; +use PHPUnit\Framework\TestCase; + +/** + * DateTimeRangeTest. + * + * @author Artem Henvald + */ +class DateTimeRangeTest extends TestCase +{ + public function testConstructor(): void + { + $since = new \DateTime('now'); + $till = new \DateTime('tomorrow'); + + $dateTimeRange = new DateTimeRange($since, $till); + + self::assertInstanceOf(DateTimeRangeInterface::class, $dateTimeRange); + self::assertInstanceOf(\DateTimeImmutable::class, $dateTimeRange->getSince()); + self::assertInstanceOf(\DateTimeImmutable::class, $dateTimeRange->getTill()); + self::assertSame($since->format('Y-m-d H:i:s'), $dateTimeRange->getSince()->format('Y-m-d H:i:s')); + self::assertSame($till->format('Y-m-d H:i:s'), $dateTimeRange->getTill()->format('Y-m-d H:i:s')); + } + + public function testConstructorWithExceptionForDifferentTimezones(): void + { + $since = new \DateTime('now', new \DateTimeZone('Europe/Kiev')); + $till = new \DateTime('now', new \DateTimeZone('Europe/Warsaw')); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Datetimes have different timezones'); + + new DateTimeRange($since, $till); + } + + public function testGetTimezone(): void + { + $since = new \DateTime('now', new \DateTimeZone('Europe/Kiev')); + $till = new \DateTime('now', new \DateTimeZone('Europe/Kiev')); + + $dateTimeRange = new DateTimeRange($since, $till); + + self::assertEquals('Europe/Kiev', $dateTimeRange->getTimezone()->getName()); + } + + public function testGetTimezoneName(): void + { + $since = new \DateTime('now', new \DateTimeZone('Europe/Kiev')); + $till = new \DateTime('now', new \DateTimeZone('Europe/Kiev')); + + $dateTimeRange = new DateTimeRange($since, $till); + + self::assertEquals('Europe/Kiev', $dateTimeRange->getTimezoneName()); + } + + public function testIsEqual(): void + { + $dateTimeRange1 = new DateTimeRange( + new \DateTime('2000-01-01 14:45:00', new \DateTimeZone('UTC')), + new \DateTime('2000-01-01 15:00:00', new \DateTimeZone('UTC')) + ); + $dateTimeRange2 = new DateTimeRange( + new \DateTime('2000-01-01 14:45:00', new \DateTimeZone('UTC')), + new \DateTime('2000-01-01 15:00:00', new \DateTimeZone('UTC')) + ); + $dateTimeRange3 = new DateTimeRange( + new \DateTime('2000-01-01 12:00:00', new \DateTimeZone('UTC')), + new \DateTime('2000-01-01 15:00:00', new \DateTimeZone('UTC')) + ); + $dateTimeRange4 = new DateTimeRange( + new \DateTime('2000-01-01 14:45:00', new \DateTimeZone('UTC')), + new \DateTime('2000-01-01 20:00:00', new \DateTimeZone('UTC')) + ); + $dateTimeRange5 = new DateTimeRange( + new \DateTime('2000-01-01 12:00:00', new \DateTimeZone('UTC')), + new \DateTime('2000-01-01 21:00:00', new \DateTimeZone('UTC')) + ); + $dateTimeRange6 = new DateTimeRange( + new \DateTime('2000-01-01 14:45:00', new \DateTimeZone('Europe/Kiev')), + new \DateTime('2000-01-01 15:00:00', new \DateTimeZone('Europe/Kiev')) + ); + + self::assertTrue($dateTimeRange1->isEqual($dateTimeRange2), 'Since/till timezones are same, time is same'); + self::assertFalse($dateTimeRange1->isEqual($dateTimeRange3), 'Since/till timezones are same, since time is different'); + self::assertFalse($dateTimeRange1->isEqual($dateTimeRange4), 'Since/till timezones are same, till time is different'); + self::assertFalse($dateTimeRange1->isEqual($dateTimeRange5), 'Since/till timezones are same, since/till time is different'); + self::assertFalse($dateTimeRange1->isEqual($dateTimeRange6), 'Since/till timezones are different'); + } + + /** + * @param DateTimeRange $dateTimeRange1 + * @param DateTimeRange $dateTimeRange2 + * @param bool $intersects + * + * @dataProvider dataProviderForTestIntersectsSameDates + * @dataProvider dataProviderForTestIntersectsDifferentDates + */ + public function testIntersects(DateTimeRange $dateTimeRange1, DateTimeRange $dateTimeRange2, bool $intersects): void + { + self::assertSame($intersects, $dateTimeRange1->intersects($dateTimeRange2)); + } + + public static function dataProviderForTestIntersectsSameDates(): iterable + { + //