Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/spatie/opening-hours into…
Browse files Browse the repository at this point in the history
… 3.x-merge

# Conflicts:
#	.github/workflows/tests.yml
#	src/OpeningHours.php
#	src/Time.php
  • Loading branch information
kylekatarnls committed Nov 5, 2023
2 parents b352fa7 + 2113e28 commit bf40b99
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 28 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php: ['7.4', '8.0', '8.1']
php: ['7.4', '8.0', '8.1', '8.2', '8.3']
setup: ['lowest', 'stable']

name: PHP ${{ matrix.php }} - ${{ matrix.setup || 'stable' }}
Expand Down
79 changes: 68 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ Returns an `OpeningHoursForDay` object for a regular day. A day is lowercase str
$openingHours->forDay('monday');
```

#### `OpeningHours::forDate(DateTime $dateTime): Spatie\OpeningHours\OpeningHoursForDay`
#### `OpeningHours::forDate(DateTimeInterface $dateTime): Spatie\OpeningHours\OpeningHoursForDay`

Returns an `OpeningHoursForDay` object for a specific date. It looks for an exception on that day, and otherwise it returns the opening hours based on the regular schedule.

Expand Down Expand Up @@ -368,15 +368,15 @@ Checks if the business is closed on a day in the regular schedule.
$openingHours->isClosedOn('sunday');
```

#### `OpeningHours::isOpenAt(DateTime $dateTime): bool`
#### `OpeningHours::isOpenAt(DateTimeInterface $dateTime): bool`

Checks if the business is open on a specific day, at a specific time.

```php
$openingHours->isOpenAt(new DateTime('2016-26-09 20:00'));
```

#### `OpeningHours::isClosedAt(DateTime $dateTime): bool`
#### `OpeningHours::isClosedAt(DateTimeInterface $dateTime): bool`

Checks if the business is closed on a specific day, at a specific time.

Expand All @@ -400,33 +400,90 @@ Checks if the business is closed right now.
$openingHours->isClosed();
```

#### `OpeningHours::nextOpen(DateTimeInterface $dateTime) : DateTime`
#### `OpeningHours::nextOpen`

Returns next open DateTime from the given DateTime
```php
OpeningHours::nextOpen(
?DateTimeInterface $dateTime = null,
?DateTimeInterface $searchUntil = null,
?DateTimeInterface $cap = null,
) : DateTimeInterface`
```

Returns next open `DateTime` from the given `DateTime` (`$dateTime` or from now if this parameter is null or omitted).

If a `DateTimeImmutable` object is passed, a `DateTimeImmutable` object is returned.

Set `$searchUntil` to a date to throw an exception if no open time can be found before this moment.

Set `$cap` to a date so if no open time can be found before this moment, `$cap` is returned.

```php
$openingHours->nextOpen(new DateTime('2016-12-24 11:00:00'));
```
`

#### `OpeningHours::nextClose`

```php
OpeningHours::nextClose(
?DateTimeInterface $dateTime = null,
?DateTimeInterface $searchUntil = null,
?DateTimeInterface $cap = null,
) : DateTimeInterface`
```

Returns next close `DateTime` from the given `DateTime` (`$dateTime` or from now if this parameter is null or omitted).

If a `DateTimeImmutable` object is passed, a `DateTimeImmutable` object is returned.

#### `OpeningHours::nextClose(DateTimeInterface $dateTime) : DateTime`
Set `$searchUntil` to a date to throw an exception if no closed time can be found before this moment.

Returns next close DateTime from the given DateTime
Set `$cap` to a date so if no closed time can be found before this moment, `$cap` is returned.

```php
$openingHours->nextClose(new DateTime('2016-12-24 11:00:00'));
```

#### `OpeningHours::previousOpen(DateTimeInterface $dateTime) : DateTime`
#### `OpeningHours::previousOpen`

Returns previous open DateTime from the given DateTime
```php
OpeningHours::previousOpen(
?DateTimeInterface $dateTime = null,
?DateTimeInterface $searchUntil = null,
?DateTimeInterface $cap = null,
) : DateTimeInterface`
```

Returns previous open `DateTime` from the given `DateTime` (`$dateTime` or from now if this parameter is null or omitted).

If a `DateTimeImmutable` object is passed, a `DateTimeImmutable` object is returned.

Set `$searchUntil` to a date to throw an exception if no open time can be found after this moment.

Set `$cap` to a date so if no open time can be found after this moment, `$cap` is returned.

```php
$openingHours->previousOpen(new DateTime('2016-12-24 11:00:00'));
```

#### `OpeningHours::previousClose(DateTimeInterface $dateTime) : DateTime`
#### `OpeningHours::previousClose`

```php
OpeningHours::previousClose(
?DateTimeInterface $dateTime = null,
?DateTimeInterface $searchUntil = null,
?DateTimeInterface $cap = null,
) : DateTimeInterface`
```

Returns previous close `DateTime` from the given `DateTime` (`$dateTime` or from now if this parameter is null or omitted).

