Calculate Business Hours between two dates

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

Post Reply
User avatar
quadoc
Forum Contributor
Posts: 137
Joined: Fri Jul 01, 2005 5:33 pm
Location: Atlanta, GA

Calculate Business Hours between two dates

Post by quadoc »

Does anyone have a script or know where I can get a script to calculate business hours between two dates excluding holidays. I can probably create one myself but it would probably takes forever. :?
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Post by Weirdan »

Once I had to calculate business days between two dates, here's what I came to:

Code: Select all

/**
     Number of seconds in a day
     **/
    define("DAYSECONDS", 60 * 60 * 24);
    
    /**
     counts holidays falling into the given range

     @param     $left   integer     leftmost end-point of the interval (unix timestamp)
     @param     $right  integer     rightmost end-point of the interval (unix timestamp)
     @return            integer     count of holidays falling into the given range
     **/
    function count_weekend_days($left, $right) {
        if($right < $left) return false;

        $firstmonday = strtotime("monday", $left);
        $lastmonday = strtotime("last monday", $right);

        $weeks_between = floor( ($lastmonday - $firstmonday) / (DAYSECONDS * 7) );

        $first_weekend = ceil( ($firstmonday - $left) / DAYSECONDS );
        $first_weekend = ($first_weekend < 2 ? $first_weekend : 2);

        $last_weekend = floor( ( strtotime("next monday", $lastmonday) - $right ) / DAYSECONDS );
        $last_weekend = ( (2 - $last_weekend) > 0 ? ( 2 - $last_weekend) : 0);

        return $first_weekend + 2 * $weeks_between + $last_weekend;

    }

    /**
     counts weekdays falling into the given range

     @param     $left   integer     leftmost end-point of the interval (unix timestamp)
     @param     $right  integer     rightmost end-point of the interval (unix timestamp)
     @return            integer     count of weekdays falling into the given range
     **/
    function count_working_days($left, $right) {
        if($right < $left) return false;
        return floor( ($right - $left) / DAYSECONDS ) - count_weekend_days($left, $right);
    }
User avatar
pickle
Briney Mod
Posts: 6445
Joined: Mon Jan 19, 2004 6:11 pm
Location: 53.01N x 112.48W
Contact:

Post by pickle »

Untested, but this might work:

Code: Select all

//set up holidays.  There may be a built in PHP function, but I don't know it.
$holidays[] = '24.12.2005';
$holidays[] = '25.12.2005';

$date1 = "1100674800";
$date2 = "1132243341";
$running_date = $date1;

$business_hours_per_day = 8;
$total_hours = 0;
do
{
   $day_number = date('w',$running_date);
   
   //only consider weekdays
   if($day_number != 0 && $day_number != 6)
   {
      //check if day is a holiday
      if(!in_array(date('j.m.Y',$running_date),$holidays)
      {
         $total_hours += $business_hours_per_day;
      }
   }
   $running_date = strototime('+1 day',$running_date);
}
while($running_date <= $date2);
This will also work when a day doesn't have 60 * 60 * 24 seconds (switching in and out of daylight saving time)
Real programmers don't comment their code. If it was hard to write, it should be hard to understand.
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Post by Weirdan »

pickle wrote:This will also work when a day doesn't have 60 * 60 * 24 seconds (switching in and out of daylight saving time)
Mine works as well :) Rounding takes care of DST switching.

There's another pitfall in your algorithm: its execution time is in linear proportion to the difference between last_date and first_date, while mine has constant execution time.
User avatar
pickle
Briney Mod
Posts: 6445
Joined: Mon Jan 19, 2004 6:11 pm
Location: 53.01N x 112.48W
Contact:

Post by pickle »

Yes, but mine has more indentation, therefore is better in every possible way ;)

Though I do think mine's a little easier, conceptually, to understand - that point about execution time is a good one.
Real programmers don't comment their code. If it was hard to write, it should be hard to understand.
User avatar
quadoc
Forum Contributor
Posts: 137
Joined: Fri Jul 01, 2005 5:33 pm
Location: Atlanta, GA

Post by quadoc »

Thanks for the tips guys, I tested both versions and here what I came up with.

Code: Select all

