Calculate business days

143,015

Solution 1

Here's a function from the user comments on the date() function page in the PHP manual. It's an improvement of an earlier function in the comments that adds support for leap years.

Enter the starting and ending dates, along with an array of any holidays that might be in between, and it returns the working days as an integer:

<?php
//The function returns the no. of business days between two dates and it skips the holidays
function getWorkingDays($startDate,$endDate,$holidays){
    // do strtotime calculations just once
    $endDate = strtotime($endDate);
    $startDate = strtotime($startDate);


    //The total number of days between the two dates. We compute the no. of seconds and divide it to 60*60*24
    //We add one to inlude both dates in the interval.
    $days = ($endDate - $startDate) / 86400 + 1;

    $no_full_weeks = floor($days / 7);
    $no_remaining_days = fmod($days, 7);

    //It will return 1 if it's Monday,.. ,7 for Sunday
    $the_first_day_of_week = date("N", $startDate);
    $the_last_day_of_week = date("N", $endDate);

    //---->The two can be equal in leap years when february has 29 days, the equal sign is added here
    //In the first case the whole interval is within a week, in the second case the interval falls in two weeks.
    if ($the_first_day_of_week <= $the_last_day_of_week) {
        if ($the_first_day_of_week <= 6 && 6 <= $the_last_day_of_week) $no_remaining_days--;
        if ($the_first_day_of_week <= 7 && 7 <= $the_last_day_of_week) $no_remaining_days--;
    }
    else {
        // (edit by Tokes to fix an edge case where the start day was a Sunday
        // and the end day was NOT a Saturday)

        // the day of the week for start is later than the day of the week for end
        if ($the_first_day_of_week == 7) {
            // if the start date is a Sunday, then we definitely subtract 1 day
            $no_remaining_days--;

            if ($the_last_day_of_week == 6) {
                // if the end date is a Saturday, then we subtract another day
                $no_remaining_days--;
            }
        }
        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
            $no_remaining_days -= 2;
        }
    }

    //The no. of business days is: (number of weeks between the two dates) * (5 working days) + the remainder
//---->february in none leap years gave a remainder of 0 but still calculated weekends between first and last day, this is one way to fix it
   $workingDays = $no_full_weeks * 5;
    if ($no_remaining_days > 0 )
    {
      $workingDays += $no_remaining_days;
    }

    //We subtract the holidays
    foreach($holidays as $holiday){
        $time_stamp=strtotime($holiday);
        //If the holiday doesn't fall in weekend
        if ($startDate <= $time_stamp && $time_stamp <= $endDate && date("N",$time_stamp) != 6 && date("N",$time_stamp) != 7)
            $workingDays--;
    }

    return $workingDays;
}

//Example:

$holidays=array("2008-12-25","2008-12-26","2009-01-01");

echo getWorkingDays("2008-12-22","2009-01-02",$holidays)
// => will return 7
?>

Solution 2

Get the number of working days without holidays between two dates :

Use example:

echo number_of_working_days('2013-12-23', '2013-12-29');

Output:

3

Function:

function number_of_working_days($from, $to) {
    $workingDays = [1, 2, 3, 4, 5]; # date format = N (1 = Monday, ...)
    $holidayDays = ['*-12-25', '*-01-01', '2013-12-23']; # variable and fixed holidays

    $from = new DateTime($from);
    $to = new DateTime($to);
    $to->modify('+1 day');
    $interval = new DateInterval('P1D');
    $periods = new DatePeriod($from, $interval, $to);

    $days = 0;
    foreach ($periods as $period) {
        if (!in_array($period->format('N'), $workingDays)) continue;
        if (in_array($period->format('Y-m-d'), $holidayDays)) continue;
        if (in_array($period->format('*-m-d'), $holidayDays)) continue;
        $days++;
    }
    return $days;
}

Solution 3

There are some args for the date() function that should help. If you check date("w") it will give you a number for the day of the week, from 0 for Sunday through 6 for Saturday. So.. maybe something like..

