Last active
February 9, 2022 05:01
-
-
Save merlinblack/ccdb4c32c769445746386311001abc6f to your computer and use it in GitHub Desktop.
Work out business days - ignoring holidays
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 | |
use Carbon\Carbon; | |
/** | |
* BusinessDayCalculator | |
* Computes date interval, or adds days to a date while | |
* skipping the weekend. | |
* | |
* NOT considered: | |
* - Holidays | |
* - Locales where Saturday and Sunday are not the weekend. | |
*/ | |
class BusinessDayCalculator | |
{ | |
// See https://gist.github.com/quawn/8503445 | |
static public function countBusinessDays( $start, $end) | |
{ | |
if (is_string($start)) { | |
$start = new Carbon($start); | |
} | |
if (is_string($end)) { | |
$end = new Carbon($end); | |
} | |
$actualDays = $start ->diff($end)->d; // "Day" component of interval | |
$fullWeeks = floor( $actualDays / 7 ); | |
$remainingDays = fmod( $actualDays, 7); | |
$startingDay = $start->dayOfWeekIso; | |
$endingDay = $end->dayOfWeekIso; | |
/** | |
* The two can be equal in leap years when february has 29 days, | |
* In the first case the whole interval is within a week, | |
* in the second case the interval is two or more weeks. | |
*/ | |
if ($startingDay <= $endingDay) { | |
if ($startingDay <= 6 && 6 <= $endingDay) $remainingDays--; | |
if ($startingDay <= 7 && 7 <= $endingDay) $remainingDays--; | |
} | |
else { | |
/** | |
* the day of the week for start is later than the day of | |
* the week for end | |
*/ | |
if ($startingDay == 7) { | |
/** | |
* if the start date is a Sunday, | |
* then we definitely subtract 1 day | |
*/ | |
$remainingDays--; | |
if ($endingDay == 6) { | |
/** | |
* if the end date is a Saturday, | |
* then we subtract another day | |
*/ | |
$remainingDays--; | |
} | |
} | |
else { | |
/** | |
* the start date was a Saturday (or earlier), | |
* and the end date was (Mon..Fri) | |
* so we skip an entire weekend and subtract 2 days | |
*/ | |
$remainingDays -= 2; | |
} | |
} | |
/** | |
* The no. of business days is: | |
* (no. of weeks between the dates) * (5 working days) + the remainder | |
* february in non leap years will give a remainder of 0 | |
*/ | |
$workingDays = $fullWeeks * 5; | |
if ($remainingDays > 0 ) | |
{ | |
$workingDays += $remainingDays; | |
} | |
return $workingDays; | |
} | |
static public function addBusinessDays($date, int $businessDays ) | |
{ | |
if (is_string($date)) { | |
$date = new Carbon($date); | |
} | |
$weeks = floor($businessDays / 5); | |
$remainder = fmod( $businessDays, 5); | |
/** if the date is in the weekend, skip to Monday, before adding days */ | |
if ($date->dayOfWeekIso == 6) $date->addDays(2); | |
if ($date->dayOfWeekIso == 7) $date->addDay(); | |
$date->addWeeks($weeks); | |
/** | |
* does the current day plus remainder take us | |
* into/through the weekend? | |
*/ | |
if ($date->dayOfWeekIso + $remainder > 6) { | |
/** skip over Saturday and Sunday */ | |
$remainder += 2; | |
} | |
$date->addDays($remainder); | |
return $date; | |
} | |
static public function subtractBusinessDays($date, int $businessDays ) | |
{ | |
if (is_string($date)) { | |
$date = new Carbon($date); | |
} | |
$weeks = floor($businessDays / 5); | |
$remainder = fmod( $businessDays, 5); | |
/** if the date is in the weekend, skip back to Friday, before subtracting days */ | |
if ($date->dayOfWeekIso == 6) $date->subDay(); | |
if ($date->dayOfWeekIso == 7) $date->subDays(2); | |
$date->subWeeks($weeks); | |
/** | |
* does the current day minus remainder take us | |
* into/through the previous weekend? | |
*/ | |
if ($date->dayOfWeekIso - $remainder < 1) { | |
/** skip over Saturday and Sunday */ | |
$remainder += 2; | |
} | |
$date->subDays($remainder); | |
return $date; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment