How to add hours onto a DateTime taking into account working hours and on hold time

Ok I am really struggling with this one and desperalty need a hand with it. Its not so much the coding thats the problem its that I cant get my head around the logical steps that I need to take to make this work.

How would I structure a function that will allow me to add hours only to dates between 08:30 in the morning until 17:30 of the evening, excluding Saturday and Sunday?

For example: if the given date was in 17:30 on a Tuesday and I add two hours the result would be 10:30 on Wednesday. Or if the given date was in 17:00 on Friday, the result would be 10:00 on Monday.

Another catch is I also need to account for time placed on hold but Ill worry about that after I get the first part working.

Does this make sense?

here is the code I have come up with so far but its not correct and I am starting to really struggle with the logic:


function add_deadline($givenDate, $hours){

    $date_opened = new DateTime($givenDate);
    $today = new DateTime($date_opened->format('Y-m-d'));//get today's date only

    $opened_copy = clone $date_opened;

    $deadline = $opened_copy->add(new DateInterval('PT'.$hours.'H'));//add on hours
    //set working hours
    $startofday = clone $date_opened;
    $startofday->setTime(8,30);

    $endofday = clone $date_opened;
    $endofday->setTime(17,30);

    //check if deadline is after 17:30
    if($deadline > $endofday){
        //get difference from the end of working day to deadline in hours
        $t1 = $endofday->format('Y-m-d H:i:s');
        $difference = $endofday->diff($deadline);
        $diff_date = $endofday->add($difference);
        $t2 = $diff_date->format('Y-m-d H:i:s');

        $over = calculate_hours($t1, $t2);

        //set to next working since deadline is after 17;30
        $nextday = $today->modify('+ 1 day');
        $nextday->setTime(8, 30);
        //Set deadline to the next day and add on hours
        $deadline = $nextday->add(new DateInterval("PT".$over."H"));

    }
    //get days to add on if the next day is a weekend day
    $ifweekend = check_day($deadline);
    $deadline = $deadline->modify($ifweekend);

    $new_time = $deadline->format('Y-m-d H:i');

    return $new_time;

}

//checks if passed in date falls on a weekend day and if so passes back day difference
function check_day($deadline) {

    $day = $deadline->format(l);

    if($day == 'Saturday'){

        return '+ 2 days';
    }
    elseif($deadline == 'Sunday'){
        return "+ 1 days";
    }
    else {
        return '+ 0 days';
    }

}
//returns the hours between two dates
function calculate_hours($t1, $t2){
    $t1 = StrToTime ($t1);
    $t2 = StrToTime ($t2);

    $diff = $t2 - $t1;
    $hours = $diff / ( 60 * 60 );

    return $hours;
}

//Pass in date and the hours to add on
$future = add_deadline('2014-06-26 11:30', '18');
echo $future;

Inputting 2014-06-26 11:30 and adding 18 hours results in: 2014-06-27 20:30 when infact it should be 2014-06-30 11:30

As I said really struggling with this one hence this post, even a breakdown of the steps involved in how to calculate the new dates correctly would be a massive help.

Cheers

I found a C# function that does the same thing:


static DateTime AddWithinWorkingHours(DateTime start, TimeSpan offset)
{
  const int hoursPerDay = 9;
  const int startHour = 8;

// Don't start counting hours until start time is during working hours
if (start.TimeOfDay.TotalHours > startHour + hoursPerDay)
   start = start.Date.AddDays(1).AddHours(startHour);
if (start.TimeOfDay.TotalHours < startHour)
   start = start.Date.AddHours(startHour);
if (start.DayOfWeek == DayOfWeek.Saturday)
   start.AddDays(2);
else if (start.DayOfWeek == DayOfWeek.Sunday)
   start.AddDays(1);

// Calculate how much working time already passed on the first day
TimeSpan firstDayOffset = start.TimeOfDay.Subtract(TimeSpan.FromHours(startHour));

// Calculate number of whole days to add
int wholeDays = (int)(offset.Add(firstDayOffset).TotalHours / hoursPerDay);

// How many hours off the specified offset does this many whole days consume?
TimeSpan wholeDaysHours = TimeSpan.FromHours(wholeDays * hoursPerDay);

// Calculate the final time of day based on the number of whole days spanned and the specified offset
TimeSpan remainder = offset - wholeDaysHours;

// How far into the week is the starting date?
int weekOffset = ((int)(start.DayOfWeek + 7) - (int)DayOfWeek.Monday) % 7;

// How many weekends are spanned?
int weekends = (int)((wholeDays + weekOffset) / 5);

 // Calculate the final result using all the above calculated values
 return start.AddDays(wholeDays + weekends * 2).Add(remainder);
 }