$busDays = 3;
$day = date("w");
if( $day > 2 && $day <= 5 ) { /* if between Wed and Fri */
  $day += 2; /* add 2 more days for weekend */
}
$day += $busDays;

This is just a rough example of one possibility..

Solution 4

$startDate = new DateTime( '2013-04-01' );    //intialize start date
$endDate = new DateTime( '2013-04-30' );    //initialize end date
$holiday = array('2013-04-11','2013-04-25');  //this is assumed list of holiday
$interval = new DateInterval('P1D');    // set the interval as 1 day
$daterange = new DatePeriod($startDate, $interval ,$endDate);
foreach($daterange as $date){
if($date->format("N") <6 AND !in_array($date->format("Y-m-d"),$holiday))
$result[] = $date->format("Y-m-d");
}
echo "<pre>";print_r($result);

Solution 5

Holiday calculation is non-standard in each State. I am writing a bank application which I need some hard business rules for but can still only get a rough standard.

/**
 * National American Holidays
 * @param string $year
 * @return array
 */
public static function getNationalAmericanHolidays($year) {


    //  January 1 - New Year’s Day (Observed)
    //  Calc Last Monday in May - Memorial Day  strtotime("last Monday of May 2011");
    //  July 4 Independence Day
    //  First monday in september - Labor Day strtotime("first Monday of September 2011")
    //  November 11 - Veterans’ Day (Observed)
    //  Fourth Thursday in November Thanksgiving strtotime("fourth Thursday of November 2011");
    //  December 25 - Christmas Day        
    $bankHolidays = array(
          $year . "-01-01" // New Years
        , "". date("Y-m-d",strtotime("last Monday of May " . $year) ) // Memorial Day
        , $year . "-07-04" // Independence Day (corrected)
        , "". date("Y-m-d",strtotime("first Monday of September " . $year) ) // Labor Day
        , $year . "-11-11" // Veterans Day
        , "". date("Y-m-d",strtotime("fourth Thursday of November " . $year) ) // Thanksgiving
        , $year . "-12-25" // XMAS
        );

    return $bankHolidays;
}
Share:
143,015

Related videos on Youtube

AdamTheHutt
Author by

AdamTheHutt

Updated on May 13, 2022