If a `DateTimeImmutable` object is passed, a `DateTimeImmutable` object is returned.

Set `$searchUntil` to a date to throw an exception if no closed time can be found after this moment.

Returns previous close DateTime from the given DateTime
Set `$cap` to a date so if no closed time can be found after this moment, `$cap` is returned.

```php
$openingHours->nextClose(new DateTime('2016-12-24 11:00:00'));
Expand Down
13 changes: 13 additions & 0 deletions src/Exceptions/SearchLimitReached.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Spatie\OpeningHours\Exceptions;

use DateTimeInterface;

class SearchLimitReached extends Exception
{
public static function forDate(DateTimeInterface $dateTime): self
{
return new self('Search reached the limit: '.$dateTime->format('Y-m-d H:i:s.u e'));
}
}
5 changes: 3 additions & 2 deletions src/Helpers/DiffTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ private function diffInSeconds(string $stateCheckMethod, string $nextDateMethod,

while ($date < $endDate) {
if ($this->$stateCheckMethod($date)) {
$date = $this->$skipDateMethod($date);
$date = $this->$skipDateMethod($date, null, $endDate);

continue;
}

$nextDate = min($endDate, $this->$nextDateMethod($date));
$nextDate = min($endDate, $this->$nextDateMethod($date, null, $endDate));
$time += floatval($nextDate->format('U.u')) - floatval($date->format('U.u'));
$date = $nextDate;
}
Expand Down
66 changes: 57 additions & 9 deletions src/OpeningHours.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Spatie\OpeningHours\Exceptions\InvalidDayName;
use Spatie\OpeningHours\Exceptions\InvalidTimezone;
use Spatie\OpeningHours\Exceptions\MaximumLimitExceeded;
use Spatie\OpeningHours\Exceptions\SearchLimitReached;
use Spatie\OpeningHours\Helpers\Arr;
use Spatie\OpeningHours\Helpers\DataTrait;
use Spatie\OpeningHours\Helpers\DateTimeCopier;
Expand All @@ -24,7 +25,7 @@ class OpeningHours

use DataTrait, DateTimeCopier, DiffTrait;

/** @var \Spatie\OpeningHours\Day[] */
/** @var \Spatie\OpeningHours\OpeningHoursForDay[] */
protected array $openingHours = [];

/** @var \Spatie\OpeningHours\OpeningHoursForDay[] */
Expand Down Expand Up @@ -476,8 +477,11 @@ public function currentOpenRangeEnd(DateTimeInterface $dateTime): ?DateTimeInter
);
}

public function nextOpen(?DateTimeInterface $dateTime = null): DateTimeInterface
{
public function nextOpen(
?DateTimeInterface $dateTime = null,
?DateTimeInterface $searchUntil = null,
?DateTimeInterface $cap = null
): DateTimeInterface {
$outputTimezone = $this->getOutputTimezone($dateTime);
$dateTime = $this->applyTimezone($dateTime ?? new $this->dateTimeClass());
$dateTime = $this->copyDateTime($dateTime);
Expand All @@ -501,6 +505,14 @@ public function nextOpen(?DateTimeInterface $dateTime = null): DateTimeInterface
return $this->getDateWithTimezone($dateTime, $outputTimezone);
}

if ($cap && $dateTime > $cap) {
return $cap;
}

if ($searchUntil && $dateTime > $searchUntil) {
throw SearchLimitReached::forDate($searchUntil);
}

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

$nextOpen = $openingHoursForDay->nextOpen(PreciseTime::fromDateTime($dateTime));
Expand All @@ -523,21 +535,27 @@ public function nextOpen(?DateTimeInterface $dateTime = null): DateTimeInterface
);
}

public function nextClose(?DateTimeInterface $dateTime = null): DateTimeInterface
{
public function nextClose(
?DateTimeInterface $dateTime = null,
?DateTimeInterface $searchUntil = null,
?DateTimeInterface $cap = null
): DateTimeInterface {
$outputTimezone = $this->getOutputTimezone($dateTime);
$dateTime = $this->applyTimezone($dateTime ?? new $this->dateTimeClass());
$dateTime = $this->copyDateTime($dateTime);
$nextClose = null;

if ($this->overflow) {
$dateTimeMinus1Day = $this->yesterday($dateTime);
$openingHoursForDayBefore = $this->forDate($dateTimeMinus1Day);

if ($openingHoursForDayBefore->isOpenAtNight(PreciseTime::fromDateTime($dateTimeMinus1Day))) {
$nextClose = $openingHoursForDayBefore->nextClose(PreciseTime::fromDateTime($dateTime));
}
}

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

if (! $nextClose) {
$nextClose = $openingHoursForDay->nextClose(PreciseTime::fromDateTime($dateTime));

Expand All @@ -564,6 +582,14 @@ public function nextClose(?DateTimeInterface $dateTime = null): DateTimeInterfac
return $this->getDateWithTimezone($dateTime, $outputTimezone);
}

if ($cap && $dateTime > $cap) {
return $cap;
}

if ($searchUntil && $dateTime > $searchUntil) {
throw SearchLimitReached::forDate($searchUntil);
}

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

$nextClose = $openingHoursForDay->nextClose(PreciseTime::fromDateTime($dateTime));
Expand All @@ -577,8 +603,11 @@ public function nextClose(?DateTimeInterface $dateTime = null): DateTimeInterfac
);
}

public function previousOpen(DateTimeInterface $dateTime): DateTimeInterface
{
public function previousOpen(
DateTimeInterface $dateTime,
DateTimeInterface $searchUntil = null,
DateTimeInterface $cap = null
): DateTimeInterface {
$outputTimezone = $this->getOutputTimezone($dateTime);
$dateTime = $this->copyDateTime($this->applyTimezone($dateTime));
$openingHoursForDay = $this->forDate($dateTime);
Expand All @@ -602,6 +631,14 @@ public function previousOpen(DateTimeInterface $dateTime): DateTimeInterface
return $this->getDateWithTimezone($midnight, $outputTimezone);
}

if ($cap && $dateTime < $cap) {
return $cap;
}

if ($searchUntil && $dateTime < $searchUntil) {
throw SearchLimitReached::forDate($searchUntil);
}

$previousOpen = $openingHoursForDay->previousOpen(PreciseTime::fromDateTime($dateTime));
}

Expand All @@ -613,8 +650,11 @@ public function previousOpen(DateTimeInterface $dateTime): DateTimeInterface
);
}

public function previousClose(DateTimeInterface $dateTime): DateTimeInterface
{
public function previousClose(
DateTimeInterface $dateTime,
DateTimeInterface $searchUntil = null,
DateTimeInterface $cap = null
): DateTimeInterface {
$outputTimezone = $this->getOutputTimezone($dateTime);
$dateTime = $this->copyDateTime($this->applyTimezone($dateTime));
$previousClose = null;
Expand Down Expand Up @@ -649,6 +689,14 @@ public function previousClose(DateTimeInterface $dateTime): DateTimeInterface
return $this->getDateWithTimezone($midnight, $outputTimezone);
}

if ($cap && $dateTime < $cap) {
return $cap;
}

if ($searchUntil && $dateTime < $searchUntil) {
throw SearchLimitReached::forDate($searchUntil);
}

$previousClose = $openingHoursForDay->previousClose(PreciseTime::fromDateTime($dateTime));
}

Expand Down
14 changes: 9 additions & 5 deletions src/Time.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,18 +95,15 @@ public function toDateTime(DateTimeInterface $date = null): DateTimeInterface
public function format(string $format = self::TIME_FORMAT, $timezone = null): string
{
$date = $this->date ?: ($timezone
? new DateTime('1970-01-01 00:00:00', $timezone instanceof DateTimeZone
? new DateTimeImmutable('1970-01-01 00:00:00', $timezone instanceof DateTimeZone
? $timezone
: new DateTimeZone($timezone)
)
: null
);

if ($this->hours === 24 && $this->minutes === 0 && substr($format, 0, 3) === self::TIME_FORMAT) {
return '24:00'.(strlen($format) > 3
? ($date ?? new DateTimeImmutable('1970-01-01 00:00:00'))->format(substr($format, 3))
: ''
);
return '24:00'.$this->formatSecond($format, $date);
}

return $this->toDateTime($date)->format($format);
Expand All @@ -116,4 +113,11 @@ public function __toString(): string
{
return $this->format();
}

private function formatSecond(string $format, ?DateTimeImmutable $date = null): string
{
return strlen($format) > 3
? ($date ?? new DateTimeImmutable('1970-01-01 00:00:00'))->format(substr($format, 3))
: '';
}
}
31 changes: 31 additions & 0 deletions tests/OpeningHoursTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,7 @@ public function it_can_set_the_timezone_on_the_openings_hours_object()

/**
* @test
*
* @dataProvider timezones
*/
public function it_can_handle_timezone_for_date_string($timezone)
Expand Down Expand Up @@ -1451,4 +1452,34 @@ public function testHoursRangeAreKept()
$this->assertNull($monday[0]->getData());
$this->assertSame('09:00-12:00,13:00-18:00', (string) $monday);
}

public function testSearchWithEmptyHours()
{
$openingHours = OpeningHours::create([
'monday' => [],
'tuesday' => [],
'wednesday' => [],
'thursday' => [],
'friday' => [],
'saturday' => [],
'sunday' => [],
'exceptions' => [
'2016-11-11' => ['09:00-12:00'],
],
]);

$minutes = $openingHours->diffInClosedMinutes(
new DateTimeImmutable('2023-05-17 12:00'),
new DateTimeImmutable('2023-05-23 12:00')
);

$this->assertSame(6.0, $minutes / 60 / 24);

$minutes = $openingHours->diffInOpenMinutes(
new DateTimeImmutable('2023-05-17 12:00'),
new DateTimeImmutable('2023-05-23 12:00')
);

$this->assertSame(0.0, $minutes);
}
}

0 comments on commit bf40b99

Please sign in to comment.