Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Compare inputs

Status
Not open for further replies.

kzn

MIS
Jan 28, 2005
209
GB
Hi

I am totally stuck, basically I have 4 start times and 4 end times what I would like to do is compare each combination to make sure there is no overlap. ie Start time is 10:24 and end time is 11:15 for activity 1 and I dont want to allow any other activity to have a start or end time that overlaps with either of these times.

I plan to take these inputs and insert them into the database. I have written the code to format the times correctly but to not complicated things I have left this out, this assumes the time formats are correct. Can anyone point me in the direction of how I can prevent an overlap of times captured in the database.

Any help appreciated.






<?php

foreach($_POST['startTime'] as $k => $startTime) {
$endTime = $_POST['endTime'][$k];

**** I dont know how to compare the value combinations $startTime - $endtime

}
?>
<form action="test.php" method="post">

Activity 1 Start <input type="text" name="startTime[]" /> End <input type="text" name="endTime[]" /> <br /><br />
Activity 2 Start <input type="text" name="startTime[]" /> End <input type="text" name="endTime[]" /> <br /><br />
Activity 3 Start <input type="text" name="startTime[]" /> End <input type="text" name="endTime[]" /> <br /><br />
Activity 4 Start <input type="text" name="startTime[]" /> End <input type="text" name="endTime[]" /> <br /><br />
<input type="hidden" name="test" value="true" />
<input type="submit" name="submit" value="Go" />


</form>
 
To add, the amount of activities can fluctuate to. Hope this makes sense any help appreciated. thank you
 
Hi

Put them all in a single array as date - type pairs, like this :
Code:
array(
  array('2013-01-01','start'),
  array('2013-02-02','end'),
  array('2013-03-03','start'),
  array('2013-04-04','end'),
)
Then sort the array on the items' 1[sup]st[/sup] then 2[sup]nd[/sup] elements. ( 'end' will get before 'start' due to their alphabetic order, so if there is a starting and an ending event in the same time, the ending one will appear first. )
At the end just check the array : if start and end times alternate, there is no overlap; if you find two or more consecutive start times, there are overlaps.


Feherke.
feherke.github.io
 
i have not tested the below, and see that feher has since posted, but for what it is worth ...
Code:
<?php


class timeOverlapChecker{
	
	/**
	 * convert the arrays into a single array
	 * @param $startTimes
	 * @param $endTimes
	 * @return unknown_type
	 */
	public function __construct($startTimes, $endTimes){
		if(!date_default_timezone_get()) date_default_timezone_set('UTC');//prevent errors
		$dates = array();
		foreach($startTimes as $key=>$start):
			$dates[] = array('start'=>$this->cleanse($start), 'end'=>$this->cleanse($endTimes[$key]));
		endforeach;
		$this->dates = $dates;
	}
	
	/**
	 * transform a time string into a unix time stamp
	 *
	 */
	private function cleanse($date){
		return strtotime("$date today");
	}
	
	/**
	 * perform the overlap test
	 
	 */
	public function test(){
		$overlaps = array();
		foreach($this->dates as $key=>$date):
			foreach($this->dates as $_key=>$_date):
				if($_key <= $key) continue; //don't duplicate unnecessarily
				if($this->overlaps($date['start'], $date['end'], $_date['start'], $_date['end'])):
					$overlaps[] = array($_key, $key);
				endif;
			endforeach;
		endforeach;
		return count($overlaps > 0)?$overlaps:false;
	}
	
	private function overlaps($start1, $end1, $start2, $end2){
		/*
		 * ensure that time pairs are the right way around
		 */
		for($i=1; $i<=2; $i++):
			$array = array($start{$i}, $end{$i});
			sort($array);
			list($start{$i},$end{$i}) = $array;
		endfor;
		if($start2 > $start1 && $start2 < $end1) return true; 
		if($end2 > $start1 && $end2 < $end1) return true;
		return false;
	}
}

$test = new timeoverlapchecker($_POST['startTimes'], $_POST['endTimes']);
$result = $test->test();
if($result == false):
	echo 'no overlaps';
else:
	echo 'the following date pairs overlap<br/>';
	print_r($result);
endif;
	
?>
 
Hi thanks for the replies

Below is the function I created for inserting data into the table, I excluded this as I did not want to complicate things. I just wanted to somehow create something that compared the times for overlaps. The code I have written does not have have any ability to check for overlaps in times. Jpadie I tried the class you developed but it has not worked. I just wish there was an easy way of doing this.

feherke, thanks I am still trying to work out your suggestion. Arrays are not my strong point.