Comments

  • AdamTheHutt
    AdamTheHutt almost 2 years

    I need a method for adding "business days" in PHP. For example, Friday 12/5 + 3 business days = Wednesday 12/10.

    At a minimum I need the code to understand weekends, but ideally it should account for US federal holidays as well. I'm sure I could come up with a solution by brute force if necessary, but I'm hoping there's a more elegant approach out there. Anyone?

    Thanks.

    • wormhit
      wormhit over 9 years
      I created a decent library for that. github.com/andrejsstepanovs/business-days-calculator It is stable and ready to go into production.
    • mika
      mika about 9 years
      Oh, I think we should mention that, nowadays, we can use the DateTime::modify function to add weekdays straight away: $my_date = new \DateTime(); $my_date->modify("+ 7 weekday"); will just perform seemlessly.
    • Suresh Kamrushi
      Suresh Kamrushi over 8 years
      A detail blog: goo.gl/YOsfPX
    • Dave
      Dave over 6 years
      A simpler/cleaner answer: stackoverflow.com/questions/5532002/…
  • mcgrailm
    mcgrailm almost 14 years
    this function expects a start and end date what if you have a start date and you want the result to be x business days from given date ?
  • flamingLogos
    flamingLogos almost 14 years
    @mcgrailm: It's a similar idea, but you'd probably want to write a second function because the arguments and return values are swapped. It would be something like ((X days % 5 days per week) * 2 days per weekend) + X days + difference of the day-of-the-week of the start and end dates + holidays).
  • flamingLogos
    flamingLogos almost 14 years
    @mcgrailm: Just found this question--its answers may point you in the right direction: stackoverflow.com/questions/2681787/….
  • mcgrailm
    mcgrailm over 13 years
    the only difference between mine and yours is that mine is one based your is zero based. You really haven't done anything here. I am happy this code helped you. However, I don't this qualifies as being "based on" code its more like your trying to claim my code as your own
  • mcgrailm
    mcgrailm about 13 years
    I'm assuming this is based off of Bobbin's code I believe I addressed this issue as well
  • Admin
    Admin about 13 years
    sorry but mcgrailm yours doesnt quite work....it doesnt take into account if the day in $enddate falls on a holiday...its only concerned about holidays during the adds unless im missing something
  • mcgrailm
    mcgrailm about 13 years
    @Richard i think I understand what your saying. It does NOT check the start date to see if its a holiday or weekend day it calculates business days AFTER the start date. if you wanted to include the start date in the check you could take out the +1 day
  • Elliot Robinson
    Elliot Robinson over 12 years
    I think it depends on the grammar and what your trying to achieve. For example I'm trying to work out when a report needs to be reviewed, but if it's submitted on a weekend, and needs to be completed in 3 business, then the counting starts on Monday (taking it's not a holiday)... I've posted my versions, which are both based on your code, but tweaked slightly.
  • ThePerson
    ThePerson almost 12 years
    Just if it helps anyone. I was trying to call this unix date format already rather than using strings in the YYYY-MM-DD format which this method expects. If you already have a date format in an int, you can just remove the strtotime() calls at the top. Hope this helps anyone with my problem
  • ThePerson
    ThePerson almost 12 years
    Another thing. If you compare Friday 2pm to the following Saturday 11am, it says that is 1 day, then as the end is a Saturday it takes one off and returns 0. It is kind of true that it is 0 days, but for some uses (Such as comparing work hand in dates ), any time after Friday 2pm should count as 1 day. (As it does comparing Friday 2pm to Friday 3pm, that is one day, but comparing Friday 2pm to Saturday 11am gives 0)
  • mnowotka
    mnowotka over 11 years
    There is a bug in this function. What if there is a timezone change between those dates? For example for CEST: echo getWorkingDays("2012-01-01","2012-05-01",$holidays); won't give you integer. stackoverflow.com/questions/12490521/…
  • solepixel
    solepixel about 11 years
    Independence day is $year.'-07-04' (July 4), not June 4
  • McNab
    McNab almost 11 years
    Very useful - one thing, I had to change !in_array($enddate,$holidays) to !in_array(date('Y-m-d',$enddate),$holidays) if using an holiday array passed in like Bobbins' - $holidays=array('2013-06-16','2013-07-12','2013-08-05'); Otherwise you are checking an array full of dates for a timestamp, which always returns false.
  • Thomas Ruiz
    Thomas Ruiz almost 10 years
    That's the old way to do it. Use Glavić answer if possible
  • KOGI
    KOGI over 9 years
    I upvoted because it works and has some great improvements :) But you really should at least mention @Suresh Kamrushi who posted most of this code a month earlier. :)
  • Will B.
    Will B. about 9 years
    If working days are always monday to friday you can substitute the DateInterval and workingdays array for $interval = DateInterval::createFromFormat('1 weekday'); I also recommend using $holidays = array_flip($holidays); before the foreach and if isset($holidays[$period->format('Y-m-d')]); to lower the amount of processing time needed per iteration. But recommend creating a custom function for holidays to be able to process relative holidays like thanksgiving last thursday of november or labor day first monday of september
  • Damian Yerrick
    Damian Yerrick almost 9 years
    Can this be expanded to include Good Friday (which I seem to remember is the Friday before the first Sunday after first full moon after March 21)?
  • Steve Horvath
    Steve Horvath over 7 years
    For me this function bugs out on the last quarters - maybe due to the aforementioned DST bug. Using Glavic's method works perfectly.
  • BrookeAH
    BrookeAH over 6 years
    Holidays that fall on weekends are observed on the day before (if Saturday) or the day after (if Sunday). I was able to use your array to get the actual observed days. Thanks.
  • user3655829
    user3655829 almost 5 years
    Be aware of different time zones. Here with Europe/Berlin this will not work. I just do it on the timestamp now with date('N') and adding 1day and its timezone independent.
  • jpswade
    jpswade over 4 years
    This method is incredibly complex, there's much simpler ways to achieve this, just scroll down...