Skip to content

Instantly share code, notes, and snippets.

@merlinblack
Last active February 9, 2022 05:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save merlinblack/ccdb4c32c769445746386311001abc6f to your computer and use it in GitHub Desktop.
Save merlinblack/ccdb4c32c769445746386311001abc6f to your computer and use it in GitHub Desktop.
Work out business days - ignoring holidays
<?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