Skip to content

Instantly share code, notes, and snippets.

@iansltx
Last active August 29, 2015 14:15
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save iansltx/978d8d043122d6710d2f to your computer and use it in GitHub Desktop.
Save iansltx/978d8d043122d6710d2f to your computer and use it in GitHub Desktop.
Calculate due dates, taking business days into account
<?php
class FastForwarder
{
protected $skipWhen = [];
protected $numDays = 0;
public static function createWithDays($num_days)
{
$ff = new static;
$ff->numDays = $num_days;
return $ff;
}
public function skipWhen($filter, $filter_name)
{
if (!is_bool(call_user_func($filter, new \DateTime()))) {
throw new \InvalidArgumentException('$filter must accept a \DateTimeInterface and return a boolean.');
}
$this->skipWhen[$filter_name] = $filter;
return $this;
}
public function exec(\DateTimeInterface $dt, array &$filters_used = null)
{
$newDt = new \DateTime($dt->format('c'));
$daysLeft = $this->numDays;
$dateInterval = new \DateInterval('P1D');
$filtersUsedMap = [];
$increment = 1;
while ($daysLeft > 0) {
$skipFilterName = $this->getSkippedBy($newDt);
if ($skipFilterName) {
$filtersUsedMap[$skipFilterName] = true;
} else {
--$daysLeft;
}
$newDt->add($dateInterval);
}
do { // if end date falls on a skipped day, keep skipping until a valid day is found
$skipFilterName = $this->getSkippedBy($newDt);
if ($skipFilterName) {
$filtersUsedMap[$skipFilterName] = true;
$newDt->add($dateInterval);
}
} while ($skipFilterName);
if ($filters_used !== null) {
$filters_used = array_keys($filtersUsedMap);
}
return $newDt;
}
protected function getSkippedBy(\DateTime $dt)
{
foreach ($this->skipWhen as $name => $fn) {
if (call_user_func($fn, $dt)) {
return $name;
}
}
return null;
}
}
<?php // load FastForwarder somehow...
$dayCount = 10;
$ff = FastForwarder::createWithDays($dayCount);
$ff->skipWhen(function (\DateTimeInterface $dt) {
return in_array($dt->format('w'), [0, 6]);
}, 'weekend');
$ff->skipWhen(function (\DateTimeInterface $dt) {
if ($dt->format('m') != 2) {
return false;
}
return $dt->format('w') == 1 && $dt->format('d') > 14 && $dt->format('d') <= 21;
}, 'presidents_day');
$ff->skipWhen(function (\DateTimeInterface $dt) {
if ($dt->format('m') != 11) {
return false;
}
return $dt->format('w') == 4 && $dt->format('d') > 21 && $dt->format('d') <= 28;
}, 'thanksgiving');
$ff->skipWhen(function (\DateTimeInterface $dt) {
return $dt->format('m') == 12 && $dt->format('d') == 25;
}, 'christmas');
$filtersUsed = [];
$startDate = new \DateTime('2015-11-20 09:00:00');
// nov 20 isn't skipped; reach nov 21 with 9 days remaining
// nov 21 is skipped; reached nov 22 with 9 days remaining
// nov 22 skipped; 9 days
// nov 23 not skipped; 8 days
// nov 24 not skipped; 7 days
// nov 25 not skipped; 6 days
// nov 26 skipped; 6 days
// nov 27 not skipped; 5 days
// nov 28 skipped; 5 days
// nov 29 skipped; 5 days
// nov 30 not skipped; 4 days
// dec 1 not skipped; 3 days
// dec 2 not skipped; 2 days
// dec 3 not skipped; 1 day
// dec 4 not skipped; reached dec 5 with 0 days remaining
// dec 5 is not a valid due date; advance to dec 6
// dec 6 is not a valid due date; advance to dec 7
// dec 7 is a valid due date; we're good to go!
$endDate = $ff->exec($startDate, $filtersUsed); // $filtersUsed is passed by ref and optional
echo $endDate->format('Y-m-d H:i:s') . "\n"; // 2015-12-07 09:00:00
echo implode(', ', $filtersUsed) . "\n"; // weekend, thanksgiving
$otherStartDate = new \DateTime('2015-02-12 09:00:00');
$otherEndDate = $ff->exec($otherStartDate, $filtersUsed);
echo $otherEndDate->format('Y-m-d H:i:s') . "\n"; // 2015-02-27 09:00:00
echo implode(', ', $filtersUsed) . "\n"; // weekend, presidents_day
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment