Jump to content

check dates from array dont fall between 2 new dates, code not working...


rich_traff

Recommended Posts

Hi, can anyone tell me whats wrong with this code?

 

i have an array of arrays that contain start and end dates of bookings as shown here taken from mysql;

 

Array ( [0] => Array ( [0] => 2011-05-26 [1] => 2011-05-28 ) [1] => Array ( [0] => 2011-05-28 [1] => 2011-05-30 ) [2] => Array ( [0] => 2011-06-13 [1] => 2011-06-24 ) )

 

ie;

booking 1: start = 2011-05-26, end = 2011-05-28

booking 2: start = 2011-05-28, end = 2011-05-30

booking 3: start = 2011-06-13, end = 2011-06-24

 

i then have a new start_date and end_date and i need to check that none of the dates from the array fall between the 2 new dates, new dates being unix timestamp.

 

$start_date // unix timestamp
$end_date // unix timestamp

$checkDates // array (as above)

$dateUnavailable = 0;

			foreach( $checkDates as $key => $date)
			{
				if (strtotime($date) > $start_date && strtotime($date) < $end_date);
				{
					$dateUnavailable = 1;
				}

			}

 

no matter what dates are used, $dateUnavailable is always set to 1...

 

Can anyone tell me what im doing wrong here or suggest a better solution that works?

Link to comment
Share on other sites

I see a couple of problems.

 

First, since you have a multidimensional array, in your foreach() loop the '$date' value is an array. So, you are trying to compare an array against the start/end dates. You need to compare the two date value in the '$date' array against the start/end dates.

 

Second, your logic of comparing the dates will not work. You can't compare the two dates individually against both the start and end dates. The logic you have will always show that there is a conflict unless the dates of the record exactly equal the start/end target dates. And, I think that should be considered a conflict as well. Think about it for a moment. If you have a start/end dates of Feb 1 to Feb 28. If you have a record for Jan1 to Jan 31, both the dates are less than the end target date, which would not be a conflict, but the logic above would make it seem so.

 

This should be what you want. It may seem counter intuitive since you are comparing the record start date with the target end date and the record end date against the target start date. But, trust me it is correct. It will validate that the record time period does not overlap the time period of the target.

$dateUnavailable = false;
foreach( $checkDates as $key => $dates)
{
    //Create variables for rrecord start/end dates
    list($recStart, $recEnd) = $dates;
    if (strtotime($recStart) < $end_date && strtotime($recEnd) < $start_date);
    {
        $dateUnavailable = true;
        break;
    }
}

Link to comment
Share on other sites

Hi, im afraid that hasn't solved the problem, $dateUnavailable is still being set to true no matter what…

 

Firstly

First, since you have a multidimensional array, in your foreach() loop the '$date' value is an array. So, you are trying to compare an array against the start/end dates. You need to compare the two date value in the '$date' array against the start/end dates.

 

Thanks for pointing that out

 

If you have a start/end dates of Feb 1 to Feb 28. If you have a record for Jan1 to Jan 31, both the dates are less than the end target date, which would not be a conflict, but the logic above would make it seem so.

Im not sure i follow here… Both record dates are less than the target end date, but neither of them are greater than the target start date so surely the if statement in my original code would not be true

 

if (strtotime($recStart) < $end_date && strtotime($recEnd) < $start_date);

 

Sorry but i don't think this is correct as it would not stop someone selecting new dates that fall either side of the record dates…

 

Perhaps i've not been very clear in my original post, to reiterate the problem…

 

I have a calendar that lets a user book out cars by selecting a start and end date for the booking. It currently shows all the days the car is already booked out on and will not let you select those days. There is nothing to stop someone selecting a start date before one booking and an end date after it though which would go through another booking, which is what i need to stop happening.

 

ie - there is a booking for the 7th-8th, i need to stop someone selecting the 6th and 9th as start/end dates and creating a booking that overlaps the first...

Link to comment
Share on other sites

The logic of the code I provided is valid. But, I think I misunderstood which dates were which. If the dates in the $checkDates array are the already booked dates and the $start_date & $end_date are the proposed dates for a new booking, then you just need to reverse the logic in the code.

$dateUnavailable = false;
foreach($checkDates as $key => $dates)
{
    //Create variables for rrecord start/end dates
    list($bookedStart, $bookedEnd) = $dates;
    if ($start_date < strtotime($bookedEnd) && $end_date > strtotime($bookedStart));
    {
        $dateUnavailable = true;
        break;
    }
}

 

If you still get invalid results provide the start/end dates and the $checkDates array.

 

