Previously already have customers reserve times :
09-11
12-14
23-01
$shopOpenTime = 08;
$shopCloseTime = 04;
What is the easiest way using php to list out all available times?
01-04 / 08-09 / 11-12 / 14-23
Coding :
foreach($manyOldBook as $eachOldBook)
{
$availableTime = $availableTime . $eachOldBook['book_end'] . " - " . $eachOldBook['book_start'] . " / ";
}
echo $availableTime;
I tried :
using if else to convert 24 to 00, 25 to 01, 26 to 02...
using if else to check if $previous_book_end < 04, then show $shopCloseTime.
using if else to check if $previous_book_start > 08, then show $shopOpenTime.
End up there are more than 100 lines of if else statement, too messy code, too much code... I am looking for easier way to do it?
Maybe use php fmode()? w3cschool fmode tuturial
Or Maybe store all booked times in php array, and print value which is not exist in the array?
Any other creative and easier way to do it?
I would use PHPs DateTime object for this. It will give you more flexibility, e.g. if your reservation is not a whole hour or things like that.
First convert your input data into DateTime object and the get the free blocks between.
Example:
<?php
$reservedTimes = [
['start' => 9,
'end' => 11,
],
['start' => 12,
'end' => 14,
],
['start' => 23,
'end' => 01,
],
];
$shopOpenTime = new DateTime();
$shopOpenTime->setTime(8,0);
$shopCloseTime = new DateTime();
$shopCloseTime->add(new DateInterval('P1D'))->setTime(4,0);
$freeTimes = [];
//start of the free time block is the shop open time
$freeTimeStart = $shopOpenTime;
foreach ($reservedTimes as $reservedTime) {
//add 24h if end is lower than start
if ($reservedTime['end'] < $reservedTime['start']) {
$reservedTime['end'] = $reservedTime['end'] + 24;
}
//end of the free time block is start of the reservation
$freeTimeEnd = new DateTime();
$freeTimeEnd->setTime($reservedTime['start'], 0);
//add new element to array if start and end is not the same time
if ($freeTimeStart != $freeTimeEnd) {
$freeTimes[] = ['start' => clone($freeTimeStart), 'end' => $freeTimeEnd];
}
//start of the next free block is the end of this reservation
$freeTimeStart->setTime($reservedTime['end'], 0);
}
//add the last element until shop closing time
$freeTimeEnd = $shopCloseTime;
if ($freeTimeStart != $freeTimeEnd) {
$freeTimes[] = ['start' => clone($freeTimeStart), 'end' => $freeTimeEnd];
}
foreach ($freeTimes as $freeTime) {
print('Start ' . $freeTime['start']->format('Y-m-d H:i:s') . ' ');
print('End ' . $freeTime['end']->format('Y-m-d H:i:s'));
print(PHP_EOL);
}
This gives you:
Start 2021-10-09 08:00:00 End 2021-10-09 09:00:00
Start 2021-10-09 11:00:00 End 2021-10-09 12:00:00
Start 2021-10-09 14:00:00 End 2021-10-09 23:00:00
Start 2021-10-10 01:00:00 End 2021-10-10 04:00:00
The desired result
I would like to have an associative array that contains a range of times (between opening and closing times) with an interval of 15 minutes. For example:
[
'2017-01-16' => [ // Start of the week (Monday)
'08:00', // Opening time
'08:15',
'08:30',
// Etc..
'18:00', // Closing time
],
'2017-01-17' => [ // Tuesday
'10:00', // Opening time
'10:15',
'10:30',
// Etc..
'22:00', // Closing time
],
// For every day in the week.
];
Another thing I would like to be able to do, is: Take a range of times (e.g. 09:00 - 10:00) and remove it from the array (at a specific date key)
The steps I made (so far)
I have an array that looks just like the one above. But.. it starts with 00:00 and ends at 23:45. With the following code (mainly from another question at Stackoverflow):
private function generateDateRange(Carbon $start_date, Carbon $end_date)
{
$dates = [];
while ($start_date->lte($end_date)) {
if(! array_key_exists($start_date->format('Y-m-d'), $dates)) {
$dates[$start_date->format('Y-m-d')] = [];
} else {
array_push($dates[$start_date->format('Y-m-d')], $start_date->format('H:i'));
if(in_array($start_date->format('H:i'), $dates[$start_date->format('Y-m-d')])) {
$start_date->addMinutes(15);
} else {
$start_date->addDay();
}
}
}
return $dates;
}
$start = Carbon::now()->startOfWeek();
$end = Carbon::now()->endOfWeek();
$range = $this->generateDateRange($start, $end);
My question
How can I do this in PHP (Laravel)? I am planning to make this (more) dynamic by using a database. But first I want to have a working basic.
Does someone know what I could do to reach the desired result?
Try this:
private function generateDateRange(Carbon $start_date, Carbon $end_date,$slot_duration = 15)
{
$dates = [];
$slots = $start_date->diffInMinutes($end_date)/$slot_duration;
//first unchanged time
$dates[$start_date->toDateString()][]=$start_date->toTimeString();
for($s = 1;$s <=$slots;$s++){
$dates[$start_date->toDateString()][]=$start_date->addMinute($slot_duration)->toTimeString();
}
return $dates;
}
Here is a little background on what I am trying to create.
I am creating a function called getNextBilling($dateStart,$dateCount = 20)
You give it a period length which is the days you want someone to be billed
$test->period = '2,5,15';
it takes a starting date which
I have assigned on a test page
$test->getNextBilling('2015-06-12 00:00:00',2);
What the function is supposed to do, is make sure if the inputed dates are less than the current date to skip this months billing, which works perfectly.
What doesn't work is that I have a foreach loop inside of my while loop to see all the days inputed, I can't find a way to make the code work where it actually just shows the amount of days you want.
example above which shows I want to display 2 days the output is
Array
(
[1] => 2015-06-15
[2] => 2015-07-02
[3] => 2015-07-05
[4] => 2015-07-15
)
Here is code of the function
// SET START DATE
$startDate = new DateTime($dateStart);
// LOOP
$i = 1;
while($i <= $dateCount){
// LOOP THROUGH INPUTED DAYS
foreach($days as $day){
// CLEAN DAY
$day = formatNumbersOnly($day);
// SET LAST DAY
$lastDay = new DateTime($startDate->format('Y-m-d'));
$lastDay->modify('last day of this month');
// CHECK FOR PASSED DAY
if($day > $startDate->format('j')){
// CHECK FOR 28-29-30-31
if($day > $lastDay->format('j')){
// SET NEW DATE
$startDate->setDate(
$startDate->format('Y'),
$startDate->format('m'),
$lastDay->format('j')
);
} else {
// SET NEW DATE
$startDate->setDate(
$startDate->format('Y'),
$startDate->format('m'),
$day
);
}
// SET ARRAY
$nextBilling[$i] = $startDate->format('Y-m-d');
} else {
// SKIP
$i--;
}
// INCREASE COUNT
$i++;
}
// NEXT MONTH
$startDate->modify('1 month');
$startDate->setDate($startDate->format('Y'),$startDate->format('m'),1);
}
Now I understand where the problem is, it's because of the increasing the count inside of the foreach loop however when I put it outside it will only output the last day in days like this:
Array
(
[-1] => 2015-06-15
[0] => 2015-07-15
[1] => 2015-08-15
[2] => 2015-09-15
)
If anyone has any criticism or tips that would be greatly appreciated. I will be continuing on trying to fix it.
EDIT: I added a if statment at the beginning of the foreach to check if $i is greater than $dateCount it will break out of the loop. Thanks Twisty for your time and efforts, I appreciate it.
Glad you found an answer. Here is something I was working on while you found it.
// SET START DATE
$startDate = new DateTime($dateStart);
$nextBill = new DateTime();
for($i=1;$i<$countDays;$i++){
switch(true){
case ($startDate->format('j') > $days[2]):
// go to next month
$nextBill->setDate(
$startDate->format('Y'),
$startDate->format('m')+1,
$days[0];
);
break;
case ($startDate->format('j') > $days[1]):
// Is start date greater than 2nd billing (5)
$nextBill->setDate(
$startDate->format('Y'),
$startDate->format('m'),
$days[2];
);
break;
case ($startDate->format('j') > $days[0]):
// is starteDate greater than 1st billing (2)
$nextBill->setDate(
$startDate->format('Y'),
$startDate->format('m'),
$days[1];
);
break;
default:
$nextBill->setDate(
$startDate->format('Y'),
$startDate->format('m'),
$days[0];
);
}
$nextBilling[$i] = $nextBill->format('Y-m-d');
$startDate = $nextBill->setDate(
$nextBill->format('Y'),
$nextBill->format('m'),
$nextBill->format('j')+1
);
}
This will ensure that if $startDate is the 1st of the month, it shows the 2nd, and then the 5th. Id $startDate is the 3rd, it will show the 5th and 15th. If $startDate is the 20th, it will show the 2nd and 5th of the next month.
I have some PHP code to calculate the number of days between two specific dates. The difference should not count Sundays and Saturdays. Also, I have an array of dates, which includes holidays, which also need to be skipped.
I gave the starting date as 01-05-2015 and ending date as 01-06-2015. I gave the entire days in the month of may as array. Thus the difference should be 1 day. But I am getting the output as 7. What is the problem? Here is the code.
function dateRange($first, $last) {
$dates = array();
$current = strtotime($first);
$now = $current;
$last = strtotime($last);
while( $current <= $last ) {
if (date('w', $current) != 0){
$dates[] = date('d-m-Y', $current);
}
$current = strtotime('+1 day', $current);
}
unset($dates[0]);
return $dates;
}
$datea = "01-05-2015";
$date = "01-06-2015";
$hdsarray = array("1-05-2015","2-05-2015","4-05-2015","5-05-2015","7-05-2015","8-05-2015","9-05-2015","11-05-2015","12-05-2015","14-05-2015","15-05-2015","16-05-2015","18-05-2015","19-05-2015","21-05-2015","22-05-2015","23-05-2015","25-05-2015","26-05-2015","28-05-2015","29-05-2015","30-05-2015");
$datesarray = dateRange($datea, $date);
$result = array_diff($hdsarray,$datesarray);
$date_diff = sizeof($result);
echo $date_diff;
The only problem I can see is in the usage of array_diff, It actually includes the sat and sun which is excluded by dateRange function, if not found in holidays list.
Instead, you can pass your holiday dates in dateRange function, and filter over there.
function dateRange($first, $last, $excludeDates) {
$dates = array();
$current = strtotime($first);
$now = $current;
$last = strtotime($last);
while( $current <= $last ) {
if (date('w', $current) != 0 && date('w', $current) != 6 && !in_array(date('j-m-Y', $current), $excludeDates)){
$dates[] = date('d-m-Y', $current);
}
$current = strtotime('+1 day', $current);
}
return $dates;
}
$datea = "01-05-2015";
$date = "01-06-2015";
$hdsarray = array("1-05-2015","2-05-2015","4-05-2015","5-05-2015","7-05-2015","8-05-2015","9-05-2015","11-05-2015","12-05-2015","14-05-2015","15-05-2015","16-05-2015","18-05-2015","19-05-2015","21-05-2015","22-05-2015","23-05-2015","25-05-2015","26-05-2015","28-05-2015","29-05-2015","30-05-2015");
$datesarray = dateRange($datea, $date, $hdsarray);print_r($datesarray);
Result:
Array
(
[0] => 06-05-2015
[1] => 13-05-2015
[2] => 20-05-2015
[3] => 27-05-2015
[4] => 01-06-2015
)
All the 5 dates come in the result, are not sat, sun, and also not there in holidays list.
It seems that there are several problems here. First, as pointed out by others the condition:
if (date('w', $current) != 0){
only checks for Sundays, if it should also include Saturday's it should be:
if (date('w', $current) != 0 && date('w', $current) != 6){
Secondly, it seems that the $hdsarray array does not contain all of the days in May. It seems that all of the Wednesdays are missing.
The third issue is that you are using array_diff on two arrays, one containing Dates and the other ones containing Strings. From the documentation:
Two elements are considered equal if and only if (string) $elem1 ===
(string) $elem2. In words: when the string representation is the same.
In your $hdsarray you are using "1-05-2015" to denote the first day of the month, while:
echo date('d-m-Y', strtotime("1-05-2015"));
results in "01-05-2015". You will need to add an additional 0 in $hdsarray for these dates or work with dates as well.
Last but not least, the current algorithm will not work correctly if the $hdsarray contains dates for a Saturday or Sunday, the result of array_diff will still contain these dates. Since you want to filter the result of daterange the array_filter function might be more suitable.
Despite an answer has already been provided, here is a little snippet with a class handling everything for you:
<?php
class dateRange {
protected $start, $end, $daysToExclude, $datesToExclude;
function __construct($dateStart, $dateEnd, $daysToExclude, $datesToExclude) {
$this->start = $dateStart;
$this->end = $dateEnd;
$this->daysToExclude = $daysToExclude;
$this->datesToExclude = $this->fixFormat($datesToExclude);
}
public function getRangeLength ($callback = null) {
$tmp = array();
$now = strtotime($this->start);
$to = strtotime($this->end);
while ( $now <= $to ) {
if (!in_array(date("w", $now), $this->daysToExclude)) {
$tmp[] = date('d-m-Y', $now);
}
$now = strtotime('+1 day', $now);
}
is_callable($callback) && call_user_func($callback, array_diff($tmp,$this->datesToExclude));
return count(array_diff($tmp,$this->datesToExclude));
}
private function fixFormat($el) {
if (!is_array($el)) {
return false;
}
else {
foreach ($el as &$value) {
$value = date("d-m-Y",strtotime($value));
}
return $el;
}
}
}
?>
I decided to keep your current logic (using date_diff), but I thought that, in the future, you may have your boss telling you "You know what? I don't want to have mondays aswell there" so, with the current system, you will have to edit your function manually and, perhaps, you won't remember anymore what you did.
The class above expects four parameters:
dateStart (d-m-Y format)
dateEnd (d-m-Y format)
daysToExclude (array with IDs of the days to exclude -> example array(0,6) to exclude saturdays and sundays).
datesToExclude (array with the dates to exclude, every format supported).
The class will automatically fix the datesToExclude array format in order to allow you to use date_diff.
Here is an example to use it, following your case:
<?php
$dateStart = "01-05-2015";
$dateEnd = "01-06-2015";
$daysToExclude = array(0,6);
$exclusions = array(
"1-05-2015",
"2-05-2015",
"4-05-2015",
"5-05-2015",
"7-05-2015",
"8-05-2015",
"9-05-2015",
"11-05-2015",
"12-05-2015",
"14-05-2015",
"15-05-2015",
"16-05-2015",
"18-05-2015",
"19-05-2015",
"21-05-2015",
"22-05-2015",
"23-05-2015",
"25-05-2015",
"26-05-2015",
"28-05-2015",
"29-05-2015",
"30-05-2015"
);
$dateRange = new dateRange($dateStart, $dateEnd, $daysToExclude, $exclusions);
echo $dateRange->getRangeLength();
?>
The code above outputs 5.
The function getRangeLength also accepts a callback and will return the array resulting of the date_diff operation, so you can also:
$dateRange->getRangeLength(function($res) {
echo "Literal output: <br />";
print_r($res);
echo "<br />count is: " . count($res);
});
The above outputs:
Literal output:
Array ( [3] => 06-05-2015 [8] => 13-05-2015 [13] => 20-05-2015 [18] => 27-05-2015 [21] => 01-06-2015 )
count is: 5
So if you later will need to remove mondays too, you will be able to easily do that by changing daysToExclude to array(0,1,6);
Hope this will be helpful to anyone else who will need this, despite a valid answer has already been posted.
Your original problem, in any case, was pretty much related to the array_diff function, which was NOT doing its job because of the fact that the date strings were not compatible, because "1-01-2015" is different from "01-01-2015", unless you first convert BOTH of them to times and then back to dates.
The code is fine (except that $nowis not used at all). The problem is the $hdsarray is wrong:
It should $hdsarray = array("01-05-2015", "02-05-2015", "04-05-2015", "05-05-2015", "07-05-2015", "08-05-2015", "09-05-2015",...);
date('d-m-Y', $current);will always return a leading 0 for all days between 1 and 9.
That's where the difference comes from.