Anybody able to convert this to php?

Anybody messing with this? My mind is mush and I’m working with straight forward strtotime instead of class objects.

Well after fighting with strtotime and trying to figure out if the seconds were in the allowed time and not a weekend, it finally dawned on me to try a different way. I work with arrays all the time and I thought why not build an array of good hours and use the $hours variable as the key. Simple. Anyway, here’s my take.

<?php 

function add_deadline($givenDate, $hours){ 
	$range = (ceil($hours/7)*120);
	$cnt=1;
	$goodhours = array();
	foreach(range(1,$range) as $num):
	
		$datetime = date("Y-m-d H:i:s", strtotime('+'.$num.' hour',strtotime($givenDate)));
		$time = date("Hi", strtotime('+'.$num.' hour',strtotime($givenDate)));
		$day = date("D", strtotime('+'.$num.' hour', strtotime($givenDate)));
		
		if($day != 'Sat' && $day != 'Sun' && $time >= 830 && $time <= 1730):
			$goodhours[$cnt] = $datetime;
			 
			if($cnt >= $hours && array_key_exists($hours,$goodhours)):
				return $goodhours[$hours];
				break;
			endif;
			
			$cnt++;
		endif;
		
	endforeach;
}

//Pass in date and the hours to add on
$future = add_deadline('2014-06-26 11:30', '18');
echo $future; 
?>

Thanks for taking the time to help, not many people did.

I think Iv cracked it, What do you think of this method?


function addRollover($givenDate, $addtime, $dayStart, $dayEnd, $weekDaysOnly) {
    //Break the working day start and end times into hours, minuets
    $dayStart = explode(',', $dayStart);
    $dayEnd = explode(',', $dayEnd);
    //Create required datetime objects and hours interval
    $datetime = new DateTime($givenDate);
    $endofday = clone $datetime;
    $endofday->setTime($dayEnd[0], $dayEnd[1]); //set end of working day time
    $interval = 'PT'.$addtime.'H';
    //Add hours onto initial given date
    $datetime->add(new DateInterval($interval));
    //if initial date + hours is after the end of working day
    if($datetime > $endofday)
    {
        //get the difference between the initial date + interval and the end of working day in seconds
        $seconds = $datetime->getTimestamp()- $endofday->getTimestamp();

        //Loop to next day
        while(true)
        {
            $endofday->add(new DateInterval('PT24H'));//Loop to next day by adding 24hrs
            $nextDay = $endofday->setTime($dayStart[0], $dayStart[1]);//Set day to working day start time
            //If the next day is on a weekend and the week day only param is true continue to add days
            if(in_array($nextDay->format('l'), array('Sunday','Saturday')) && $weekDaysOnly)
            {
                continue;
            }
            else //If not a weekend
            {
                $tmpDate = clone $nextDay;
                $tmpDate->setTime($dayEnd[0], $dayEnd[1]);//clone the next day and set time to working day end time
                $nextDay->add(new DateInterval('PT'.$seconds.'S')); //add the seconds onto the next day
                //if the next day time is later than the end of the working day continue loop
                if($nextDay > $tmpDate)
                {
                    $seconds = $nextDay->getTimestamp()-$tmpDate->getTimestamp();
                    $endofday = clone $tmpDate;
                    $endofday->setTime($dayStart[0], $dayStart[1]);
                }
                else //else return the new date.
                {
                    return $endofday;

                }
            }
        }
    }
    return $datetime;
}


$currentTime = '2014-06-27 08:30:00';
$dayStart = '8,30';
$dayEnd = '17,30';

$future = addRollover($currentTime, 65, $dayStart, $dayEnd, true);

echo "Results: </br>";
echo $future->format('Y-m-d H:i:s').'</br>';


see any problems with it?

Initially I don’t know.
With my version, given the same datetime and hours I get
2014-07-07 13:30:00

Your version results in
2014-07-08 10:30:00