As a visual example, below are the various combinations of how a booked date range and various requested date ranges. If you look at all the "invalid" date ranges (the requested start date is before the booked end date) AND (the requested end date is before the booked start date)

 

    3-----6      <== Booked Date Range

---------------- <== Valid Request date ranges
1-2
1---3
          6---8
            7-8

---------------- <== Invalid Request date ranges
  2---4
    3-4
      4-5
        5-6
        5---7
  2---------7

Link to comment
Share on other sites

apologies if i wasn't clear which dates were which.

 

Im still getting invalid results though... I've been checking through the rest of my code to make sure there are no errors before getting to this point and cant find any...

 

Here is a working example of the dates;

 

Array of already booked dates:

Array ( [0] => Array ( [0] => 07/20/2011 [1] => 07/21/2011 ) [1] => Array ( [0] => 08/16/2011 [1] => 08/17/2011 ) )

 

dates selected by user:

start date: 1312844400, end date: 1312930800  // ok - should set $dateUnavailable to false

start date: 1313362800, end date: 1313622000  // not aloud - should set $dateUnavailable to true

 

currently, all date entries are resulting in $dateUnavailable being set to true

 

Link to comment
Share on other sites

OK, I have been "assuming" that the array you are posting is not formatted as it REALLY is. You should do a print_r() and post the result of that. Here is what I assume is the true format of your array:

Array (
    [0] => array(
        [0] => 07/20/2011
        [1] => 07/21/2011
        )
    [1] => Array (
        [0] => 08/16/2011
        [1] => 08/17/2011
        )
    ) 

 

Also, as my signature states I do not always test the code. If I am confident in the logic I leave it to the person I am providing the code for to do any debugging of syntax errors. In this case, the error did not produce a code failure. There was an errant semi-colon at the end of the IF statement. During my debugging I transformed the script into a function. The revised "tested" code is below. However, if the booked dates are coming from a database you should just do a single query against the database to see if there is a conflict rather than querying the database for all the records and then checking the requested dates. I would have to know the format of the data in the database to provide a query.

 

Revised script

//Returns true/false based upon whether the requested dates conflict
//with the already booked dates in the $bookedDates array
function datesAvailable($bookedDates, $start_date, $end_date)
{
    $datesAvailable = true;
    foreach($bookedDates as $key => $dates)
    {
        //Create variables for rrecord start/end dates
        list($bookedStart, $bookedEnd) = $dates;
        if ($start_date < strtotime($bookedEnd) && $end_date > strtotime($bookedStart))
        {
            return false;
        }
    }
    return true;
}

//Array of already booked dates
$checkDates = array(
   array ('07/20/2011', '07/21/2011'),
   array ('08/16/2011', '08/17/2011')
);

