I have an input array which contains arrays of day and time data:
$temp = Array
(
[0] => Array
(
[day] => Tuesday
[time] => 07:44 pm - 08:44 pm
)
[1] => Array
(
[day] => Tuesday
[time] => 04:00 am - 04:25 am
)
[2] => Array
(
[day] => Sunday
[time] => 04:00 am - 04:25 am
)
[3] => Array
(
[day] => Sunday
[time] => 04:00 am - 04:25 am
)
[4] => Array
(
[day] => Friday
[time] => 04:00 am - 04:25 am
)
[5] => Array
(
[day] => Friday
[time] => 04:00 am - 04:25 am
)
)
Now I want to group the common times display as one element and if the time is the same for more than one day then it's should display one entry. So what is the best way to achieve the desired result without making this too complex?
Array
(
[0] => Array
(
[day] => Tuesday
[time] => 04:00 am - 04:25 am & 07:44 pm - 08:44 pm
)
[1] => Array
(
[day] => Friday & Sunday
[time] => 04:00 am - 04:25 am
)
)
Here it's what I have done:
$final = [];
foreach ($temp as $val) {
$final[strtolower($val['day'])][] = $val['time'];
}
foreach ($final as $k => $v) {
sort($v);
$v = array_unique($v);
$last = array_pop($v);
$final[$k] = [
'day' => $k,
'time' => count($v) ? implode(", ", $v) . " & " . $last : $last,
];
}
There are 4 basic steps:
Remove duplicate rows.
Sort all rows by day (via lookup) ASC, then time (as a string) ASC.
Store joined time values using day values as temporary keys.
Store joined day values using joined time values as temporary keys.
Code: (Demo)
$array=[
['day'=>'Tuesday','time'=>'07:44 pm - 08:44 pm'],
['day'=>'Tuesday','time'=>'04:00 am - 04:25 am'],
['day'=>'Sunday','time'=>'04:00 am - 04:25 am'],
['day'=>'Sunday','time'=>'04:00 am - 04:25 am'],
['day'=>'Friday','time'=>'04:00 am - 04:25 am'],
['day'=>'Friday','time'=>'04:00 am - 04:25 am']
];
$array=array_unique($array,SORT_REGULAR); // remove exact duplicate rows
$order=array_flip(['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday']); // lookup array
usort($array,function($a,$b)use($order){ // sort by day name ASC then time ASC
if($order[$a['day']]==$order[$b['day']]){
return $a['time']<=>$b['time']; // 2nd sort criteria: time string
}
return $order[$a['day']]<=>$order[$b['day']]; // 1st sort criteria: day name via lookup array
});
foreach($array as $row){
if(!isset($temp[$row['day']])){
$temp[$row['day']]=$row['time']; // store time for first occurring day
}else{
$temp[$row['day']].=" & {$row['time']}"; // join times (for pre-existing day) as they are found
}
}
foreach($temp as $day=>$times){
if(!isset($result[$times])){
$result[$times]=['day'=>$day,'time'=>$times]; // store first occurring day and time using time as temp key
}else{
$result[$times]['day'].=" & $day"; // join day names as they are found
}
}
var_export(array_values($result)); // optionally remove the temporary keys
Output:
array (
0 =>
array (
'day' => 'Tuesday',
'time' => '04:00 am - 04:25 am & 07:44 pm - 08:44 pm',
),
1 =>
array (
'day' => 'Friday & Sunday',
'time' => '04:00 am - 04:25 am',
),
)
Here is another version that doesn't call array_unique(), but I don't like it as much because it does iterated sort() calls and generates a deeper temporary array.
foreach($array as $row){
$temp[$row['day']][$row['time']]=$row['time']; // remove duplicates and group by day
}
$order=array_flip(['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday']); // lookup array
uksort($temp,function($a,$b)use($order){
return $order[$a]<=>$order[$b]; // sort by day name via lookup array
});
foreach($temp as $day=>$times){
sort($times); // sort time elements ASC
$time_key=implode(' & ',$times); // join the day's time elements
if(!isset($result[$time_key])){ // if first occurrence of the time string
$result[$time_key]=['day'=>$day,'time'=>$time_key]; // store data using time string as temp key
}else{
$result[$time_key]['day'].=" & {$day}"; // concatenate new day to day element
}
}
var_export(array_values($result));
Related
I am trying to remove exactly 1 year from every date of an array.
Here is the original $periods array
Array ( [0] => 2020-06-01 [1] => 2020-07-01 [2] => 2020-08-01 [3] => 2020-09-01 [4] => 2021-01-01 [5] => 2022-01-01 [6] => 2022-06-01 [7] => 2022-08-01 [8] => 2022-10-01 [9] => 2023-04-01 )
I tried
foreach ($periods as $p) {
date_sub($p,date_interval_create_from_date_string("1 year"));
echo date_format($p,"Y-m-d"). '<br>';
}
It doesn't return anything...
Any pointers would be appreciated.
First set your array values datatype to string => "2020-06-01" , and try this code:
foreach ($periods as $period) {
// convert date string to date model object
$periodDate = date_create($period);
// subtract date string from coverted date string
date_sub($periodDate,date_interval_create_from_date_string("1 year"));
// covert subtracted date object to readable date fromat
echo date_format($periodDate,"Y-m-d");
}
You can transform dates using next code:
$res = array_map(
// convert source string to time, subtract 1 year interval
// and convert to 'Y-m-d' format in singe function
fn($p) => date('Y-m-d', strtotime("-1 year", strtotime($p))),
$periods
);
run PHP online
I am using below code to find out correct total hours but it is giving wrong results :-
Using getSortedDays to sort my array
Using addTotalAttendedHours to add total and add it into total attended hours ,I have tried all possible methods of time and none of them are working .
Even I tried removing abs and round but nothing is helping
<?php
declare(strict_types=1);
$data = [
'2020-07-14' =>
[
[
'start_time' => '14:15:00',
'end_time' => '17:45:00',
],[
'start_time' => '14:30:00',
'end_time' => '17:30:00',
],[
'start_time' => '14:30:00',
'end_time' => '17:30:00',
],
],
'2020-07-15' => [
[
'start_time' => '13:30:00',
'end_time' => '17:00:00',
],[
'start_time' => '09:00:00',
'end_time' => '14:00:00',
],
],
];
function getSortedDays(array $days): array {
return array_map(function (array $day) {
array_multisort(array_column($day, 'start_time'), SORT_ASC, $day);
return $day;
}, $days);
}
function addTotalAttendedHours(array $days): array {
$sortedDays = getSortedDays($days);
$days = array_map(function (array $day) {
$sum = (new DateTime())->setTimestamp(0);
$previousEnd = null;
foreach ($day as $time) {
$currentStart = new DateTimeImmutable($time['start_time']);
$currentEnd = new DateTimeImmutable($time['end_time']);
$sum->add($currentStart->diff($currentEnd));
if ($previousEnd !== null && $currentStart < $previousEnd) {
$sum->sub($currentStart->diff($previousEnd));
}
$previousEnd = $currentEnd;
}
$attendedSeconds = $sum->getTimestamp();
$day['total_attended_hours'] = sprintf(
'%02u:%02u:%02u',
$attendedSeconds / 60 / 60,
($attendedSeconds / 60) % 60,
$attendedSeconds % 60
);
return $day;
}, $sortedDays);
return $days;
}
echo "<pre>";
print_r(addTotalAttendedHours($data));
?>
Result
Array
(
[2020-07-14] => Array
(
[0] => Array
(
[start_time] => 14:15:00
[end_time] => 17:45:00
)
[1] => Array
(
[start_time] => 14:30:00
[end_time] => 17:30:00
)
[2] => Array
(
[start_time] => 14:30:00
[end_time] => 17:30:00
)
[total_attended_hours] => 03:15:00 //It should be 03:30:00
)
[2020-07-15] => Array
(
[0] => Array
(
[start_time] => 09:00:00
[end_time] => 14:00:00
)
[1] => Array
(
[start_time] => 13:30:00
[end_time] => 17:00:00
)
[total_attended_hours] => 08:00:00
)
)
Using above code I am getting wrong total_attended_hours where for date 2020-07-14 the result should be 03:30:00 but it is giving 03:15:00
For the first day you have these time ranges:
14:15 – 17:45
14:30 – 17:30
14:30 – 17:30
And you are doing this:
calculating the diff for the first range: 3:30
calculating the diff for the second range (3:00) and adding it: 3:30 + 3:00 = 06:30
because the start of the second range is before the previous end, you are subtracting the diff between them. the diff between 14:30 and 17:45 is 3:15, so 6:30 - 3:15 = 3:15
but the second range already ends at 17:30, so you are missing the 15 minutes between 17:30 and 17:45.
You could change the subtracting line to:
$sum->sub($currentStart->diff(min($currentEnd, $previousEnd)));
This way you wouldn't subtract too much anymore, because you would subtract only the diff of the overlapping part until the minimum of current and previous end.
Please sort the below array by its index(date). I want to sort the below array by ASC order by its date. After sorting the first one will be 2020-06-29 and its array, then 2020-06-30, I have tried more sorting mechanism but it could not solve the issue. Please help me to sort out this issue
Array(
[2020-07-01] => Array
(
[0] => Array
(
[slot] => 09:00 AM-11:00 AM
[is_available] => 1
[slot_id] => 29
)
)
[2020-06-29] => Array
(
[0] => Array
(
[slot] => 02:16 AM-02:16 AM
[is_available] => 1
[slot_id] => 14
)
)
[2020-06-30] => Array
(
[0] => Array
(
[slot] => 09:00 AM-06:00 PM
[is_available] => 1
[slot_id] => 15
)
)
[2020-07-02] => Array
(
[0] => Array
(
[slot] => 10:00 AM-05:00 PM
[is_available] => 1
[slot_id] => 35
)
)
[2020-07-03] => Array
(
[0] => Array
(
[slot] => 10:00 AM-03:00 PM
[is_available] => 1
[slot_id] => 36
)
)
)
Try ksort
here is a short example :
<?php
$fruits = array("d"=>"lemon", "a"=>"orange", "b"=>"banana", "c"=>"apple");
ksort($fruits);
foreach ($fruits as $key => $val) {
echo "$key = $val\n";
}
?>
https://www.php.net/manual/en/function.ksort.php
EDIT :
Since ksort seems to not be working, you should use uksort.
function date_compare($a, $b)
{
$t1 = strtotime($a);
$t2 = strtotime($b);
return $t1 - $t2;
}
uksort($data, 'date_compare');
here is a working example :
https://repl.it/repls/FoolhardyDrabPolygon
Step 1 : Create a function which accepts two dates ( strings ) as parameters, this function returns 1 if first date is greater than the second, it returns -1 if second date > first date or 0 if date's are equal. Working of functions starts from Step 2.
Step 2 : Toeknize the strings to get respective year, month and day of the two dates respectively. [ see : https://www.php.net/manual/en/book.tokenizer.php ]
Step 3 : If first date's year > second date's year, return 1 . If second date's year > first date's year return -1, if they are equal proceed to Step 4.
Step 4 : If first date's month > second date's month, return 1 . If second date's month > first date's month return -1, if they are equal proceed to Step 5.
Step 5 : If first date's day > second date's day, return 1 . If second date's day > first date's day return -1, if they are equal return 0.
Now use php's usort function and pass your function in it
[see : https://www.php.net/usort ]
I have an array like below & array would be dynamic.
how to check time is overlap another time of same day
I want to check the time between another time ..days wise
Array
(
[Monday] => Array
(
[0] => Array
(
[start_time] => 02:00 PM
[end_time] => 03:00 PM
)
[1] => Array
(
[start_time] => 04:00 PM
[end_time] => 05:05 PM
)
[2] => Array
(
[start_time] => 03:30 PM
[end_time] => 05:05 PM
)
)
[Sunday] => Array
(
[0] => Array
(
[start_time] => 03:00 PM
[end_time] => 04:00 PM
)
)
)
the single day has multiple time
I want to check time valid for Monday
For EX. Monday
1. 02:00 PM - 03:00 PM
2. 03:00 PM - 04:00 PM
3. 05:00 PM - 06:00 PM
above is valid time but if 3 value is
3:30 PM - 05:00 PM
This is not the valid time
I want to check dynamically
You can process one interval at the time, and check if it overlaps a previous one.
Checking if the start time or the end time is inside any other intervals is enough to find an overlap.
$times = array('Monday' => array(
0 => array('start_time' => '02:00 PM','end_time' => '03:00 PM')
, 1 => array('start_time' => '03:00 PM','end_time' => '05:05 PM')
, 2 => array('start_time' => '03:30 PM','end_time' => '05:05 PM')
)
, 'Sunday' => array(
0 => array('start_time' => '03:00 PM','end_time' => '04:00 PM')
)
);
foreach($times as $dayName => $intervals) // check each day separetely
{
// keep the indices of the valid intervals (we don't want to check if an interval overlaps an invalid one)
$validIntervals = array(0); /* no need to check first interval, always valid */
for($i = 1 ; $i < count($intervals) ; ++$i){
// parse the data of the interval to test
$start = DateTime::createFromFormat('H:i A', $intervals[$i]['start_time']);
$end = DateTime::createFromFormat('H:i A', $intervals[$i]['end_time']);
$isValid = true ;
// check if there is a conflict with a previous valid interval
foreach($validIntervals as $j)
{
$validStart = DateTime::createFromFormat('H:i A', $intervals[$j]['start_time']);
$validEnd = DateTime::createFromFormat('H:i A', $intervals[$j]['end_time']);
if(
($start > $validStart and $start < $validEnd) // the start of the interval is inside a previous one
or
($end > $validStart and $end < $validEnd) // the end of the interval is inside a previous one
)
{
// ** your processing of the incorrect interval here **
echo "In day '$dayName', interval $i [" . $intervals[$i]['start_time'] . ', ' . $intervals[$i]['end_time'] . "] overlaps with Interval $j [" . $intervals[$j]['start_time'] . ', ' . $intervals[$j]['end_time'] . "]" ;
$isValid = false ;
break ; // we found one conflict, no need to check for other one
}
}
// no conflict found, the interval is valid
if($isValid)
$validIntervals[] = $i ;
}
}
I have an array like this:
Array
(
[0] => stdClass Object
(
[Start] => 08:00
[dayName] => Tuesday
[dayID] => 2
[courseName] => Math
)
[1] => stdClass Object
(
[Start] => 10:00
[dayName] => Tuesday
[dayID] => 2
[courseName] => Geography
)
[2] => stdClass Object
(
[Start] => 14:00
[dayName] => Tuesday
[dayID] => 2
[courseName] => Science
)
[3] => stdClass Object
(
[Start] => 10:00
[dayName] => Thursday
[dayID] => 4
[courseName] => Math
)
[4] => stdClass Object
(
[Start] => 18:00
[dayName] => Friday
[dayID] => 5
[courseName] => History
)
)
What I want to do is , I want to compare the daya nd time now to the values in the array. For example lets assume that it is 7:00 am and it is Tuesday. Then I want to get the Object[0]. But if it is 11:00 o'clock then i need to get the Object[2] which starts at 14:00 on Tuesday.
It it is Tuesday and 16:00 o'clock then i need Object[3] .
If it is a weekend then i need the beginning of the week which is Tuesday at 08:00 with the Math Course.
I tried to get that using key => value but I mixed up.
How can I compare the Day and then time and in case there is a Course on that combination just pick it up if not just continue checking.
regards
littleblue
function getObject($array){
$timeNow = date('U'); // time now
$oneHour = $timeNow+3600; // time now + 1 hour
foreach($array as $num => $one){ // loop in each $array
$date = strtotime($one->Start); // convert start time to timestamp
if($date >= $timeNow && $date < $oneHour && date('l', $date) == $one->dayName){ // if all criteria met
return $array[$num]; // return that object
}
}
return array('no data'); // if no criteria is met return no data.
}
$course = getObject($yourArray);
echo $course->courseName;
You can mix the use of DateTime and a simple min search :
$myCourse = null;//initialisation
$myCourseDate =null;
$now = new DateTime;
foreach($array as $course){
//get the date from the datas
$date = DateTime::createFromFormat('l h:i',$course->dayName.' '.course->start);
if($now < $date && ($myCourseDate === null || $myCourseDate > $date)){
$myCourse = $course;
$myCourseDate = $date;
}
}
//at the end of the loop, you've got the expected course