How to identify dates that intersect x times within ranges in php? - php

We are looking to create a list of comma separated dates that tell us when a reservation is full. There are 7 units to rent so we want to know which dates are present >= 7
This Stackoverflow thread is close as it identifies intersections but I am looking for the specific dates where they intersect x amount of times.
<?php
// 2019-2-21 is present 8 times in the following array
$ranges = array(
array('id' =>'59','start' => new DateTime('2019-02-19'), 'end' => new DateTime('2019-02-21')),
array('id' =>'58','start' => new DateTime('2019-02-19'), 'end' => new DateTime('2019-02-21')),
array('id' =>'55','start' => new DateTime('2019-02-19'), 'end' => new DateTime('2019-02-21')),
array('id' =>'57','start' => new DateTime('2019-02-19'), 'end' => new DateTime('2019-02-21')),
array('id' =>'108','start' => new DateTime('2019-02-21'), 'end' => new DateTime('2019-02-28')),
array('id' =>'109','start' => new DateTime('2019-02-19'), 'end' => new DateTime('2019-02-24')),
array('id' =>'110','start' => new DateTime('2019-02-21'), 'end' => new DateTime('2019-02-23')),
array('id' =>'111','start' => new DateTime('2019-02-21'), 'end' => new DateTime('2019-02-25')),
);
function intersects($lhs, $rhs) {
return !($lhs['start'] > $rhs['end'] || $lhs['end'] < $rhs['start']);
}
function checkDates($ranges) {
// Comparison loop
for($i = 0; $i < sizeof($ranges); $i++) {
for($j = $i+1; $j < sizeof($ranges); $j++) {
if(intersects($ranges[$i], $ranges[$j])) {
echo "Date {$i} intersects with date {$j}<br>";
}
}
}
}
checkDates($ranges);
?>
I'm able to identify on a known specific date when we are over the limit
SELECT COUNT(*) FROM reservations
WHERE reservations.`date` <= '$date' AND reservations.`dateLast` >= '$date'
This gives us a count that we can compare to our qty of units but I'm not sure how to create a list of dates that intersect >= x so we can know in advance if we are sold out.
UPDATE to confirm solution:
foreach ($ranges as $range) {
while ($range['start'] <= $range['end']) {
$date = $range['start']->format('Y-m-d');
$dates[$date] = (isset($dates[$date]) ? $dates[$date] : 0) + 1; 1;//define new $dates array
$range['start']->modify('+1 day');
}
}
echo $sold_out = array_filter($dates, function($n) { return $n >= 7; });
echo '<pre>';
print_r($range);
echo '</pre>';

I think you don't need to intersect the ranges. You just need to know how many times each date appears in your list of ranges, so you can just iterate each range in ranges and count the dates.
foreach ($ranges as $range) {
while ($range['start'] <= $range['end']) {
$date = $range['start']->format('Y-m-d');
$dates[$date] = ($dates[$date] ?? 0) + 1;
// or $dates[$date] = (isset($dates[$date]) ? $dates[$date] : 0) + 1;
$range['start']->modify('+1 day');
}
}
/* Result:
array (size=10)
'2019-02-19' => int 5
'2019-02-20' => int 5
'2019-02-21' => int 8
'2019-02-22' => int 4 ...
*/
Then you can filter that to find any sold out dates.
$sold_out = array_filter($dates, function($n) { return $n >= 7; });
I think you can probably also do this in SQL by creating a temporary table with all dates in the date range you're interested in and joining it to your count query.

Related

how to stop adding more data in array in foreach loop