$available = datesAvailable($checkDates, 1312844400, 1312930800); //returns true
$available = datesAvailable($checkDates, 1313362800, 1313622000; //returns false

Link to comment
Share on other sites

I think best way would be to have start and end date fields in MySQL and then just use a SQL query to select the dates between or outside that pair of dates.

 

I wish I had though of that. Oh wait, I did.

However, if the booked dates are coming from a database you should just do a single query against the database to see if there is a conflict
Link to comment
Share on other sites

@mjdamato, ok - i understand your logic now and appreciate it is sound.

 

What i have found is there is a problem with my array itself… After doing print_r it displayed '1' and nothing else… I should have done this first as i was copying the results of the query from another function elsewhere in the script (although it is the exact same query)

 

Even when i insert the array manually though, it still shows '1'

 

This is the exact code i am using right now…

(it is a joomla component btw)

 

$car_id = JRequest::getInt('id', '');

/*
$db->setQuery("SELECT start_date, end_date FROM #__carbooking_bookings WHERE car_id = '$car_id'");
$bookedDates = $db->loadRowList();
*/

$bookedDates = array(
   		array ('07/20/2011', '07/21/2011'),
   		array ('08/16/2011', '08/17/2011')
	);

	$datesUnavailable = 0;
	foreach($bookedDates as $key => $dates)
	{
	//Create variables for rrecord start/end dates
	list($bookedStart, $bookedEnd) = $dates;
		if ($start_date_unixTime < strtotime($bookedEnd) && $end_date_unixTime > strtotime($bookedStart))
		{
		$datesUnavailable = 1;
		}
	}

	if ($datesUnavailable = 1)
	{
		$this->setRedirect(JRoute::_('index.php?option=com_carbooking&id=' . $row->id . '&view=single'), 'Your selection falls over another booking, dates Unavailable: ' . $datesUnavailable . ', start date: ' . $start_date_unixTime . ', end date: ' . $end_date_unixTime . ', array: ' . print_r($bookedDates) . ', car id: ' . $car_id);
	}

 

The message displayed on redirect ie from this

 

$this->setRedirect(JRoute::_('index.php?option=com_carbooking&id=' . $row->id . '&view=single'), 'Your selection falls over another booking, dates Unavailable: ' . $datesUnavailable . ', start date: ' . $start_date_unixTime . ', end date: ' . $end_date_unixTime . ', array: ' . print_r($bookedDates) . ', car id: ' . $car_id)

 

gives exactly this;

Your selection falls over another booking, dates Unavailable: 1, start date: 1312844400, end date: 1312930800, array: 1, car id: 1

 

Showing $datesUnavailable is being set to 1

the start and end dates are formatted correctly

the array however, instead of being this;

 

Array ( [0] => Array ( [0] => 07/20/2011 [1] => 07/21/2011 ) [1] => Array ( [0] => 08/16/2011 [1] => 08/17/2011 ) ) 

 

is this;

 

1

 

do you know why this might be happening?

Link to comment
Share on other sites

OK, I have been "assuming" that the array you are posting is not formatted as it REALLY is. You should do a print_r() and post the result of that. Here is what I assume is the true format of your array:

 

Yes, you were correct in your assumption... at least you were correct in assuming that i assumed the array would be formatted that way... turns out i should have checked more thoroughly...

Link to comment
Share on other sites

You can't do a print_() inside an echo statement (I have made the same mistake before). You would have to assign the results of a print_r to a variable then echo out the variable.

 

Since I did test the last code I provided, I am going to suggest you use that code. Also, it is more logical. Having a variable called $datesUnavailable and setting it to false to mean that dates are available is the programmatical equivalent of a double negative. It can be confusing. That is why I had changed the logic to determine in "datesavailable" is true. Below is a rewrite of your script above with my latest function incorporated with LOTS of debugging code to help pinpoint any issues if the results are not what you expect.

 

As you have done previously, the script currently has a hard-coded $bookedDates array defined in the datesAvailable() function. Once that works, you can remove the hard-coded array and uncomment the query to get the booked dates from the DB. I modified the function to take a parameter for the card ID and to run the query for booked dates in the function. If I didn't make any errors it should work once you uncomment the line to run the query.

 

<?php

//Returns true/false based upon whether the requested dates conflict
//with the already booked dates in the DB for the requested car id
function datesAvailable($car_id, $start_date_ts, $end_date_ts)
{
    /*
    $query = "SELECT start_date, end_date FROM #__carbooking_bookings WHERE car_id = '$car_id'";
    $db->setQuery($query);
    $bookedDates = $db->loadRowList();
    */

    //Hard coded test array
    $bookedDates = array(
   		array ('07/20/2011', '07/21/2011'),
   		array ('08/16/2011', '08/17/2011')
	);
#DEBUG
echo "Booked dates array contains " . count($bookedDates) . " records<br><br>\n";
echo "Checking availabliity from " . date('m-d-Y', $start_date_ts) . " to " .  date('m-d-Y', $end_date_ts) . "<br><br>\n";

    foreach($bookedDates as $key => $dates)
    {
#DEBUG
echo "Checking booked dates of {$dates[0]} to {$dates[0]} - ";
        //Create variables for rrecord start/end dates
        list($bookedStart, $bookedEnd) = $dates;
        if ($start_date_ts < strtotime($bookedEnd) && $end_date_ts > strtotime($bookedStart))
        {
            //Conflicts detected, return false
#DEBUG
echo "CONFLICT, Return FALSE<br>\n";
            return false;
        }
    }
#DEBUG
echo "No Conflicts, Return TRUE<br>\n";
    //No conflicts detected, return true
    return true;
} //End function datesAvailable()


$car_id = JRequest::getInt('id', '');
if (!datesAvailable($car_id, $start_date_unixTime, $end_date_unixTime))
{
    $redir_url = 'index.php?option=com_carbooking&id=' . $row->id . '&view=single';
    $redir_msg = "Your selection falls over another booking, dates Unavailable: start date: {$start_date_unixTime}, end date: {$end_date_unixTime}, car id: {$car_id}";
    $this->setRedirect(JRoute::_($redir_ur), $redir_msg);
}

?>

Link to comment
Share on other sites

ok, i think this is nearly there but have another issue which i think is just due to my inexperience using functions and not really knowing how to use them together properly…

 

this code needs to be part of a larger function that checks a few things before returning a final output. I've taken the last rewritten function you provided and tested it on its own and it works no problems (the debugging helped me understand more so thanks for that)

 

Im not really sure how to include functions within other functions though (and got errors whilst trying) so used an include. When i remove the redirects (from the controller file) your code echo's out everything you expect it to, correct date, returns true or false correctly etc.

 

When i try to take the return value and base an if statement on it though (in the controller) it is always set to true…

 

This is the controller file function

 

<?php
function points_spend()
{
// does stuff here

$car_id = JRequest::getInt('id', '');
include 'bookings_overlap.php';

if ( $datesAvailable = true)
{
$this->setRedirect(JRoute::_('index.php?option=com_carbooking&id=' . $row->id . '&view=single'), 'The dates are true' . $datesAvailable);
}
elseif ( $datesAvailable = false)
{
$this->setRedirect(JRoute::_('index.php?option=com_carbooking&id=' . $row->id . '&view=single'), 'The dates are false' . $datesAvailable);
}

// does more stuff here
}
?>

 

and the included file

 

<?php

//Returns true/false based upon whether the requested dates conflict
//with the already booked dates in the DB for the requested car id
function datesAvailable($car_id, $start_date_unixTime, $end_date_unixTime)
{
    /*
    $query = "SELECT start_date, end_date FROM #__carbooking_bookings WHERE car_id = '$car_id'";
    $db->setQuery($query);
    $bookedDates = $db->loadRowList();
    */

    //Hard coded test array
    $bookedDates = array(
   		array ('07/20/2011', '07/21/2011'),
   		array ('08/16/2011', '08/17/2011')
	);

    foreach($bookedDates as $key => $dates)
    {
        list($bookedStart, $bookedEnd) = $dates;
        if ($start_date_unixTime < strtotime($bookedEnd) && $end_date_unixTime > strtotime($bookedStart))
        {
            //Conflicts detected, return false
            return false;
        }
    }
    //No conflicts detected, return true
    return true;
} 

$datesAvailable = datesAvailable($car_id, $start_date_unixTime, $end_date_unixTime);
?>

 

Can you tell me where im still going wrong with this?

 

btw - i fully appreciate your time spent on this, i know i've already asked a lot...

Link to comment
Share on other sites

@rich_traff

 

Kinda difficult to debug your problem without seeing it all together, but I'll give it a go. In your controller "function" you have an if() condition as follows:

if ( $datesAvailable = true)

 

First off, if the value of $datesAvailable will be true or false, you don't need "test" if it is equal to true. That is redundant. Just use

if ( $datesAvailable)

 

But, the problems with what you have is 1) You are ASSIGNING the value of true to the variable $datesAvailable not testing it (which would be corrected by what I posted just above). 2) I don't see where you ever assigned a value to $datesAvailable before that if() condition is run. Variables created outside a function are not available inside a function. It is called variable scope. You can code around this, but it is bad practice.

 