$date1 = 1131026473;
$date2 = 1131390020;
echo "begin date ".$date1." = ";echo date('m/d/Y g:i a D',$date1)."<br>";
echo "end date   ".$date2." = ";echo date('m/d/Y g:i a D',$date2)."<br>";
echo "Weirdan version <br>";
echo count_working_days($date1,$date2)." days";
echo "<br>";
echo "Pickle version<br>";
echo count_working_days2($date1,$date2)." hours";
begin date 1131026473 = 11/03/2005 9:01 am Thu
end date 1131390020 = 11/07/2005 2:00 pm Mon

Weirdan version
1 days

Pickle version
24 hours

I need it to calculate 9:01 - 6:00pm = 8hr on Thu
9:00 - 6:00pm = 8hr on Fri
9:00 - 2:00pm = 5hr on Mon
total = 21 hrs
User avatar
pickle
Briney Mod
Posts: 6445
Joined: Mon Jan 19, 2004 6:11 pm
Location: 53.01N x 112.48W
Contact:

Post by pickle »

Tack this on the end of mine:

Code: Select all

$running_total += (($running_date - $date2) * 60 * 60);

Again this is untested. What this does is add the difference (positive or negative) between the calculated end date and the actual end date.
Real programmers don't comment their code. If it was hard to write, it should be hard to understand.
yum-jelly
Forum Commoner
Posts: 98
Joined: Sat Oct 29, 2005 9:16 pm

Post by yum-jelly »

I would do something like below, because then you can base exact hours on each day and also include holidays that may or may or may not be working holidays! You could also easily add different work weeks with different hours but that would be another example!

Code: Select all

<?

// the starting date

$start_date = '11-17-2005';

// the ending date

$end_date = '11-20-2005';

// the daily hours in a week! sunday to saturday

$work_hours = array ( 0, 8, 8, 8, 8, 8, 0 );

// an array of holidays (key = date, value = hours of work for that holiday)

$holidays = array ( '11-24-2005' => 4, '12-25-2005' => 0 );

echo hours_till ( $start_date, $end_date, $work_hours, $holidays );

function hours_till ( $s, $e, $w, $h )
{
	$r = array ();
	$s = mktime ( 0, 0, 0, substr ( $s, 0, 2 ), substr ( $s, 3, 2 ), substr ( $s, 6, 4 ) );
	$e = mktime ( 0, 0, 0, substr ( $e, 0, 2 ), substr ( $e, 3, 2 ), substr ( $e, 6, 4 ) );

	if ( is_array ( $h ) )
	{
		foreach ( $h AS $hn => $hv )
		{
			$temp = mktime ( 0, 0, 0, substr ( $hn, 0, 2 ), substr ( $hn, 3, 2 ), substr ( $hn, 6, 4 ) );

			if ( $temp >= $s && $temp <= $e )
			{
				$r[$temp] = $hv;
			}
		}
	}

	$th = 0;

	$ns = date ( 'w', $s );

	for ( $x = $s; $x <= $e; $x += 86400 )
	{
		$f = true;

		foreach ( $r AS $hn => $hv )
		{
			if ( $x == $hn )
			{
				$f = false;
				$th += $hv;
				unset ( $r[$hn] );
				break;
			}
		}

		if ( $f )
		{
			$th += $w[$ns];
		}

		$ns = ( $ns == 6 ? 0 : ++$ns );
	}

	return ( $th );
}

?>

yj
User avatar
quadoc
Forum Contributor
Posts: 137
Joined: Fri Jul 01, 2005 5:33 pm
Location: Atlanta, GA

Post by quadoc »

yum-yelly, your version came up to 24hr based on the above inputs same as Pickle. How do I make it to calculate 21hr? Thanks for the tips though.
User avatar
quadoc
Forum Contributor
Posts: 137
Joined: Fri Jul 01, 2005 5:33 pm
Location: Atlanta, GA

Post by quadoc »

yum-yelly,

I was trying to understand the code and was wondering what is the variable $hv? Thanks...
User avatar
RobertGonzalez
Site Administrator
Posts: 14293
Joined: Tue Sep 09, 2003 6:04 pm
Location: Fremont, CA, USA

Post by RobertGonzalez »

It's the value for the array index 'hn' in the array 'h'. Wasn't it totally obvious, silly?

NOTE | This post is totally a joke. I just couldn't pass up a chance to poke fun at that variable naming convention. Carry on...
Post Reply