i looping 2 array a date and the data
$rdate = $request->month; //<-- Requested month
$days = Carbon::create();
$days = $days->daysInMonth; //<-- Finding how much days in month
for ($i = 1; $i < $days; $i++) { //Looping Date
$date = $rdate . '-' . $i; //Making full Date with formate 'Y-m-d'
foreach ($games as $game) { //Looping game array have 3 collection ['game1', 'game2', 'game3']
$result = Result::whereDate('created_at', $date)->where('game', $game->name)->where('admin_id', $admin->id)->first(); // Query Results for getting perticular game with date
if ($result) { //Checking if current date haven't result
$r[] = [
'game' => $game->name,
'number' => $result->number
];
} else {
$r[] = [
'game' => $game->name,
'number' => '-'
];
}
}
$resultd[] = [
'date' => $date,
'results' => $r // i want to stop adding old data here
];
}
i am expecting this result
{"results":[{"date":"2020-08-1","results":[{"game":"game1","number":"-"},{"game":"game2","number":"-"},{"game":"game3","number":"-"}]},{"date":"2020-08-2","results":[{"game":"game1","number":"-"},{"game":"game2","number":"-"},{"game":"game3","number":"-"}]
What actually getting
Adding old $r to results array
How to fix it i am trying to break it but can't figure it out
I have some untested code, as I do not have your data but I think this code below should work, you may need to modify some code as needed.
$rdate = '2020-8';
$days = collect([1,2,3]);
$games = collect(['game1', 'game2', 'game3']);
return $days->map(function ($item) use ($rdate,$games) {
$date = $rdate.'-'.$item;
return [
"date" => $date,
"results" => $games->map(function ($g) use ($date) {
$result = Result::whereDate('created_at', $date)
->where('game', $g->name)
->where('admin_id', $admin->id)
->first();
return ['game' => $g->name,
'number' => $result ? $result->number : '-',
];
})
];
});

Check season of year in php

I want to print a different image every season of the year. Based on a date from my database. I want to print it in php/html5.
Found some things on the internet but none of them worked with my database date.
Just need to format the values o match yours
$someDay = "2018-12-01";
$spring = (new DateTime('March 20'))->format("Y-m-d");
$summer = (new DateTime('June 20'))->format("Y-m-d");
$fall = (new DateTime('September 22'))->format("Y-m-d");
$winter = (new DateTime('December 21'))->format("Y-m-d");
switch(true) {
case $someDay >= $spring && $someDay < $summer:
echo 'It\'s Spring!';
break;
case $someDay >= $summer && $someDay < $fall:
echo 'It\'s Summer!';
break;
case $someDay >= $fall && $someDay < $winter:
echo 'It\'s Fall!';
break;
default:
echo 'It must be Winter!';
}
Try it online!
Based on year of defined date and some leap year logic
$current_date = new DateTimeImmutable();
# $current_date = $current_date->setDate(2023,3,1); // test
$seasons = [
(object) ['name' => 'winter', 'date' => $current_date->setDate($current_date->format('Y'),1,1)],
(object) ['name' => 'spring', 'date' => $current_date->setDate($current_date->format('Y'),2,($current_date->format('Y') % 4 === 0) ? 19 : 20)],
(object) ['name' => 'summer', 'date' => $current_date->setDate($current_date->format('Y'),5,($current_date->format('Y') % 4 === 0) ? 20 : 21)],
(object) ['name' => 'autumn', 'date' => $current_date->setDate($current_date->format('Y'),8,($current_date->format('Y') % 4 === 0) ? 22 : 23)],
(object) ['name' => 'winter', 'date' => $current_date->setDate($current_date->format('Y'),11,($current_date->format('Y') % 4 === 0) ? 20 : 21)]
];
$season = array_slice(array_filter($seasons, fn($season) => $season->date <= $current_date),-1)[0]->name;
echo $season;
If your seasons are variable, and stored in the database, you can fix your problem by simply
Splitting the season that intersects newyear
Set all yearstamps to 2000
Do a a < b < c check
Splitting the season elïminates your problem straightaway
Here's a .NET implementation
Here's a JSfiddle

How To Remove Duplicate Elements from array after merging with another array in php?

I am trying to Write Program for Calculating Next 20 dates After Specifying Start date, then from 20 dates i have Exclude Weekends & Holidays(Array holidays('2016-12-13',2016-12-24)) And Result Array which includes only Working Days Excluding Saturday & Sunday, from this Result Array after Passing Holiday array(Eg:- holidays('2016-12-13',2016-12-24))), it must be Excluded from result array. i:e;
I want Expected Output Below mentioned
.
<?php
$Date=array('2016-12-01');
echo "\n <br />Start Date:-" . $Date[0] . "";
/*Code For Generating Next 20 Dates Starts*/
//$start = strtotime($s_row['schedule_start_date']);
$start = strtotime('2016-12-01');
$dates=array();
for($i = 0; $i<20; $i++)
{
array_push($dates,date('Y-m-d', strtotime("+$i day", $start)));
}
echo "\n <br /> Array Of next 20 Days/dates of Given:-";
print_r($dates);
$start=array();
$start=$dates; /*Code For Generating Next 20 Dates Ends*/
$result=array();
$start = strtotime(array_values($Date)[0]);
//$end = strtotime(array_values($Date)[30]);
$result = array();
$begin = new DateTime( '2016-12-01' );
$end = new DateTime( '' );
//$end = $end->modify( '+1 day' );
$interval = new DateInterval('P1D');
$daterange = new DatePeriod($begin, $interval ,$end);
foreach($daterange as $date)
{
//echo $date->format("Y-m-d") . "<br>";
if (date('N', $start) <= 5) /* 'N' number days 1 (mon) to 7 (sun) */
/*5 weekday */
{
$current = date('Y-m-d', $start); //m/d/Y
$result[$current] = '';
}
$start += 86400;
//echo "Days Without Sat Sun".$result[date($date->format("Y-m-d"))];
//echo "Days Without Sat Sun".$result2[date($current->format("Y-m-d"))];
}
echo " \n <br /> Dates Without Weekends LIKE (Excluding Saturday & Sunday):-";
print_r($result);
/*For Holiday*/
$FinalArray = array();
$holidays = array(
'2016-12-13',
'2016-12-24',
);
echo " \n <br /> Given Holiday Dates Are:-";
print_r($holidays);
$a1 = $result;
$a2 = $holidays;
$array = array_diff(array_merge($a1,$a2),array_intersect($a1,$a2));
echo "\n <br /> Output:-";
print_r($array);
?>
it Gives Output as :- Array ( [2016-12-01] => [2016-12-02] => [2016-12-05] => [2016-12-06] => [2016-12-07] => [2016-12-08] => [2016-12-09] => [2016-12-12] => [2016-12-13] => [2016-12-14] => [2016-12-15] => [2016-12-16] => [2016-12-19] => [2016-12-20] => [2016-12-21] => [2016-12-22] => [2016-12-23] => [0] => 2016-12-13 [1] => 2016-12-24 )
> But I Want Expected Output:-
Array ( [2016-12-01] => [2016-12-02] => [2016-12-05] => [2016-12-06] => [2016-12-07] => [2016-12-08] => [2016-12-09] => [2016-12-12] => [2016-12-14] => [2016-12-15] => [2016-12-16] => [2016-12-19] => [2016-12-20] => [2016-12-21] => [2016-12-22] => [2016-12-23]
You Can Notice That 2016-12-13 is Not There in Above Expected Output as in '2016-12-13', 2016-12-24 is passed as Holiday via holiday array ($holidays = array( '2016-12-13', '2016-12-24', );) i:e; if i pass any date through holidays array it should not be included in result Array(). i:e 2016-12-13 is Available in Result array as well as holiday array So While while printing Final OUTPUT:- 13th date(2016-12-13) Should not be Included in final Output. Anybody Solve this will be Appreciated Thanks in Advance.
When I have to remove duplicates from a array the function that I keep going back to is
array array_unique ( array $array [, int $sort_flags = SORT_STRING ] )
you can find the documentation Here
<?php
$input = array("a" => "green", "red", "b" => "green", "blue", "red");
$result = array_unique($input);
print_r($result);
?>
the output
Array
(
[a] => green
[0] => red
[1] => blue
)
I hope that this was able to help
I prefer to calculate all dates just in one pass. (You may skip filling $dates and $dates_mon_fri arrays if they doesn't used in output also.) There is yet another approach to avoid array_diff() and array_unique() functions. I've used an array_flip() to exchange keys with values in $holdidays array to use fast array_key_exists() function.
<?php
$start = strtotime('2016-12-01');
$holidays = [
'2016-12-13',
'2016-12-24',
];
$dates = [];
$dates_mon_fri = [];
$dates_working = [];
$flip_holidays = array_flip($holidays);
for ($i = 0; $i < 20; $i++) {
$timestamp = strtotime("+$i day", $start);
$date = date('Y-m-d', $timestamp);
$dates[] = $date;
$mon_fri = false;
if (date('N', $timestamp) <= 5) {
$dates_mon_fri[] = $date;
$mon_fri = true;
}
if ($mon_fri && !array_key_exists($date, $flip_holidays)) {
$dates_working[] = $date;
}
}
var_dump($dates);
var_dump($dates_mon_fri);
var_dump($dates_working);
You can avoid using explicit looping:
$begin = new DateTimeImmutable('2016-12-01');
$end = $begin->modify('+20 days');
$interval = new DateInterval('P1D');
$daterange = new DatePeriod($begin, $interval, $end);
$allDates = iterator_to_array($daterange);
$datesExcludingWeekends = array_filter($allDates, function ($date) {
return (int) $date->format("N") < 6;
});
$datesExcludingWeekends = array_map(
'date_format',
$datesExcludingWeekends,
array_fill(1, count($datesExcludingWeekends), 'Y-m-d')
);
$holidays = [
'2016-12-13',
'2016-12-24',
];
$datesExcludingWeekendsIncludingHolidays = array_flip(array_merge(
$datesExcludingWeekends,
array_diff($holidays, $datesExcludingWeekends)
));
Here is working demo.
Also, take a look at the Carbon library. If you need some exhaustive working with dates this library can really ease your life.

Find recent and upcoming dates

I have a two-dimensional array containing events and dates:
$events = array(
array('event' => 'event1', 'date' => '2016-05-05'),
array('event' => 'event2', 'date' => '2016-05-08'),
array('event' => 'event3', 'date' => '2016-05-08'),
array('event' => 'event4', 'date' => '2016-05-10'),
array('event' => 'event5', 'date' => '2016-05-10'),
array('event' => 'event6', 'date' => '2016-05-11'),
array('event' => 'event7', 'date' => '2016-05-11'),
array('event' => 'event8', 'date' => '2016-05-13')
};
Let's say today is 2016-05-10 and I want to create 3 new arrays:
$recent - all events happened on the previous available day
$today - all events today
$upcoming - all events happening on the next available day
$today is easy:
$today = array();
for($i = 0; $i < count($events); ++$i) {
if($events[$i]['date'] == date("Y-m-d") {
array_push($today, $events[$i]['event']);
}
}
So, I will need $recent to contain event2 and event3, and $upcoming to contain event6 and event7.
The question is how to find the recent and the upcoming ones.
*Clarification:
I don't want all the events in $recent happened before today, but the events happened on the previous available day. So in this case only events happened on 2016-05-08
// Take all dates from source array
$dates = array_unique(array_map(function ($i) { return strtotime($i); } , array_column($events, 'date')));
sort($dates);
$today = strtotime('midnight');
// find previouse date. It will be 1970-1-1 if not present in array
$prev = #max(array_filter($dates, function($i) use($today) { return $i < $today; }));
// find туче date. It will be 1970-1-1 if not present in array
$next = #min(array_filter($dates, function($i) use($today) { return $i > $today; }));
$prev = date('Y-m-d', $prev);
$next = date('Y-m-d', $next);
// fill arrays
$recent = array();
$upcoming = array();
$today = array();
for($i = 0; $i < count($events); ++$i) {
if($events[$i]['date'] == date('Y-m-d')) {
array_push($today, $events[$i]['event']);
}
if($events[$i]['date'] == $prev) {
array_push($recent, $events[$i]['event']);
}
if($events[$i]['date'] == $next) {
array_push($upcoming , $events[$i]['event']);
}
}
Try with creating three (3) blank array, check the date is greater or less than today or not and push into array according to conditions.
$today = array();
$upcoming = array();
$recent = array();
$thisDay = date("Y-m-d");
$count = count($events);
$max = max(array_column($events, 'date'));
$min = min(array_column($events, 'date'));
for($i = 0; $i < $count; $i++){
if($events[$i]['date'] > $thisDay){
$max = ($max > $events[$i]['date']) ? $events[$i]['date'] : $max;
array_push($upcoming, $events[$i]['event']);
array_push($upcoming_dates, $events[$i]['date']);
}
elseif($events[$i]['date'] < $thisDay){
$min = ($min < $events[$i]['date']) ? $events[$i]['date'] : $min;
array_push($recent, $events[$i]['event']);
array_push($recent_dates, $events[$i]['date']);
}
else
array_push($today, $events[$i]['event']);
}
foreach($recent_dates as $key => $value){
if($value != $min)
unset($recent[$key]);
}
foreach($upcoming_dates as $key => $value){
if($value != $max)
unset($upcoming[$key]);
}
echo '<pre>';
print_r($today);
print_r($upcoming);
print_r($recent);
Result
Today:
Array
(
[0] => event4
[1] => event5
)
Upcoming:
Array
(
[0] => event6
[1] => event7
)
Recent:
Array
(
[1] => event2
[2] => event3
)
Note: You use push_array which is not any library function in PHP. For Re-index of recent and upcoming you can use array_values.
Another solution should be
$recent = array();
$upcoming = array();
$today = array();
$all_dates = array();
foreach ($events as $event):
array_push($all_dates, $event['date']);
endforeach;
if ($key = array_search('2016-05-10', $all_dates)) {
$prev_date = $all_dates[$key - 1];
}
for ($i = 0; $i < count($all_dates); $i++) {
if ($all_dates[$i] > $all_dates[$key]) {
$next_date = $all_dates[$i + 1];
break;
}
}
for ($i = 0; $i < count($events); ++$i) {
if ($events[$i]['date'] == date('Y-m-d')) {
array_push($today, $events[$i]['event']);
}
if ($events[$i]['date'] == $prev_date) {
array_push($recent, $events[$i]['event']);
}
if ($events[$i]['date'] == $next_date) {
array_push($upcoming, $events[$i]['event']);
}
}
echo '<pre>';
print_r($upcoming);
print_r($today);
print_r($recent);
Output will be
Array
(
[0] => event6
[1] => event7
)
Array
(
[0] => event4
[1] => event5
)
Array
(
[0] => event2
[1] => event3
)

Check date ranges (start and end date) for overlap

function checkDateOverlap($ranges) {
$res = $ranges[0];
$countRanges = count($ranges);
for ($i = 0; $i < $countRanges; $i++) {
$r1s = $res['start'];
$r1e = $res['end'];
$r2s = $ranges[$i]['start'];
$r2e = $ranges[$i]['end'];
if ($r1s >= $r2s && $r1s <= $r2e || $r1e >= $r2s && $r1e <= $r2e || $r2s >= $r1s && $r2s <= $r1e || $r2e >= $r1s && $r2e <= $r1e) {
$res = array(
'start' => $r1s > $r2s ? $r1s : $r2s,
'end' => $r1e < $r2e ? $r1e : $r2e
);
} else
return false;
}
return $res;
}
// example of returned dates that overlap
$ranges = array(
array('start' => '2014-01-01', 'end' => '2014-01-04'),
array('start' => '2014-01-05', 'end' => '2014-01-10'),
array('start' => '2014-01-04', 'end' => '2014-01-07')
);
//example of failure
$ranges2 = array(
array('start' => '2014-01-01', 'end' => '2014-01-04'),
array('start' => '2014-01-05', 'end' => '2014-01-10'),
array('start' => '2014-01-11', 'end' => '2014-01-17')
);
var_dump(checkDateOverlap($ranges));
The following is what I was attempting to check intersection of date ranges. In the array "ranges1" this example has overlapping dates. It should return the dates. In array $ranges2, this should pass as no intersecting dates.
Now the weird thing is the start and end date can be the exact same, so you could make an entry for just a single day. I've tried many things, and I'm stumped.
I believe there needs to be another for loop, but regardless I am not getting success.
Here was another go I had:
<?php
// pass your ranges to this method and if there is a common intersecion it will
// return it or false
function checkDateOverlap($ranges){
$res = $ranges[0];
$countRanges = count($ranges);
for ($i = 0; $i < count($countRanges); $i++) {
for($j = $i+1; $j < count($countRanges); $j++) {
$r1s = $res['start'];
$r1e = $res['end'];
$r2s = $ranges[$i]['start'];
$r2e = $ranges[$i]['end'];
if (($r1s >= $r2e && $r2s <= $r1e)) {
$res[] = array(
'start' => $r1s > $r2s ? $r1s : $r2s,
'end' => $r1e < $r2e ? $r1e : $r2e
);
} else
return false;
}
}
return $res;
}
// example
$ranges = array(
array('start' => '2014-01-04', 'end' => '2014-01-05'),
array('start' => '2014-01-06', 'end' => '2014-01-10'),
array('start' => '2014-01-11', 'end' => '2014-01-13')
);
echo "<pre>";
var_dump(checkDateOverlap($ranges));
echo "</pre>";
Any advice greatly appreciated.
$ranges = array(
array('start' => new DateTime('2014-01-01'), 'end' => new DateTime('2014-01-05')),
array('start' => new DateTime('2014-01-06'), 'end' => new DateTime('2014-01-06')),
array('start' => new DateTime('2014-01-07'), 'end' => new DateTime('2014-01-07')),
);
function intersects($lhs, $rhs) {
// Note that this function allows ranges that "touch",
// eg. one pair starts at the exact same time that the other ends.
// Adding less "or equal to" will allow same start date
return !($lhs['start'] > $rhs['end'] || $lhs['end'] < $rhs['start']);
}
function checkDates($ranges) {
// Comparison loop is of size n•log(n), not doing any redundant comparisons
for($i = 0; $i < sizeof($ranges); $i++) {
for($j = $i+1; $j < sizeof($ranges); $j++) {
if(intersects($ranges[$i], $ranges[$j])) {
echo "Date {$i} intersects with date {$j}\n";
}
}
}
}
checkDates($ranges);
I've attached my working code sample to hopefully help someone else in the future looking for the same solution. This will print the arrays that intersect.
If you use usort to first sort the dates, the work gets a lot easier. The following can be optimized a lot, but it is done step-by-step make it easier to understand.
//The date comparison function, sort on start and then on end
function cmp($a, $b)
{
if($a['start']<$b['start']) return -1;
if($a['start']>$b['start']) return 1;
if($a['end']<$b['end']) return -1;
if($a['end']>$b['end']) return 1;
return 0; // start=start and end=end
}
$ranges = array(
array('start' => '2014-01-01', 'end' => '2014-01-04'),
array('start' => '2014-01-05', 'end' => '2014-01-10'),
array('start' => '2014-01-04', 'end' => '2014-01-07')
);
usort($ranges, 'cmp'); // Sort the dates
$output = array();
for($i=0; $i<sizeof($ranges); $i++)
{
$endindex = $i; // The index containing the proper 'end' value
for($j=$i+1; $j<sizeof($ranges); $j++)
{
if($ranges[$endindex]['start'] == $ranges[$j]['start']) // Overlap
$endindex = $j;
elseif($ranges[$endindex]['end']>=$ranges[$j]['start']) // Overlap
$endindex = $j;
}
$output[] = array('start' => $ranges[$i]['start'], 'end' => $ranges[$endindex]['end']);
// Break the rules by hard-setting $i from the for loop - it works great in this case
$i = $endindex;
}
print_r($output);
It works for your example. If you have other rules that must be used, hopefully you can adjust this code.
Here are some remarks:
- You do not check the validity of the date formed by 'start' and 'end'.
- Why do you not convert the dates to timestamp ?
-> It's more easier and faster to compare integer value instead of string ?
Why do you not use PHP DateTime and DateInterval Objects ?
http://php.net/manual/en/book.datetime.php

Categories