0

BRIEFING:

I'm trying to calculate the business days between two dates for a custom project management system I'm building. Here's what I made so far as shown below. It seems to be working fine but I'm not sure how accurate it is and whether I should use it or not. Any feedback would be much appreciated!

CODE

<?php

    # date variables:
    date_default_timezone_set('Asia/Kuwait');
    $date['start'] = date('Y-m-d H:i:s');
    $date['end'] = '2013-12-01 08:00:00';
    $date['off'] = array('Friday','Saturday'); # usual days off in Kuwait)

    # calculate the difference:
    $date_s = new DateTime($date['start']);
    $date_e = new DateTime($date['end']);
    $interval = $date_s->diff($date_e);

    # obtain relevant values:
    $remaining_days = $interval->format('%r%a');
    $remaining_weeks = floor($remaining_days/7);
    $weekend_days = ($remaining_weeks*count($date['off']));

    # additional holidays (just an example):
    $extra_holidays_array = array
    (
        'holiday 1' => '2013-06-24 00:00:00',
        'holiday 2' => '2013-06-25 00:00:00',
        'holiday 3' => '2013-07-01 00:00:00',
        'holiday 4' => '2013-08-24 00:00:00'
    );

    # check if a real holiday:
    $extra_holidays = 0;
    foreach( $extra_holidays_array as $check_date )
    {
        $day_of_holiday = date('l', strtotime($check_date));
        if( ! in_array($day_of_holiday,$date['off']) ){ $extra_holidays++; }
    }

    # total holidays:
    $total_holidays = ($weekend_days+$extra_holidays);

    # business days:
    $business_days_nh = ($remaining_days-$weekend_days); # NO extra holidays
    $business_days_wh = ($remaining_days-$weekend_days-$extra_holidays); # WITH extra holidays

?>

<ul>
<li>Current Date: <?php echo $date['start']; ?></li>
<li>Deadline Date: <?php echo $date['end']; ?></li>
</ul>
<ul>
<li>Remaining Days: <?php echo $remaining_days; ?></li>
<li>Remaining Weeks: <?php echo $remaining_weeks; ?></li>
</ul>
<ul>
<li>Usual Holidays: <?php echo $weekend_days; ?></li>
<li>Extra Holidays: <?php echo $extra_holidays; ?></li>
<li>Total Holidays: <?php echo $total_holidays; ?></li>
</ul>
<ul>
<li>Business Days (Before Holidays): <?php echo $business_days_nh; ?></li>
<li>Business Days (After Holidays): <?php echo $business_days_wh; ?></li>
</ul>
M. Tahan
  • 111
  • 3
  • 1
    Do you have a set of unit tests covering edge cases? – Mark Baker May 27 '13 at 16:38
  • 1
    I'm sorry I don't understand. What do you mean exactly? If what I'm assuming is correct, then if the start date is equal or larger than the end date, I get negative values which are not a problem for me. – M. Tahan May 27 '13 at 16:40
  • I mean that many developers also write unit tests to go with their code, tests that they can run to prove that the code works correctly. That way, they don't need to ask other people if it works, but can prove that it works if all the tests pass – Mark Baker May 27 '13 at 16:49
  • http://stackoverflow.com/questions/336127/calculate-business-days – Valery Viktorovsky May 27 '13 at 16:58
  • To @MarkBaker: Based on the tests I've performed at [link](http://www.calculatorsoup.com/calculators/time/date-day.php), everything is in good order. I just wanted to get a second opinion to make absolutely sure I haven't missed something out. – M. Tahan May 27 '13 at 17:19

1 Answers1

1

I have written a function in a previous project for this exact purpose:

Here is what to use as parameters:

  • $start - Start Unix timestamp (you can use strtotime('1st january 2013') to get the timestamp
  • $end - End unix timestamp
  • Optional $holidays - An array of dates to count as holidays (IE bank holidays etc), it uses strtotime() to convert dates
  • Optional $returnAsArray - Return a list of working day timestamps, or simply the number of working days

Important Its important to know, timestamps given should be timestamps for the beginning of the day, for example:

To use 1st of January 2013 as a timestamp, use the value of: mktime(0, 0, 0, 1, 1, 2013);

function networkDays($start, $end, array $holidays = array(), $returnAsArray = false)
{
    if(!is_int($start)){ 
        trigger_error('Parameter 1 expected to be integer timestamp. ' . ucfirst(gettype($start)) . ' given.', E_USER_WARNING);
        return false;
    }

    if(!is_int($end)){ 
        trigger_error('Parameter 2 expected to be integer timestamp. ' . ucfirst(gettype($start)) . ' given.', E_USER_WARNING);
        return false;
    }

    if(!is_array($holidays)){ 
        $holidays = array();
        trigger_error('Parameter 3 expected to be Array. ' . ucfirst(gettype($holidays)) . ' given.', E_USER_NOTICE);
    }

    if(!is_bool($returnAsArray)){ 
        trigger_error('Parameter 4 expected to be Boolean. ' . ucfirt(gettype($returnAsArray)) . ' given.', E_USER_WARNING);
        return false;
    }


    if($start>=$end){ 
        $nEnd = $start;
        $nStart = $end;

        $start = $nStart;
        $end = $nEnd;
    }


    foreach($holidays as $key => $holiday)
    {
        $holidays[$key] = strtotime($holiday);
    }


    $numberOfDays = ceil(((($end-$start)/60)/60)/24);

    $networkDay = 0;
    $networkDayArray = array();

    for($d = 0; $d < $numberOfDays; $d++)
    {
        $dayTimestamp = $start+(86400*$d);

        if(date('N',$dayTimestamp)<6 && !in_array($dayTimestamp,$holidays))
        {
            $networkDay += 1;
            $networkDayArray[] = $dayTimestamp;
        }
    }


    if($returnAsArray)
    {
        return $networkDayArray;
    } else {
        return $networkDay;
    }
}
Phil Cross
  • 9,017
  • 12
  • 50
  • 84