So either mine is wrong or yours is adding some extra time.
After checking mine I found an error in that I was adding “$goodhour” for the transition from 17:30 to 8:30 on each day. Adding another array and check to see if the “day” is in this new “days” array I can now check for this transition between days.

Result: Both your code and my version come up with the same answer. Yeah!

<?php

function add_deadline($givenDate, $hours){
	$range = (ceil($hours/7)*120);
	$cnt=1;
	$days = array();
	$goodhours = array();
	$days[] = date("Y-m-d", strtotime($givenDate));
	foreach(range(1,$range) as $num):
	
		$datetime = date("Y-m-d H:i:s", strtotime('+'.$num.' hour',strtotime($givenDate)));
		$date = date("Y-m-d", strtotime('+'.$num.' hour',strtotime($givenDate)));
		$time = date("Hi", strtotime('+'.$num.' hour',strtotime($givenDate)));
		$day = date("D", strtotime('+'.$num.' hour', strtotime($givenDate)));
		if($day != 'Sat' && $day != 'Sun' && $time >= 830 && $time <= 1730):
		
			if(!in_array($date, $days)){
				$days[] = $date;
			}else{
				$goodhours[$cnt] = $datetime;
				
				if($cnt >= $hours && array_key_exists($hours,$goodhours)):
					return $goodhours[$hours];
					break;
				endif;
				
				$cnt++;
			}
		endif;
		
	endforeach;
}

//Pass in date and the hours to add on
$future = add_deadline('2014-06-27 08:30', '65');

echo "Results: </br>";
echo $future;

?>

Timezone difference? My server is 4 timezones away and I was puzzled why the time was off until I “set” it.

Good point in that timezone should be set using date_default_timezone_set();
However it was an error in my code where I didn’t account correctly for crossing days. My last version should account for that.

For anyone following the thread.
17:30 is a valid time and 8:30 is a valid time and my first attempt listed both times in the array, however as far as adjustment hours, an hour had not passed. My last solution accounts for that.

Drummin, Im not sure I quite understand yours with the goodhour concept. Could your function be easily altered to output the number of hours in working hours?

Andy91, well it is building an array adding one hour for every adjust hour given. Say I change the bottom part of the code to return full array and print the result for 25 hours.
Modified Section:

				if($cnt >= $hours && array_key_exists($hours,$goodhours)):
					//return $goodhours[$hours];
					return $goodhours;
					break;
				endif;
				
				$cnt++;
			}
		endif;
		
	endforeach;
}

//Pass in date and the hours to add on
$future = add_deadline('2014-06-27 08:30', '25');

echo "Results: <br />";
echo "{$future[25]}<br />";
echo "<pre>";
print_r($future);
echo "</pre>";

You’ll see an array of all the hours after the initial datetime.

Results:
2014-07-01 15:30:00

Array
(
    [1] => 2014-06-27 09:30:00
    [2] => 2014-06-27 10:30:00
    [3] => 2014-06-27 11:30:00
    [4] => 2014-06-27 12:30:00
    [5] => 2014-06-27 13:30:00
    [6] => 2014-06-27 14:30:00
    [7] => 2014-06-27 15:30:00
    [8] => 2014-06-27 16:30:00
    [9] => 2014-06-27 17:30:00
    [10] => 2014-06-30 09:30:00
    [11] => 2014-06-30 10:30:00
    [12] => 2014-06-30 11:30:00
    [13] => 2014-06-30 12:30:00
    [14] => 2014-06-30 13:30:00
    [15] => 2014-06-30 14:30:00
    [16] => 2014-06-30 15:30:00
    [17] => 2014-06-30 16:30:00
    [18] => 2014-06-30 17:30:00
    [19] => 2014-07-01 09:30:00
    [20] => 2014-07-01 10:30:00
    [21] => 2014-07-01 11:30:00
    [22] => 2014-07-01 12:30:00
    [23] => 2014-07-01 13:30:00
    [24] => 2014-07-01 14:30:00
    [25] => 2014-07-01 15:30:00
)

You see that the array KEYs represent each hour adjustment with KEY 25 being the target. So “Working Hours” we already know to be 25 or whatever amount that is entered. Or are you asking something else?

I see how your function works now, Both the function i posted and yours are accptable solutions to this question. Iv started a new thread where I am just trying to get the amount of hours that have elapsed between two given dates in working hours. I would’nt mind your input on that.

Cheers

Ahh, two dates. Will do.