Skip to content

Commit

Permalink
Merge d41d232 into 1bfbf43
Browse files Browse the repository at this point in the history
  • Loading branch information
Hugh Grigg committed Apr 12, 2018
2 parents 1bfbf43 + d41d232 commit cc7a887
Show file tree
Hide file tree
Showing 4 changed files with 281 additions and 19 deletions.
35 changes: 17 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@ can be useful for calculating shipping dates, for example.
This library provides an extension for the `Carbon` class in the
[Carbon](http://carbon.nesbot.com/docs/) date time library.

While Carbon already has methods like `addWorkingDays()` and
`diffInWeekendDays()`, this extension lets you handle business time more
precisely and flexibly. It can consider public holidays from WebCal.fi, as well
as your own customised times which can be specified directly or with constraint-
matching.
While Carbon already has methods like `diffInWeekendDays()`, this extension lets
you handle business time more precisely and flexibly. It can consider public
holidays from WebCal.fi, as well as your own customised times which can be
specified directly or with constraint-matching.

[Official music video for this library](https://www.youtube.com/watch?v=WGOohBytKTU)

Expand Down Expand Up @@ -55,7 +54,7 @@ $threeBusinessTime = $friday->addBusinessDays(3);
$monday = new BusinessTime\BusinessTime('Monday');
$previousBusinessDay = $now->subBusinessDay();
// = Friday
$threeBusinessTimeAgo = $now->subBusinessDays(3);
$threeBusinessDaysAgo = $now->subBusinessDays(3);
// = Wednesday
```

Expand Down Expand Up @@ -123,8 +122,6 @@ $businessTime = new BusinessTime\BusinessTime();
$businessTime->setLengthOfBusinessDay(BusinessTime\Interval::hours(6));
```

See https://secure.php.net/manual/en/class.dateinterval.php

If you have complicated business time constraints (see below), it might be
helpful to let BusinessTime calculate the length of a business day for you. You
can do that by passing in a `DateTime` representing your standard business day
Expand Down Expand Up @@ -159,9 +156,9 @@ and forces explicitness, e.g. with `$now->addBusinessTime(30)`.

Similarly, no unit smaller than an hour is included out-of-the-box because the
concept of a "business minute" is questionable for most use cases. You can
easily calculate minutes by multiplying by 60. Note that because the default
precision is one hour, you may well need to adjust the precision to e.g 15
minutes to get accurate calculations (see the note on precision and
calculate minutes by multiplying by 60 if you do need them. Note that because
the default precision is one hour, you may need to adjust the precision to e.g
15 minutes to get accurate calculations (see the note on precision and
performance).

## Describing business times
Expand All @@ -183,15 +180,15 @@ You can then use the `businessDays()` and `nonBusinessDays()` methods on the
time period to get that information. For example:

```php
$businessTime = new BusinessTime\BusinessTime();
$nonBusinessTimes = $businessTime->nonBusinessDays();
$businessDays = $timePeriod->businessDays();
$nonBusinessDays = $timePeriod->nonBusinessDays();
```

This returns an array of `BusinessTime` objects for each non-business day, which
can tell you their name:

```php
$nonBusinessTimes[0]->businessName();
$nonBusinessDays[0]->businessName();
// = e.g. "the weekend"
```

Expand All @@ -208,6 +205,8 @@ $timePeriod = BusinessTime\BusinessTimePeriod::fromBusinessTimes($start, $end);

$businessPeriods = $timePeriod->businessPeriods();
// = array of BusinessTimePeriod instances for each period of business time.
$nonBusinessPeriods = $timePeriod->nonBusinessPeriods();
// = array of BusinessTimePeriod instances for each period of non-business time.
```

This lets you see the precise business timings that make up the whole time
Expand Down Expand Up @@ -383,7 +382,7 @@ $factory = new BusinessTime\Remote\WebCalFiFactory(
$dates = $factory->getDates();
// = array of date objects from the specified calendar.
$webCalFiConstraint = $factory->makeConstraint();
// = a Dates constraint containing the retrieved dates and their descriptions.
// = a constraint containing the retrieved dates and their descriptions.
```

The constraint will be set up with names and dates from the WebCal.fi calendar
Expand All @@ -400,7 +399,7 @@ add customised exceptions to the dates it provides.
You can add any other source you like by implementing the `Constraint` interface
described above.

## Recurring business deadlines (WIP)
## Recurring business deadlines

As well as calculating business time, it's often useful to make calculations
about deadlines or "cut-off" times. For example, the cut-off time for
Expand All @@ -410,9 +409,9 @@ dealing with this.
You can create deadlines using the same time constraints described above:

```php
$deadline = new BusinessTime\Deadlines\RecurringDeadline(
$deadline = new BusinessTime\Deadline\RecurringDeadline(
new BusinessTime\Constraint\Weekdays(),
new BusinessTime\Constraint\TimesOfDay('11:00')
new BusinessTime\Constraint\HoursOfDay(11)
);
```

Expand Down
2 changes: 1 addition & 1 deletion src/BusinessTime.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
*/
class BusinessTime extends Carbon
{
/** @var All */
/** @var All|BusinessTimeConstraint[] */
private $businessTimeConstraints;

/** @var Interval */
Expand Down
96 changes: 96 additions & 0 deletions src/Deadline/RecurringDeadline.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

namespace BusinessTime\Deadline;

use BusinessTime\BusinessTime;
use BusinessTime\Constraint\BusinessTimeConstraint;
use BusinessTime\Constraint\Composite\All;
use DateTimeInterface;

/**
* A recurring cut-off point in time. For example, orders might be shipped at
* 11am on weekdays, and this class could be used to get the next shipping time.
*
* @see RecurringDeadlineTest
*/
class RecurringDeadline
{
/** @var All|BusinessTimeConstraint[] */
private $deadlineConstraints;

/**
* Be careful to ensure that the given constraints will reliably match a
* deadline without too many iterations.
*
* @param BusinessTimeConstraint ...$deadlineConstraints
*/
public function __construct(BusinessTimeConstraint ...$deadlineConstraints)
{
$this->deadlineConstraints = new All(...$deadlineConstraints);
}

/**
* Get the next time this deadline will occur after a given time.
*
* It's possible this will loop infinitely if the given constraints never
* match a time with the given precision.
*
* @param BusinessTime $time
*
* @return BusinessTime
*/
public function nextOccurrenceFrom(BusinessTime $time): BusinessTime
{
$time = $time->copy();

// Advance until we're out of a current deadline (as we want the next).
while ($this->isDeadline($time)) {
$time = $time->add($time->precision());
}

// Advance until we hit the next deadline.
while (!$this->isDeadline($time)) {
$time = $time->add($time->precision());
}

return $time->floor();
}

/**
* Get the previous time this deadline occurred after a given time.
*
* It's possible this will loop infinitely if the given constraints never
* match a time with the given precision.
*
* @param BusinessTime $time
*
* @return BusinessTime
*/
public function previousOccurrenceFrom(BusinessTime $time): BusinessTime
{
$time = $time->copy();

// Regress until we're out of a current deadline (as we want the
// previous one).
while ($this->isDeadline($time)) {
$time = $time->sub($time->precision());
}

// Regress until we hit the next deadline.
while (!$this->isDeadline($time)) {
$time = $time->sub($time->precision());
}

return $time->floor();
}

/**
* @param DateTimeInterface $time
*
* @return bool
*/
private function isDeadline(DateTimeInterface $time): bool
{
return $this->deadlineConstraints->isBusinessTime($time);
}
}
167 changes: 167 additions & 0 deletions tests/Unit/Deadline/RecurringDeadlineTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<?php

namespace BusinessTime\Tests\Unit\Deadline;

use BusinessTime\BusinessTime;
use BusinessTime\Constraint\HoursOfDay;
use BusinessTime\Constraint\WeekDays;
use BusinessTime\Deadline\RecurringDeadline;
use PHPUnit\Framework\TestCase;

/**
* Unit test the RecurringDeadline class.
*/
class RecurringDeadlineTest extends TestCase
{
/**
* Test finding the next occurrence of a deadline for weekdays at 11am.
*
* @dataProvider nextDeadlineWeekDays11amProvider
*
* @param string $time
* @param string $expectedNextDeadline
*/
public function testNextDeadlineWeekDays11am(
string $time,
string $expectedNextDeadline
): void {
// Given we have a recurring deadline for weekdays at 11am;
$deadline = new RecurringDeadline(new WeekDays(), new HoursOfDay(11));

// And a specific time;
$businessTime = new BusinessTime($time);

// When we get the next occurrence of the deadline;
$nextOccurrence = $deadline->nextOccurrenceFrom($businessTime);

// Then it should be as expected.
self::assertSame(
$expectedNextDeadline,
$nextOccurrence->format('l H:i')
);
}

/**
* Provides times and the expected next occurrence of a deadline for
* weekdays at 11am.
*
* @return array[]
*/
public function nextDeadlineWeekDays11amProvider(): array
{
return [
// From Monday
['Monday 00:00', 'Monday 11:00'],
['Monday 09:00', 'Monday 11:00'],
['Monday 09:30', 'Monday 11:00'],
['Monday 10:59', 'Monday 11:00'],
['Monday 11:00', 'Tuesday 11:00'],
['Monday 11:01', 'Tuesday 11:00'],
['Monday 17:00', 'Tuesday 11:00'],
['Monday 23:59', 'Tuesday 11:00'],
// From Friday
['Friday 00:00', 'Friday 11:00'],
['Friday 09:00', 'Friday 11:00'],
['Friday 09:30', 'Friday 11:00'],
['Friday 10:59', 'Friday 11:00'],
['Friday 11:00', 'Monday 11:00'],
['Friday 11:01', 'Monday 11:00'],
['Friday 17:00', 'Monday 11:00'],
['Friday 23:59', 'Monday 11:00'],
// From Saturday
['Saturday 00:00', 'Monday 11:00'],
['Saturday 09:00', 'Monday 11:00'],
['Saturday 09:30', 'Monday 11:00'],
['Saturday 10:59', 'Monday 11:00'],
['Saturday 11:00', 'Monday 11:00'],
['Saturday 11:01', 'Monday 11:00'],
['Saturday 17:00', 'Monday 11:00'],
['Saturday 23:59', 'Monday 11:00'],
// From Sunday
['Sunday 00:00', 'Monday 11:00'],
['Sunday 09:00', 'Monday 11:00'],
['Sunday 09:30', 'Monday 11:00'],
['Sunday 10:59', 'Monday 11:00'],
['Sunday 11:00', 'Monday 11:00'],
['Sunday 11:01', 'Monday 11:00'],
['Sunday 17:00', 'Monday 11:00'],
['Sunday 23:59', 'Monday 11:00'],
];
}

/**
* Test finding the previous occurrence of a deadline for weekdays at 11am.
*
* @dataProvider previousDeadlineWeekDays11amProvider
*
* @param string $time
* @param string $expectedNextDeadline
*/
public function testPreviousDeadlineWeekDays11am(
string $time,
string $expectedNextDeadline
): void {
// Given we have a recurring deadline for weekdays at 11am;
$deadline = new RecurringDeadline(new WeekDays(), new HoursOfDay(11));

// And a specific time;
$businessTime = new BusinessTime($time);

// When we get the previous occurrence of the deadline;
$nextOccurrence = $deadline->previousOccurrenceFrom($businessTime);

// Then it should be as expected.
self::assertSame(
$expectedNextDeadline,
$nextOccurrence->format('l H:i')
);
}

/**
* Provides times and the expected previous occurrence of a deadline for
* weekdays at 11am.
*
* @return array[]
*/
public function previousDeadlineWeekDays11amProvider(): array
{
return [
// From Friday
['Friday 00:00', 'Thursday 11:00'],
['Friday 09:00', 'Thursday 11:00'],
['Friday 09:30', 'Thursday 11:00'],
['Friday 10:59', 'Thursday 11:00'],
['Friday 11:00', 'Thursday 11:00'],
['Friday 11:01', 'Thursday 11:00'],
['Friday 17:00', 'Friday 11:00'],
['Friday 23:59', 'Friday 11:00'],
// From Monday
['Monday 00:00', 'Friday 11:00'],
['Monday 09:00', 'Friday 11:00'],
['Monday 09:30', 'Friday 11:00'],
['Monday 10:59', 'Friday 11:00'],
['Monday 11:00', 'Friday 11:00'],
['Monday 11:01', 'Friday 11:00'],
['Monday 17:00', 'Monday 11:00'],
['Monday 23:59', 'Monday 11:00'],
// From Saturday
['Saturday 00:00', 'Friday 11:00'],
['Saturday 09:00', 'Friday 11:00'],
['Saturday 09:30', 'Friday 11:00'],
['Saturday 10:59', 'Friday 11:00'],
['Saturday 11:00', 'Friday 11:00'],
['Saturday 11:01', 'Friday 11:00'],
['Saturday 17:00', 'Friday 11:00'],
['Saturday 23:59', 'Friday 11:00'],
// From Sunday
['Sunday 00:00', 'Friday 11:00'],
['Sunday 09:00', 'Friday 11:00'],
['Sunday 09:30', 'Friday 11:00'],
['Sunday 10:59', 'Friday 11:00'],
['Sunday 11:00', 'Friday 11:00'],
['Sunday 11:01', 'Friday 11:00'],
['Sunday 17:00', 'Friday 11:00'],
['Sunday 23:59', 'Friday 11:00'],
];
}
}

0 comments on commit cc7a887

Please sign in to comment.