Skip to content

Commit

Permalink
Introduced WorkingHours interface and added ShiftsWorking Hours - add…
Browse files Browse the repository at this point in the history
…ed simple readme (#2)
  • Loading branch information
norberttech authored Jun 11, 2020
1 parent 3ccd3c6 commit e8b51a9
Show file tree
Hide file tree
Showing 13 changed files with 312 additions and 49 deletions.
140 changes: 140 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,143 @@ Time Management Framework for PHP
[Source: Wikipedia](https://en.wikipedia.org/wiki/Aeon)

Define working hours and check Date Time against them, exclude holidays,
define exceptions and custom working days.

Business Hours class takes 3 parameters, described below.

```php
final class BusinessHours
{
/**
* @param BusinessDays $regularBusinessDays - lowest priority when checking open hours, overwrites nothing
* @param BusinessDays $customBusinessDays - highest priority when checking open hours, overwrites business days and non business days
* @param NonBusinessDays $nonBusinessDays - medium priority when checking open hours, overwrites regular business days
*/
public function __construct(BusinessDays $regularBusinessDays, BusinessDays $customBusinessDays, NonBusinessDays $nonBusinessDays)
{
}
}
```

So it's all about the priority of execution, custom business days comes over non-business days,
and non-business days comes over regular business days.

Working Hours can be dined in linear way but also as a collection of shifts (if you take a break in the middle of the day).

* `\Aeon\Calendar\Gregorian\BusinessHours\WorkingHours\LinearWorkingHours();`
* `\Aeon\Calendar\Gregorian\BusinessHours\WorkingHours\ShiftsWorkingHours()`

```php
interface WorkingHours
{
public function openFrom() : Time;

public function openTo() : Time;

public function isOpen(Time $time) : bool;
}
```

### Simple business open from Monday to Friday 6am - 6pm

```php
<?php

use Aeon\Calendar\Gregorian\BusinessHours;
use Aeon\Calendar\Gregorian\BusinessHours\WorkingHours\LinearWorkingHours;
use Aeon\Calendar\Gregorian\Time;
use \Aeon\Calendar\Gregorian\DateTime;

$businessHours = new BusinessHours(
$regularBusinessDays = BusinessHours\BusinessDays::mondayFriday(
new LinearWorkingHours(Time::fromString('6am'), Time::fromString('6pm'))
),
BusinessHours\BusinessDays::none(),
BusinessHours\NonBusinessDays::none()
);

$businessHours->isOpen(DateTime::fromString('2020-01-03 8am')); // true
```

### Monday - Friday 6am - 6pm, Weekends 11am - 6pm

```php
<?php

use Aeon\Calendar\Gregorian\BusinessHours;
use Aeon\Calendar\Gregorian\BusinessHours\WorkingHours\LinearWorkingHours;
use Aeon\Calendar\Gregorian\Time;
use \Aeon\Calendar\Gregorian\DateTime;

$businessHours = new BusinessHours(
$regularBusinessDays = BusinessHours\BusinessDays::wholeWeek(
$weekWorkingHours = new LinearWorkingHours(Time::fromString('6am'), Time::fromString('6pm')),
$weekendWorkingHours = new LinearWorkingHours(Time::fromString('11am'), Time::fromString('6pm'))
),
BusinessHours\BusinessDays::none(),
BusinessHours\NonBusinessDays::none()
);

$businessHours->isOpen(DateTime::fromString('2020-01-03 8am')); // true
```
### Closed during regional holidays in Poland

```php
<?php

use Aeon\Calendar\Gregorian\BusinessHours;
use Aeon\Calendar\Gregorian\BusinessHours\WorkingHours\LinearWorkingHours;
use Aeon\Calendar\Gregorian\Holidays\GoogleCalendar\CountryCodes;
use Aeon\Calendar\Gregorian\Holidays\GoogleCalendarRegionalHolidays;
use Aeon\Calendar\Gregorian\Time;
use \Aeon\Calendar\Gregorian\Day;

$businessHours = new BusinessHours(
$regularBusinessDays = BusinessHours\BusinessDays::wholeWeek(
$weekWorkingHours = new LinearWorkingHours(Time::fromString('6am'), Time::fromString('6pm')),
$weekendWorkingHours = new LinearWorkingHours(Time::fromString('11am'), Time::fromString('6pm'))
),
BusinessHours\BusinessDays::none(),
$nonBusinessDay = new BusinessHours\NonBusinessDays(
new BusinessHours\NonBusinessDay\Holidays(
new GoogleCalendarRegionalHolidays(CountryCodes::PL)
)
)
);

$businessHours->isOpenOn(Day::fromString('2020-06-11')); // false
```

### Closed during regional holidays in Poland but open on January first

```php
<?php

use Aeon\Calendar\Gregorian\BusinessHours;
use Aeon\Calendar\Gregorian\BusinessHours\WorkingHours\LinearWorkingHours;
use Aeon\Calendar\Gregorian\Holidays\GoogleCalendar\CountryCodes;
use Aeon\Calendar\Gregorian\Holidays\GoogleCalendarRegionalHolidays;
use Aeon\Calendar\Gregorian\Time;
use \Aeon\Calendar\Gregorian\Day;

$businessHours = new BusinessHours(
$regularBusinessDays = BusinessHours\BusinessDays::wholeWeek(
$weekWorkingHours = new LinearWorkingHours(Time::fromString('6am'), Time::fromString('6pm')),
$weekendWorkingHours = new LinearWorkingHours(Time::fromString('11am'), Time::fromString('6pm'))
),
$customBusinessDays = new BusinessHours\BusinessDays(
new BusinessHours\BusinssDay\CustomBusinessDay(
Day::fromString('2020-01-01'),
new LinearWorkingHours(Time::fromString('11am'), Time::fromString('3pm'))
)
),
$nonBusinessDay = new BusinessHours\NonBusinessDays(
new BusinessHours\NonBusinessDay\Holidays(
new GoogleCalendarRegionalHolidays(CountryCodes::PL)
)
)
);

$businessHours->isOpenOn(Day::fromString('2020-06-11')); // false
```
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"phpunit"
],
"test:mutation": [
"infection -vvv --test-framework-options='--testsuite=unit' --only-covered --log-verbosity=default --min-covered-msi=100 --threads=2"
"infection -vvv --test-framework-options='--testsuite=unit' --only-covered --log-verbosity=default --min-covered-msi=91 --threads=2"
],
"static:analyze": [
"psalm --output-format=compact",
Expand Down
3 changes: 2 additions & 1 deletion src/Aeon/Calendar/Gregorian/BusinessHours/BusinessDays.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Aeon\Calendar\Gregorian\BusinessHours;

use Aeon\Calendar\Gregorian\BusinessHours\WorkingHours\LinearWorkingHours;
use Aeon\Calendar\Gregorian\DateTime;
use Aeon\Calendar\Gregorian\Day;
use Aeon\Calendar\Gregorian\Exception\BusinessDayException;
Expand Down Expand Up @@ -41,7 +42,7 @@ public static function wholeWeek(WorkingHours $weekWorkingHours, ?WorkingHours $
);
}

public static function mondayFriday(WorkingHours $workingHours) : self
public static function mondayFriday(LinearWorkingHours $workingHours) : self
{
return new self(
new RegularBusinessDay(Day\WeekDay::monday(), $workingHours),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public function is(Day $day) : bool

public function isOpen(DateTime $dateTime) : bool
{
return $this->is($dateTime->day()) && $this->workingHours()->covers($dateTime);
return $this->is($dateTime->day()) && $this->workingHours()->isOpen($dateTime->time());
}

public function workingHours() : WorkingHours
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function is(Day $day) : bool

public function isOpen(DateTime $dateTime) : bool
{
return $this->workingHours->covers($dateTime);
return $this->workingHours->isOpen($dateTime->time());
}

public function workingHours() : WorkingHours
Expand Down
32 changes: 4 additions & 28 deletions src/Aeon/Calendar/Gregorian/BusinessHours/WorkingHours.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,18 @@

declare(strict_types=1);


namespace Aeon\Calendar\Gregorian\BusinessHours;

use Aeon\Calendar\Gregorian\DateTime;
use Aeon\Calendar\Gregorian\Time;
use Webmozart\Assert\Assert;

/**
* @psalm-immutable
*/
final class WorkingHours
interface WorkingHours
{
private Time $startHour;

private Time $endHour;

public function __construct(Time $startHour, Time $endHour)
{
Assert::true($endHour->isGreaterThan($startHour), 'End hour needs to be greater than start hour');
$this->startHour = $startHour;
$this->endHour = $endHour;
}

public function openFrom() : Time
{
return $this->startHour;
}
public function openFrom() : Time;

public function openTo() : Time
{
return $this->endHour;
}
public function openTo() : Time;

public function covers(DateTime $dateTime) : bool
{
return $dateTime->time()->isGreaterThanEq($this->openFrom()) &&
$dateTime->time()->isLessThanEq($this->openTo());
}
public function isOpen(Time $time) : bool;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);


namespace Aeon\Calendar\Gregorian\BusinessHours\WorkingHours;

use Aeon\Calendar\Gregorian\BusinessHours\WorkingHours;
use Aeon\Calendar\Gregorian\Time;
use Webmozart\Assert\Assert;

/**
* @psalm-immutable
*/
final class LinearWorkingHours implements WorkingHours
{
private Time $startHour;

private Time $endHour;

public function __construct(Time $startHour, Time $endHour)
{
Assert::true($endHour->isGreaterThan($startHour), 'End hour needs to be greater than start hour');
$this->startHour = $startHour;
$this->endHour = $endHour;
}

public function openFrom() : Time
{
return $this->startHour;
}

public function openTo() : Time
{
return $this->endHour;
}

public function isOpen(Time $time) : bool
{
return $time->isGreaterThanEq($this->openFrom()) &&
$time->isLessThanEq($this->openTo());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace Aeon\Calendar\Gregorian\BusinessHours\WorkingHours;

use Aeon\Calendar\Gregorian\BusinessHours\WorkingHours;
use Aeon\Calendar\Gregorian\Time;
use Webmozart\Assert\Assert;

/**
* @psalm-immutable
*/
final class ShiftsWorkingHours implements WorkingHours
{
/**
* @var array<int, LinearWorkingHours>
*/
private array $workingHours;

public function __construct(LinearWorkingHours ...$workingHours)
{
Assert::greaterThan(\count($workingHours), 0, 'Shifts can\'t be empty');

\uasort(
$workingHours,
function (LinearWorkingHours $workingHoursA, LinearWorkingHours $workingHoursB) : int {
return $workingHoursA->openFrom()->isLessThanEq($workingHoursB->openFrom())
? -1
: 1;
}
);

$this->workingHours = \array_values($workingHours);
}

public function openFrom() : Time
{
return $this->workingHours[0]->openFrom();
}

public function openTo() : Time
{
return $this->workingHours[\count($this->workingHours) - 1]->openTo();
}

public function isOpen(Time $time) : bool
{
foreach ($this->workingHours as $workingHours) {
if ($workingHours->isOpen($time)) {
return true;
}
}

return false;
}
}
Loading

0 comments on commit e8b51a9

Please # to comment.