Skip to content

Commit

Permalink
Merge pull request #108 from spatie/feature/88-opening-hours-across-m…
Browse files Browse the repository at this point in the history
…idnight

Feature/88 opening hours across midnight
  • Loading branch information
kylekatarnls authored May 7, 2019
2 parents b6b141a + 3e1978d commit e31ec7f
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 12 deletions.
6 changes: 6 additions & 0 deletions .styleci.yml
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
preset: laravel

disabled:
- short_list_syntax

enabled:
- long_list_syntax
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ $openingHours->forDate(new DateTime('2016-12-25'));
$openingHours->exceptions();
```

On construction you can set a flag for overflowing times across days. For example, for a night club opens until 3am on Friday and Saturday:

```php
$openingHours = \Spatie\OpeningHours\OpeningHours::create([
'overflow' => true,
'friday' => ['20:00-03:00'],
'saturday' => ['20:00-03:00'],
], null);
```

This allows the API to further at yesterdays data to check if the opening hours are open from yesterdays time range.

You can add data in definitions then retrieve them:

```php
Expand Down Expand Up @@ -190,7 +202,7 @@ The package should only be used through the `OpeningHours` class. There are also

### `Spatie\OpeningHours\OpeningHours`

#### `OpeningHours::create(array $data): Spatie\OpeningHours\OpeningHours`
#### `OpeningHours::create(array $data, $timezone = null): Spatie\OpeningHours\OpeningHours`

Static factory method to fill the set of opening hours.