function updateEntries() {

### GRAB FORM SUBMITS ###
$startTime = $_POST['startTime'];
$endTime = $_POST['endTime'];
$comments = $_POST['comments'];
$timesheet_id = $_POST['timesheet_id'];

### USE THE STARTTIME TO WORK THROUGH EACH ENTRY
foreach($timesheet_id as $key => $value) {


$startTimeStamp = "$startTime[$key]";
$endTimeStamp = "$endTime[$key]";

###################################################################################################################################################
###*********
### CHECK IF THE STARTTIME OR END TIME VALUES ARE EQUAL TO 5 CHARACTERS
if (((strlen($startTimeStamp) == 5) && (@ereg('[0-9]{2}[:]{1}[0-9]{2}',$startTimeStamp))) || ((strlen($startTimeStamp) == 4) && (@ereg('[0-9]{4}',$startTimeStamp)))){

$starthr = substr($startTimeStamp,0,2);
$startmin = substr($startTimeStamp,-2);

} else {

$starthr = "00";
$startmin = "00";
}


if (((strlen($endTimeStamp) == 5) && (@ereg('[0-9]{2}[:]{1}[0-9]{2}',$endTimeStamp))) || ((strlen($endTimeStamp) == 4) && (@ereg('[0-9]{4}',$endTimeStamp)))) {


$endhr = substr($endTimeStamp,0,2);
$endmin = substr($endTimeStamp,-2);
} else {

$endhr = "00";
$endmin = "00";
}



$startTimeStamp = @date('Y-m-d'.' '.$starthr.':'.$startmin.':00');
$endTimeStamp = @date('Y-m-d'.' '.$endhr.':'.$endmin.':00');

if($startTimeStamp > $endTimeStamp) {
$endTimeStamp = '0000-00-00 00:00:00';
}


$query = "update timesheet set startTime = '$startTimeStamp', endTime = '$endTimeStamp', comments = '$comments[$key]' where timesheet_id = {$timesheet_id[$key]}";
//echo $query;
$result = mysql_query($query);
}
$_SESSION['entryStatus'] = '';
$_SESSION['incompleteDate'] = '';

}
 
there was an easy way of doing this

there is. my code may not work because of syntax errors or bugs (which I am sure you can fix) but it is an entirely valid approach.

If you would like help debugging the code that I wrote, can you post back with what errors you get and a dump of the $_POST superglobal in each case.
 
Hi Jpadie I really appreciate the help you give. I have never used a class before which makes it hard for me. I know this is something I need to learn and I will work through your class until I understand it. I get the just of it. Thanks again and I will update the forum as soon as I have worked it out.
 
OK.

just run the code as it is and post the (against your form code) and post the results here.
 
Hi Jpadie

Here are the results:

Notice: Undefined index: startTimes in /site/ on line 60 Notice: Undefined index: endTimes in /site/ on line 60 Warning: date_default_timezone_get(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected 'Europe/London' for 'BST/1.0/DST' instead in /site/ on line 12 Warning: Invalid argument supplied for foreach() in /site/ on line 14 no overlaps

I would just like to add that there could be more or sometimes less than 4 activities with start and end times. An example of what the user will enter is:
Activity 1 Start Time 1024 End Time 1105
Activity 2 Start Time 1115 End Time 1200
and so on

So my objective is to make sure there is no over lap before info is stored in the databbase. ie Activity 2 could not have a start time of 1104

Thanks for the help.
 
1. I think that you call your controls startTime and endTime (missing 's'). so change accordingly in the code or your controls as you wish.
Code:
$test = new timeoverlapchecker($_POST['startTime[s]s[/s]'], $_POST['endTime[s]s[/s]']);
2. Warning: date_default_timezone_get(). I misremembered this function. instead modify the line like this
Code:
[s]if(!date_default_timezone_get())[/s] date_default_timezone_set('UTC');//prevent errors
3. my code can deal with an arbitrarily large number of start/end times.

do post back with any other errors.

the above presupposes that you are in the UTC time zone (which of course you are not). However it does not matter as you are not trying to get absolute times (pinned to a date and region) but a comparison of times. If you are wanting absolute times then you need to take account not just of the region of the server but also the region of the database server and the region of the browser.
 
here is a version of the above that does not use classes. test script is also included

Code:
<?php

function checkTimeOverlap($startTimes, $endTimes){
	/* ensure that a default time zone is set */
	date_default_timezone_set('UTC');//prevent errors
	
	/* transform the two arrays into a single multi-dimensional array */
	$dates = array();
	foreach($startTimes as $key=>$start):
		$tempdate = array(	'start'=>strtotime($start),
					'end'=>strtotime($endTimes[$key])
		);
		/* ensure that end is after start */
		if($tempdate['end'] < $tempdate['start']):
			sort($tempdate);
			list($tempdate['start'], $tempdate['end']) = $tempdate;
		endif;
		$dates[] = $tempdate;
	endforeach;
	
	/* check to see whether a time range overlaps */
	$overlaps = array();
	$_dates = $dates;
	/* outer loop */
	foreach($dates as $key=>$date):
		/* inner loop */
		foreach($_dates as $_key=>$_date):
			if($_key <= $key) continue; //don't duplicate unnecessarily

			/* test for overlap */
			if( $_date['start'] > $date['end'] || $_date['end'] < $date['start']):
				
			else: 
				$overlaps[] = array($key, $_key);
			endif;
			/* finish overlap test */
			
		endforeach;
	endforeach;
	
	if(count($overlaps) ===  0) return false;
	return $overlaps;
}

/* no overlap */
$startTimes = array('11:45','14:00', '16:00');
$endTimes = array('12:00', '15:00', '16:45');

/* run the test */
$test = checkTimeOverlap($startTimes, $endTimes);
if($test === false):
	echo 'no overlaps';
else:
	echo 'the following date pairs overlap<br/>';
	foreach ($test as $overlap):
		echo <<<TXT

Range {$startTimes[$overlap[0]]} - {$endTimes[$overlap[0]]}
overlaps with
Range {$startTimes[$overlap[1]]} - {$endTimes[$overlap[1]]}

TXT;
		endforeach;
endif;

echo "\n";

/* overlap */
$startTimes = array('11:45','14:00', '16:00', '10:30');
$endTimes = array('14:10', '15:00', '16:45', '11:50');

/* run the test */
$test = checkTimeOverlap($startTimes, $endTimes);
if($test == false):
	echo 'no overlaps';
else:
	echo 'the following date pairs overlap<br/>';
	foreach ($test as $overlap):
		echo <<<TXT

Range {$startTimes[$overlap[0]]} - {$endTimes[$overlap[0]]}
overlaps with
Range {$startTimes[$overlap[1]]} - {$endTimes[$overlap[1]]}

TXT;
		endforeach;
endif;

echo "\n";
?>

and an implementation of feherke's approach
Code:
<?php
function feherTest($startTimes, $endTimes){

	date_default_timezone_set("UTC");
	/* merge the two arrays into one */
	$all = array_merge($startTimes, $endTimes);

	/* convert each element to a time stamp */
	foreach(array('all', 'startTimes', 'endTimes') as $a) $$a = array_map('strtotime', $$a);

	/* sort the combined array low to high */
	sort($all);
	
	/* instantiate a counter */
	$i = 0;
	/* walk through the combined array and make sure that it compares nicely with the post values */
	while ($i < count ($all) ):
		$key = $i/2;
		if( $all[$i] != $startTimes[$key]) return false;
		$i++;
		if($all[$i] != $endTimes[$key]) return false;
		$i++;
	endwhile;
	return true;
}

/* no overlap */
$startTimes = array('11:45','14:00', '16:00');
$endTimes = array('12:00', '15:00', '16:45');

echo feherTest($startTimes, $endTimes) === true ? 'no overlap' : 'overlap';
echo "\n";


/* overlap */
$startTimes = array('11:45','14:00', '16:00');
$endTimes = array('14:10', '15:00', '16:45');
echo feherTest($startTimes, $endTimes) === true ? 'no overlap' : 'overlap';
echo "\n";

Feher's code suggestion is (of course) very neat. However I cannot see a way for it to return the information about the offending ranges. I probably could if I gave it more thought and less wine.

but for now you have:
a. a slightly inelegant but fast single function for comparing ranges, that will return an array of offending keys that can be used to inform the user which ranges are overlapping.
b. a very elegant (for some reason not quite so fast) single function for comparing ranges that will return a boolean true or false as to whether ranges overlap at all.

both sets of code work with either full date time references or just time references. but a mixture of the two might not work the way you expected (php will timestamp a time reference as at today's date). time references alone will not cater for events which go past midnight. so you might want to look at that and use something like a javascript date and time picker. You could use such a gadget and also exclude ranges already chosen by the user in previous selections. This might be neat from a client-side validation perspective, but does not remove the need for revalidating at the server side.

I need to investigate why Feher's code approach works slower than mine. There is nothing obvious that suggests it should. In fact, because his code bails out when an overlap is hit, it should always be quicker (on average). Ho hum...

Might this all be for a time sheet application? If so, do consider that in most cases it is legitimate for time blocks to overlap each other. For example, law firms record time in 5 minute blocks (or whatever). if you stop a task mid-block and start up straight away, then that block is recorded twice. typically this does not matter as it is unlikely you go from one chargeable task to another, more likely you go from a chargeable task to an admin task: but there are exceptions (phone calls interrupting you etc).

In the old days, simply both blocks of time were recorded and quite possibly billed. However I did hear of a US lawyer that wrote an article on a case and then mailed it to a bunch of clients, billing each client a single unit of time. He ended up billing 100 hours or so in a single day and I recall that the regulator considered that was somewhat abusive ....

 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top