Last active
August 29, 2015 14:15
-
-
Save iansltx/978d8d043122d6710d2f to your computer and use it in GitHub Desktop.
Calculate due dates, taking business days into account
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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