Expand All @@ -203,7 +215,7 @@ $openingHours = OpeningHours::create([

#### `OpeningHours::mergeOverlappingRanges(array $schedule) : array`

For safety sake, creating `OpeningHours` object with overlapping ranges will throw an exception. But you can explicitly merge them.
For safety sake, creating `OpeningHours` object with overlapping ranges will throw an exception unless you pass explicitly `'overflow' => true,` in the opening hours array definition. You can also explicitly merge them.

``` php
$ranges = [
Expand Down Expand Up @@ -333,6 +345,14 @@ Returns next close DateTime from the given DateTime
$openingHours->nextClose(new DateTime('2016-12-24 11:00:00'));
```

#### `asStructuredData() : array`

Returns a (OpeningHoursSpecification)[https://schema.org/openingHoursSpecification] as an array.

```php
$openingHours->asStructuredData();
```

### `Spatie\OpeningHours\OpeningHoursForDay`

This class is meant as read-only. It implements `ArrayAccess`, `Countable` and `IteratorAggregate` so you can process the list of `TimeRange`s in an array-like way.
Expand Down
63 changes: 53 additions & 10 deletions src/OpeningHours.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,33 @@ class OpeningHours
/** @var DateTimeZone|null */
protected $timezone = null;

/** @var bool Allow for overflowing time ranges which overflow into the next day */
private $overflow;

public function __construct($timezone = null)
{
$this->timezone = $timezone ? new DateTimeZone($timezone) : null;
if ($timezone instanceof DateTimeZone) {
$this->timezone = $timezone;
} elseif (is_string($timezone)) {
$this->timezone = new DateTimeZone($timezone);
} elseif ($timezone) {
throw new \InvalidArgumentException('Invalid Timezone');
}

$this->openingHours = Day::mapDays(function () {
return new OpeningHoursForDay();
});
}

/**
* @param array $data
* @param string[][] $data
* @param string|DateTimeZone|null $timezone
*
* @return static
*/
public static function create(array $data)
public static function create(array $data, $timezone = null): self
{
return (new static())->fill($data);
return (new static($timezone))->fill($data);
}

/**
Expand Down Expand Up @@ -94,13 +104,14 @@ public static function mergeOverlappingRanges(array $data)
}

/**
* @param array $data
* @param string[][] $data
* @param string|DateTimeZone|null $timezone
*
* @return static
*/
public static function createAndMergeOverlappingRanges(array $data)
public static function createAndMergeOverlappingRanges(array $data, $timezone = null)
{
return static::create(static::mergeOverlappingRanges($data));
return static::create(static::mergeOverlappingRanges($data), $timezone);
}

/**
Expand Down Expand Up @@ -133,7 +144,9 @@ public function getFilters(): array

public function fill(array $data)
{
list($openingHours, $exceptions, $metaData, $filters) = $this->parseOpeningHoursAndExceptions($data);
list($openingHours, $exceptions, $metaData, $filters, $overflow) = $this->parseOpeningHoursAndExceptions($data);

$this->overflow = $overflow;

foreach ($openingHours as $day => $openingHoursForThisDay) {
$this->setOpeningHoursFromStrings($day, $openingHoursForThisDay);
Expand Down Expand Up @@ -213,6 +226,18 @@ public function isOpenAt(DateTimeInterface $dateTime): bool
{
$dateTime = $this->applyTimezone($dateTime);

if ($this->overflow) {
$yesterdayDateTime = $dateTime;
if (! ($yesterdayDateTime instanceof DateTimeImmutable)) {
$yesterdayDateTime = clone $yesterdayDateTime;
}
$dateTimeMinus1Day = $yesterdayDateTime->sub(new \DateInterval('P1D'));
$openingHoursForDayBefore = $this->forDate($dateTimeMinus1Day);
if ($openingHoursForDayBefore->isOpenAt(Time::fromDateTime($dateTimeMinus1Day))) {
return true;
}
}

$openingHoursForDay = $this->forDate($dateTime);

return $openingHoursForDay->isOpenAt(Time::fromDateTime($dateTime));
Expand Down Expand Up @@ -272,8 +297,23 @@ public function nextClose(DateTimeInterface $dateTime): DateTimeInterface
$dateTime = clone $dateTime;
}

$nextClose = null;
if ($this->overflow) {
$yesterday = $dateTime;
if (! ($dateTime instanceof DateTimeImmutable)) {
$yesterday = clone $dateTime;
}
$dateTimeMinus1Day = $yesterday->sub(new \DateInterval('P1D'));
$openingHoursForDayBefore = $this->forDate($dateTimeMinus1Day);
if ($openingHoursForDayBefore->isOpenAt(Time::fromDateTime($dateTimeMinus1Day))) {
$nextClose = $openingHoursForDayBefore->nextClose(Time::fromDateTime($dateTime));
}
}

$openingHoursForDay = $this->forDate($dateTime);
$nextClose = $openingHoursForDay->nextClose(Time::fromDateTime($dateTime));
if (! $nextClose) {
$nextClose = $openingHoursForDay->nextClose(Time::fromDateTime($dateTime));
}

while ($nextClose === false || $nextClose->hours() >= 24) {
$dateTime = $dateTime
Expand Down Expand Up @@ -328,6 +368,8 @@ protected function parseOpeningHoursAndExceptions(array $data): array
$metaData = Arr::pull($data, 'data', null);
$exceptions = [];
$filters = Arr::pull($data, 'filters', []);
$overflow = (bool) Arr::pull($data, 'overflow', false);

foreach (Arr::pull($data, 'exceptions', []) as $key => $exception) {
if (is_callable($exception)) {
$filters[] = $exception;
Expand All @@ -337,13 +379,14 @@ protected function parseOpeningHoursAndExceptions(array $data): array

$exceptions[$key] = $exception;
}

$openingHours = [];

foreach ($data as $day => $openingHoursData) {
$openingHours[$this->normalizeDayName($day)] = $openingHoursData;
}

return [$openingHours, $exceptions, $metaData, $filters];
return [$openingHours, $exceptions, $metaData, $filters, $overflow];
}

protected function setOpeningHoursFromStrings(string $day, array $openingHours)
Expand Down
74 changes: 74 additions & 0 deletions tests/OpeningHoursOverflowTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

namespace Spatie\OpeningHours\Test;

use DateTime;
use DateTimeImmutable;
use PHPUnit\Framework\TestCase;
use Spatie\OpeningHours\TimeRange;
use Spatie\OpeningHours\OpeningHours;

class OpeningHoursOverflowTest extends TestCase
{
/** @test */
public function it_fills_opening_hours_with_overflow()
{
$openingHours = OpeningHours::create([
'overflow' => true,
'monday' => ['09:00-02:00'],
], null);

$this->assertInstanceOf(TimeRange::class, $openingHours->forDay('monday')[0]);
$this->assertEquals((string) $openingHours->forDay('monday')[0], '09:00-02:00');
}

/** @test */
public function check_open_with_overflow()
{
$openingHours = OpeningHours::create([
'overflow' => true,
'monday' => ['09:00-02:00'],
], null);

$shouldBeOpen = new DateTime('2019-04-23 01:00:00');
$this->assertTrue($openingHours->isOpenAt($shouldBeOpen));
}

/** @test */
public function check_open_with_overflow_immutable()
{
$openingHours = OpeningHours::create([
'overflow' => true,
'monday' => ['09:00-02:00'],
], null);

$shouldBeOpen = new DateTimeImmutable('2019-04-23 01:00:00');
$this->assertTrue($openingHours->isOpenAt($shouldBeOpen));
}

/** @test */
public function next_close_with_overflow()
{
$openingHours = OpeningHours::create([
'overflow' => true,
'monday' => ['09:00-02:00'],
], null);

$shouldBeOpen = new DateTime('2019-04-23 01:00:00');
$this->assertEquals('2019-04-23 02:00:00', $openingHours->nextClose($shouldBeOpen)->format('Y-m-d H:i:s'));
}

/** @test */
public function next_close_with_overflow_immutable()
{
$openingHours = OpeningHours::create([
'overflow' => true,
'monday' => ['09:00-02:00'],
], null);

$shouldBeOpen = new DateTimeImmutable('2019-04-23 01:00:00');
$nextTimeClosed = $openingHours->nextClose($shouldBeOpen)->format('Y-m-d H:i:s');
$this->assertEquals('2019-04-23 02:00:00', $nextTimeClosed);
$this->assertEquals('2019-04-23 01:00:00', $shouldBeOpen->format('Y-m-d H:i:s'));
}
}

0 comments on commit e31ec7f

Please sign in to comment.