From 262ff9a1d3f2fa158fbb9be760fbd2ccb9e3bdbc Mon Sep 17 00:00:00 2001 From: KyleKatarn Date: Tue, 30 Oct 2018 11:25:50 +0100 Subject: [PATCH] Fix #73 and support immutable dates --- .gitignore | 1 + composer.json | 2 +- src/OpeningHours.php | 25 +++++--- src/OpeningHoursForDay.php | 8 +++ src/Time.php | 9 ++- tests/OpeningHoursFillTest.php | 2 + tests/OpeningHoursTest.php | 106 +++++++++++++++++++++++++++++++-- tests/TimeTest.php | 10 ++++ 8 files changed, 148 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index f02a2f8..85d90cf 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ build composer.lock docs vendor +.idea diff --git a/composer.json b/composer.json index 0756259..75a3e09 100644 --- a/composer.json +++ b/composer.json @@ -39,5 +39,5 @@ }, "config": { "sort-packages": true - } + } } diff --git a/src/OpeningHours.php b/src/OpeningHours.php index 22df802..4c6392b 100644 --- a/src/OpeningHours.php +++ b/src/OpeningHours.php @@ -4,6 +4,7 @@ use DateTime; use DateTimeZone; +use DateTimeImmutable; use DateTimeInterface; use Spatie\OpeningHours\Helpers\Arr; use Spatie\OpeningHours\Exceptions\Exception; @@ -150,13 +151,17 @@ public function isClosed(): bool return $this->isClosedAt(new DateTime()); } - public function nextOpen(DateTimeInterface $dateTime): DateTime + public function nextOpen(DateTimeInterface $dateTime): DateTimeInterface { + if (! ($dateTime instanceof DateTimeImmutable)) { + $dateTime = clone $dateTime; + } + $openingHoursForDay = $this->forDate($dateTime); $nextOpen = $openingHoursForDay->nextOpen(Time::fromDateTime($dateTime)); - while ($nextOpen == false) { - $dateTime + while ($nextOpen === false) { + $dateTime = $dateTime ->modify('+1 day') ->setTime(0, 0, 0); @@ -166,18 +171,22 @@ public function nextOpen(DateTimeInterface $dateTime): DateTime } $nextDateTime = $nextOpen->toDateTime(); - $dateTime->setTime($nextDateTime->format('G'), $nextDateTime->format('i'), 0); + $dateTime = $dateTime->setTime($nextDateTime->format('G'), $nextDateTime->format('i'), 0); return $dateTime; } - public function nextClose(DateTimeInterface $dateTime): DateTime + public function nextClose(DateTimeInterface $dateTime): DateTimeInterface { + if (! ($dateTime instanceof DateTimeImmutable)) { + $dateTime = clone $dateTime; + } + $openingHoursForDay = $this->forDate($dateTime); $nextClose = $openingHoursForDay->nextClose(Time::fromDateTime($dateTime)); - while ($nextClose == false) { - $dateTime + while ($nextClose === false) { + $dateTime = $dateTime ->modify('+1 day') ->setTime(0, 0, 0); @@ -187,7 +196,7 @@ public function nextClose(DateTimeInterface $dateTime): DateTime } $nextDateTime = $nextClose->toDateTime(); - $dateTime->setTime($nextDateTime->format('G'), $nextDateTime->format('i'), 0); + $dateTime = $dateTime->setTime($nextDateTime->format('G'), $nextDateTime->format('i'), 0); return $dateTime; } diff --git a/src/OpeningHoursForDay.php b/src/OpeningHoursForDay.php index b502780..c958c3f 100644 --- a/src/OpeningHoursForDay.php +++ b/src/OpeningHoursForDay.php @@ -44,10 +44,14 @@ public function nextOpen(Time $time) { foreach ($this->openingHours as $timeRange) { if ($nextOpen = $this->findNextOpenInWorkingHours($time, $timeRange)) { + reset($timeRange); + return $nextOpen; } if ($nextOpen = $this->findNextOpenInFreeTime($time, $timeRange)) { + reset($timeRange); + return $nextOpen; } } @@ -59,10 +63,14 @@ public function nextClose(Time $time) { foreach ($this->openingHours as $timeRange) { if ($nextClose = $this->findNextCloseInWorkingHours($time, $timeRange)) { + reset($timeRange); + return $nextClose; } if ($nextClose = $this->findNextCloseInFreeTime($time, $timeRange)) { + reset($timeRange); + return $nextClose; } } diff --git a/src/Time.php b/src/Time.php index 5bf4397..46124d6 100644 --- a/src/Time.php +++ b/src/Time.php @@ -3,6 +3,7 @@ namespace Spatie\OpeningHours; use DateTime; +use DateTimeImmutable; use DateTimeInterface; use Spatie\OpeningHours\Exceptions\InvalidTimeString; @@ -83,9 +84,13 @@ public function diff(self $time): \DateInterval return $this->toDateTime()->diff($time->toDateTime()); } - public function toDateTime(DateTime $date = null): DateTime + public function toDateTime(DateTimeInterface $date = null): DateTimeInterface { - $date = $date ? (clone $date) : new DateTime('1970-01-01 00:00:00'); + if (! $date) { + $date = new DateTime('1970-01-01 00:00:00'); + } elseif (! ($date instanceof DateTimeImmutable)) { + $date = clone $date; + } return $date->setTime($this->hours, $this->minutes); } diff --git a/tests/OpeningHoursFillTest.php b/tests/OpeningHoursFillTest.php index f9d0917..4ff3887 100644 --- a/tests/OpeningHoursFillTest.php +++ b/tests/OpeningHoursFillTest.php @@ -3,6 +3,7 @@ namespace Spatie\OpeningHours\Test; use DateTime; +use DateTimeImmutable; use Spatie\OpeningHours\Day; use PHPUnit\Framework\TestCase; use Spatie\OpeningHours\TimeRange; @@ -44,6 +45,7 @@ public function it_fills_opening_hours() $this->assertEquals((string) $openingHours->forDay('friday')[0], '09:00-20:00'); $this->assertCount(0, $openingHours->forDate(new DateTime('2016-09-26 11:00:00'))); + $this->assertCount(0, $openingHours->forDate(new DateTimeImmutable('2016-09-26 11:00:00'))); } /** @test */ diff --git a/tests/OpeningHoursTest.php b/tests/OpeningHoursTest.php index 80c9c65..e9dabab 100644 --- a/tests/OpeningHoursTest.php +++ b/tests/OpeningHoursTest.php @@ -4,6 +4,7 @@ use DateTime; use DateTimeZone; +use DateTimeImmutable; use PHPUnit\Framework\TestCase; use Spatie\OpeningHours\OpeningHours; @@ -131,6 +132,14 @@ public function it_can_return_the_opening_hours_for_a_specific_date() $this->assertEquals('09:00-18:00', $openingHoursForMonday1909[0]); $this->assertCount(0, $openingHoursForMonday2609); + + $openingHoursForMonday1909 = $openingHours->forDate(new DateTimeImmutable('2016-09-19 00:00:00')); + $openingHoursForMonday2609 = $openingHours->forDate(new DateTimeImmutable('2016-09-26 00:00:00')); + + $this->assertCount(1, $openingHoursForMonday1909); + $this->assertEquals('09:00-18:00', $openingHoursForMonday1909[0]); + + $this->assertCount(0, $openingHoursForMonday2609); } /** @test */ @@ -144,6 +153,10 @@ public function it_can_determine_that_its_open_at_a_certain_date_and_time() $this->assertTrue($openingHours->isOpenAt($shouldBeOpen)); $this->assertFalse($openingHours->isClosedAt($shouldBeOpen)); + $shouldBeOpen = new DateTimeImmutable('2016-09-26 11:00:00'); + $this->assertTrue($openingHours->isOpenAt($shouldBeOpen)); + $this->assertFalse($openingHours->isClosedAt($shouldBeOpen)); + $shouldBeOpenAlternativeDate = date_create_immutable('2016-09-26 11:12:13.123456'); $this->assertTrue($openingHours->isOpenAt($shouldBeOpenAlternativeDate)); $this->assertFalse($openingHours->isClosedAt($shouldBeOpenAlternativeDate)); @@ -152,9 +165,17 @@ public function it_can_determine_that_its_open_at_a_certain_date_and_time() $this->assertFalse($openingHours->isOpenAt($shouldBeClosedBecauseOfTime)); $this->assertTrue($openingHours->isClosedAt($shouldBeClosedBecauseOfTime)); + $shouldBeClosedBecauseOfTime = new DateTimeImmutable('2016-09-26 20:00:00'); + $this->assertFalse($openingHours->isOpenAt($shouldBeClosedBecauseOfTime)); + $this->assertTrue($openingHours->isClosedAt($shouldBeClosedBecauseOfTime)); + $shouldBeClosedBecauseOfDay = new DateTime('2016-09-27 11:00:00'); $this->assertFalse($openingHours->isOpenAt($shouldBeClosedBecauseOfDay)); $this->assertTrue($openingHours->isClosedAt($shouldBeClosedBecauseOfDay)); + + $shouldBeClosedBecauseOfDay = new DateTimeImmutable('2016-09-27 11:00:00'); + $this->assertFalse($openingHours->isOpenAt($shouldBeClosedBecauseOfDay)); + $this->assertTrue($openingHours->isClosedAt($shouldBeClosedBecauseOfDay)); } /** @test */ @@ -170,6 +191,10 @@ public function it_can_determine_that_its_open_at_a_certain_date_and_time_on_an_ $shouldBeClosed = new DateTime('2016-09-26 11:00:00'); $this->assertFalse($openingHours->isOpenAt($shouldBeClosed)); $this->assertTrue($openingHours->isClosedAt($shouldBeClosed)); + + $shouldBeClosed = new DateTimeImmutable('2016-09-26 11:00:00'); + $this->assertFalse($openingHours->isOpenAt($shouldBeClosed)); + $this->assertTrue($openingHours->isClosedAt($shouldBeClosed)); } /** @test */ @@ -188,13 +213,25 @@ public function it_can_determine_that_its_open_at_a_certain_date_and_time_on_an_ $this->assertFalse($openingHours->isOpenAt($closedOnNewYearDay)); $this->assertTrue($openingHours->isClosedAt($closedOnNewYearDay)); + $closedOnNewYearDay = new DateTimeImmutable('2017-01-01 11:00:00'); + $this->assertFalse($openingHours->isOpenAt($closedOnNewYearDay)); + $this->assertTrue($openingHours->isClosedAt($closedOnNewYearDay)); + $closedOnSecondChristmasDay = new DateTime('2025-12-16 12:00:00'); $this->assertFalse($openingHours->isOpenAt($closedOnSecondChristmasDay)); $this->assertTrue($openingHours->isClosedAt($closedOnSecondChristmasDay)); + $closedOnSecondChristmasDay = new DateTimeImmutable('2025-12-16 12:00:00'); + $this->assertFalse($openingHours->isOpenAt($closedOnSecondChristmasDay)); + $this->assertTrue($openingHours->isClosedAt($closedOnSecondChristmasDay)); + $openOnChristmasMorning = new DateTime('2025-12-25 10:00:00'); $this->assertTrue($openingHours->isOpenAt($openOnChristmasMorning)); $this->assertFalse($openingHours->isClosedAt($openOnChristmasMorning)); + + $openOnChristmasMorning = new DateTimeImmutable('2025-12-25 10:00:00'); + $this->assertTrue($openingHours->isOpenAt($openOnChristmasMorning)); + $this->assertFalse($openingHours->isClosedAt($openOnChristmasMorning)); } /** @test */ @@ -213,9 +250,17 @@ public function it_can_prioritize_exceptions_by_giving_full_dates_priority() $this->assertTrue($openingHours->isOpenAt($openOnNewYearDay2018)); $this->assertFalse($openingHours->isClosedAt($openOnNewYearDay2018)); + $openOnNewYearDay2018 = new DateTimeImmutable('2018-01-01 11:00:00'); + $this->assertTrue($openingHours->isOpenAt($openOnNewYearDay2018)); + $this->assertFalse($openingHours->isClosedAt($openOnNewYearDay2018)); + $closedOnNewYearDay2019 = new DateTime('2019-01-01 11:00:00'); $this->assertFalse($openingHours->isOpenAt($closedOnNewYearDay2019)); $this->assertTrue($openingHours->isClosedAt($closedOnNewYearDay2019)); + + $closedOnNewYearDay2019 = new DateTimeImmutable('2019-01-01 11:00:00'); + $this->assertFalse($openingHours->isOpenAt($closedOnNewYearDay2019)); + $this->assertTrue($openingHours->isClosedAt($closedOnNewYearDay2019)); } /** @test */ @@ -229,6 +274,11 @@ public function it_can_determine_next_open_hours_from_non_working_date_time() $this->assertInstanceOf(DateTime::class, $nextTimeOpen); $this->assertEquals('2016-09-26 13:00:00', $nextTimeOpen->format('Y-m-d H:i:s')); + + $nextTimeOpen = $openingHours->nextOpen(new DateTimeImmutable('2016-09-26 12:00:00')); + + $this->assertInstanceOf(DateTimeImmutable::class, $nextTimeOpen); + $this->assertEquals('2016-09-26 13:00:00', $nextTimeOpen->format('Y-m-d H:i:s')); } /** @test */ @@ -242,6 +292,11 @@ public function it_can_determine_next_close_hours_from_non_working_date_time() $this->assertInstanceOf(DateTime::class, $nextTimeOpen); $this->assertEquals('2016-09-26 19:00:00', $nextTimeOpen->format('Y-m-d H:i:s')); + + $nextTimeOpen = $openingHours->nextClose(new DateTimeImmutable('2016-09-26 12:00:00')); + + $this->assertInstanceOf(DateTimeImmutable::class, $nextTimeOpen); + $this->assertEquals('2016-09-26 19:00:00', $nextTimeOpen->format('Y-m-d H:i:s')); } /** @test */ @@ -256,9 +311,16 @@ public function it_can_determine_next_open_hours_from_working_date_time() $this->assertInstanceOf(DateTime::class, $nextTimeOpen); $this->assertEquals('2016-09-27 10:00:00', $nextTimeOpen->format('Y-m-d H:i:s')); + + $nextTimeOpen = $openingHours->nextOpen(new DateTimeImmutable('2016-09-26 16:00:00')); + + $this->assertInstanceOf(DateTimeImmutable::class, $nextTimeOpen); + $this->assertEquals('2016-09-27 10:00:00', $nextTimeOpen->format('Y-m-d H:i:s')); } - /** @test */ + /** @test + * @group i + */ public function it_can_determine_next_close_hours_from_working_date_time() { $openingHours = OpeningHours::create([ @@ -266,10 +328,15 @@ public function it_can_determine_next_close_hours_from_working_date_time() 'tuesday' => ['10:00-11:00', '14:00-19:00'], ]); - $nextTimeOpen = $openingHours->nextClose(new DateTime('2016-09-26 16:00:00')); + $nextTimeClose = $openingHours->nextClose(new DateTime('2016-09-26 16:00:00')); - $this->assertInstanceOf(DateTime::class, $nextTimeOpen); - $this->assertEquals('2016-09-26 19:00:00', $nextTimeOpen->format('Y-m-d H:i:s')); + $this->assertInstanceOf(DateTime::class, $nextTimeClose); + $this->assertEquals('2016-09-26 19:00:00', $nextTimeClose->format('Y-m-d H:i:s')); + + $nextTimeClose = $openingHours->nextClose(new DateTimeImmutable('2016-09-26 16:00:00')); + + $this->assertInstanceOf(DateTimeImmutable::class, $nextTimeClose); + $this->assertEquals('2016-09-26 19:00:00', $nextTimeClose->format('Y-m-d H:i:s')); } /** @test */ @@ -287,6 +354,11 @@ public function it_can_determine_next_open_hours_from_early_morning() $this->assertInstanceOf(DateTime::class, $nextTimeOpen); $this->assertEquals('2016-09-27 10:00:00', $nextTimeOpen->format('Y-m-d H:i:s')); + + $nextTimeOpen = $openingHours->nextOpen(new DateTimeImmutable('2016-09-26 04:00:00')); + + $this->assertInstanceOf(DateTimeImmutable::class, $nextTimeOpen); + $this->assertEquals('2016-09-27 10:00:00', $nextTimeOpen->format('Y-m-d H:i:s')); } /** @test */ @@ -304,6 +376,11 @@ public function it_can_determine_next_close_hours_from_early_morning() $this->assertInstanceOf(DateTime::class, $nextTimeOpen); $this->assertEquals('2016-09-27 11:00:00', $nextTimeOpen->format('Y-m-d H:i:s')); + + $nextTimeOpen = $openingHours->nextClose(new DateTimeImmutable('2016-09-26 04:00:00')); + + $this->assertInstanceOf(DateTimeImmutable::class, $nextTimeOpen); + $this->assertEquals('2016-09-27 11:00:00', $nextTimeOpen->format('Y-m-d H:i:s')); } /** @test */ @@ -332,9 +409,27 @@ public function it_can_set_the_timezone_on_the_openings_hours_object() $this->assertFalse($openingHours->isOpenAt(new DateTime('2016-11-14 15:59', new DateTimeZone('America/Denver')))); $this->assertTrue($openingHours->isOpenAt(new DateTime('2016-10-10 09:59', new DateTimeZone('America/Denver')))); + $this->assertTrue($openingHours->isOpenAt(new DateTimeImmutable('2016-10-10 10:00'))); + $this->assertTrue($openingHours->isOpenAt(new DateTimeImmutable('2016-10-10 15:59'))); + $this->assertTrue($openingHours->isOpenAt(new DateTimeImmutable('2016-10-10 08:00'))); + $this->assertFalse($openingHours->isOpenAt(new DateTimeImmutable('2016-10-10 06:00'))); + + $this->assertFalse($openingHours->isOpenAt(new DateTimeImmutable('2016-10-10 06:00', new DateTimeZone('Europe/Amsterdam')))); + $this->assertTrue($openingHours->isOpenAt(new DateTimeImmutable('2016-10-10 09:00', new DateTimeZone('Europe/Amsterdam')))); + $this->assertTrue($openingHours->isOpenAt(new DateTimeImmutable('2016-10-10 17:59', new DateTimeZone('Europe/Amsterdam')))); + + $this->assertFalse($openingHours->isOpenAt(new DateTime('2016-11-14 17:59', new DateTimeZone('Europe/Amsterdam')))); + $this->assertTrue($openingHours->isOpenAt(new DateTime('2016-11-14 12:59', new DateTimeZone('Europe/Amsterdam')))); + + $this->assertFalse($openingHours->isOpenAt(new DateTime('2016-11-14 15:59', new DateTimeZone('America/Denver')))); + $this->assertTrue($openingHours->isOpenAt(new DateTime('2016-10-10 09:59', new DateTimeZone('America/Denver')))); + date_default_timezone_set('America/Denver'); $this->assertTrue($openingHours->isOpenAt(new DateTime('2016-10-10 09:59'))); $this->assertFalse($openingHours->isOpenAt(new DateTime('2016-10-10 10:00'))); + + $this->assertTrue($openingHours->isOpenAt(new DateTimeImmutable('2016-10-10 09:59'))); + $this->assertFalse($openingHours->isOpenAt(new DateTimeImmutable('2016-10-10 10:00'))); } /** @test */ @@ -419,6 +514,9 @@ public function it_works_when_starting_at_midnight() $nextTimeOpen = $openingHours->nextOpen(new DateTime()); $this->assertInstanceOf(DateTime::class, $nextTimeOpen); + + $nextTimeOpen = $openingHours->nextOpen(new DateTimeImmutable()); + $this->assertInstanceOf(DateTimeImmutable::class, $nextTimeOpen); } /** @test */ diff --git a/tests/TimeTest.php b/tests/TimeTest.php index bc080ca..9b3d599 100644 --- a/tests/TimeTest.php +++ b/tests/TimeTest.php @@ -3,6 +3,7 @@ namespace Spatie\OpeningHours\Test; use DateTime; +use DateTimeImmutable; use Spatie\OpeningHours\Time; use PHPUnit\Framework\TestCase; use Spatie\OpeningHours\Exceptions\InvalidTimeString; @@ -29,6 +30,10 @@ public function it_can_be_created_from_a_date_time_instance() $dateTime = new DateTime('2016-09-27 16:00:00'); $this->assertEquals('16:00', (string) Time::fromDateTime($dateTime)); + + $dateTime = new DateTimeImmutable('2016-09-27 16:00:00'); + + $this->assertEquals('16:00', (string) Time::fromDateTime($dateTime)); } /** @test */ @@ -111,5 +116,10 @@ public function it_should_not_mutate_passed_datetime() $time = Time::fromString('15:00'); $this->assertEquals('2016-09-27 15:00:00', $time->toDateTime($dateTime)->format('Y-m-d H:i:s')); $this->assertEquals('2016-09-27 12:00:00', $dateTime->format('Y-m-d H:i:s')); + + $dateTime = new DateTimeImmutable('2016-09-27 12:00:00'); + $time = Time::fromString('15:00'); + $this->assertEquals('2016-09-27 15:00:00', $time->toDateTime($dateTime)->format('Y-m-d H:i:s')); + $this->assertEquals('2016-09-27 12:00:00', $dateTime->format('Y-m-d H:i:s')); } }