What I thin you should be doing is simply calling the datesAvailable() function INSIDE the if() statement. But, to do so you need to pass the required parameters. I only see the car_id defined in the function; you also need the start/end dates.

 

Example:

if ( datesAvailable($car_id, $startDate, $endDate))

So, you would need to pass the start/end dates to the function you have.

 

Alternatively, you could define the $datesAvailable outside the function and then pass that to your function.

Link to comment
Share on other sites

@mjdamato, thanks so much for your help on this

 

setting the if statement to this corrected the problem

 

if ( datesAvailable($car_id, $start_date_unixTime, $end_date_unixTime))

 

Now, theres just one other thing, if you would be so kind...

 

You mentioned the most efficient solution would be to check for a conflict in the db with the query itself, how would i go about that?

 

Link to comment
Share on other sites

You mentioned the most efficient solution would be to check for a conflict in the db with the query itself, how would i go about that?

 

Seriously? After all this work you now want to change the solution? I'm not going to write any code, but I'll give you some info to do it yourself:

 

Instead of querying for ALL the records for the specified car ID, you would instead do a query for ONLY the records that conflict with the requested dates. Then check how many records were returned using mysql_num_rows(). If 0, there are no conflicts, if there are 1 or more, then there are conflicts. That way you don't need to process the DB results, just check how many results were returned. You will need to format the dates appropriately, but the query would look something like this.

 

SELECT start_date
FROM #__carbooking_bookings
WHERE car_id = '$car_id'
  AND end_date > $requested_start_date
  AND start_date < $requested_end_date
LIMIT 1

Link to comment
Share on other sites

Seriously? After all this work you now want to change the solution?

 

Apologies… i didn't think that through before making my last post. I had in my head it would just be the query that would change, but the function would remain, clearly not so…

 

You've given me more than enough help on this though and i really do appreciate it!

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.