Sorting array based on date/time - php

I am trying to sort a multidimensional array based on date/time, however it doesn't seem to be working correctly when I do a print_r. My best guess is that the time I provided to strtotime() is not in the correct format however the date and time formats are both listed, but separately in the php manual and no errors are thrown.
The format I use is unclear in the code so here it is: yyyy-mm-dd hhmm (24h with no colon GMT)
Here is the code:
function dateSort($a, $b){
$d1 = strtotime($a['date'].' '.$a['startTime']);
$d2 = strtotime($b['date'].' '.$a['startTime']);
return $d1 - $d2;
}
usort($events, 'dateSort');
print_r($events);

IVAO.
You had a typo in third line of snippet. Second line refers to $a, but in 3rd you mix both $b and $a :).
Also, I think, you needn't use strtotime at all. Look into snippet:
<?php
function dateSort($a, $b)
{
$d1 = floatval(str_replace('-', '', $a['date']) . " $a[startTime]");
$d2 = floatval(str_replace('-', '', $b['date']) . " $b[startTime]");
return $d1 - $d2;
}
$events = [
['date' => '2015-05-01', 'startTime' => '2300', 'value' => 'Event 1'],
['date' => '2012-05-01', 'startTime' => '1430', 'value' => 'Event 2'],
['date' => '2011-09-17', 'startTime' => '1021', 'value' => 'Event 3'],
['date' => '2001-01-22', 'startTime' => '0959', 'value' => 'Event 4'],
['date' => '1999-02-05', 'startTime' => '1740', 'value' => 'Event 5'],
];
usort($events, 'dateSort');
echo '<pre>' . print_r($events, 1) . '</pre>';
And click to codepad.

From Php manual, you can try to update your dateSort() function
function dateSort($a, $b){
$d1 = strtotime($a['date'].' '.$a['startTime']);
$d2 = strtotime($b['date'].' '.$a['startTime']);
return ($d1 < $d2) ? -1 : 1;
}
Suggest you to give us some of your output, easier to take it from there.

Related

ErrorException - Trying to get property 'name' of non-object

Sorry I am new to Laravel and PHP. I have a web application code that I need to debug. I run the code, and it gives me ErrorException Trying to get property 'name' of non-object. I looked it up, and I know it's a common problem but I still cannot figure out what is causing this error. I already looked at this page and I don't understand: Reference - What does this error mean in PHP?
This is my controller code:
function getActivity(){
$lead_history = Lead::with('user','eventTypeTrashed','locationTrashed')->get();
$event_history = Event::with('user','booking','contactus.event_type_trashed','booking.location_trashed')->get();
$data = [];
foreach ($lead_history as $key => $leads){
if(count($leads->revisionHistory) > 0){
foreach ($leads->revisionHistory as $history){
$date_diff = \DateTime::createFromFormat('Y-m-d H:i:s',date('Y-m-d H:i:s'))->diff(\DateTime::createFromFormat('Y-m-d H:i:s',date('Y-m-d H:i:s',strtotime($history->updated_at))));
if($date_diff->d > 0 ){
$date = $date_diff->d . ' days ago';
} elseif($date_diff->h > 0){
$date = $date_diff->h . ' hours ago';
}else{
$date = $date_diff->i . ' minutes ago';
}
$data[] = [
'id' => $leads->id,
'type' => 'lead',
'image' => $history->userResponsible()->user_avatar,
'user' => $history->userResponsible()->first_name .' '. $history->userResponsible()->last_name,
'user_id' => $history->userResponsible()->id,
'key' => ucwords(str_replace("_"," ",$history->fieldName())),
'client' => $leads->client_name,
'status' => 'update',
'old_value' =>$history->oldValue(),
'new_value' =>$history->newValue(),
'updated_at' => $history->updated_at,
'time_diff' =>$date,
'priority' => $leads->priority,
'location' => $leads->locationTrashed->name, // This line casuses an error
'event_type' => ($leads->eventTypeTrashed) ? $leads->eventTypeTrashed->name : ''
];
}
}
$date_diff = \DateTime::createFromFormat('Y-m-d H:i:s',date('Y-m-d H:i:s'))->diff(\DateTime::createFromFormat('Y-m-d H:i:s',date('Y-m-d H:i:s',strtotime($leads->created_at))));
if($date_diff->d > 0){
$date = $date_diff->d . ' days ago';
} elseif($date_diff->h > 0){
$date = $date_diff->h . ' hours ago';
}else{
$date = $date_diff->i . ' minutes ago';
}
$data[] = [
'id' => $leads->id,
'type' => 'lead',
'image' => $leads->user->user_avatar,
'user' => $leads->user->first_name .' '. $leads->user->last_name,
'user_id' => $leads->user->id,
'key' => '',
'client' => $leads->client_name,
'status' => 'created',
'updated_at' => $leads->created_at,
'old_value' =>'',
'new_value' =>'',
'time_diff' =>$date,
'priority' => $leads->priority,
'location' => $leads->locationTrashed->name,
'event_type' => ($leads->eventTypeTrashed) ? $leads->eventTypeTrashed->name : ''
];
}
foreach ($event_history as $key => $events){
if(count($events->revisionHistory) > 0){
foreach ($events->revisionHistory as $history){
$date_diff = \DateTime::createFromFormat('Y-m-d H:i:s',date('Y-m-d H:i:s'))->diff(\DateTime::createFromFormat('Y-m-d H:i:s',date('Y-m-d H:i:s',strtotime($history->updated_at))));
if($date_diff->d > 0){
$date = $date_diff->d . ' days ago';
}elseif ($date_diff->h > 0){
$date = $date_diff->h . ' hours ago';
}else{
$date = $date_diff->i . ' minutes ago';
}
$data[] = [
'id' => $events->id,
'type' => 'event',
'image' => $history->userResponsible()->user_avatar,
'user' => $history->userResponsible()->first_name .' '. $history->userResponsible()->last_name,
'user_id' => $history->userResponsible()->id,
'key' => ucwords(str_replace("_"," ",$history->fieldName())),
'client' => $events->booking->booking_name,
'status' => 'update',
'updated_at' => $history->updated_at,
'old_value' =>$history->oldValue(),
'new_value' =>$history->newValue(),
'time_diff' =>$date,
'priority' => $events->status,
'location' => $events->booking->location_trashed->name,
'event_type' => $events->contactus->event_type_trashed->name
];
}
}
$date_diff = \DateTime::createFromFormat('Y-m-d H:i:s',date('Y-m-d H:i:s'))->diff(\DateTime::createFromFormat('Y-m-d H:i:s',date('Y-m-d H:i:s',strtotime($events->created_at))));
if($date_diff->d > 0){
$date = $date_diff->d . ' days ago';
}elseif($date_diff->h > 0){
$date = $date_diff->h . ' hours ago';
}else{
$date = $date_diff->i . ' minutes ago';
}
$data[] = [
'id' => $events->id,
'type' => 'event',
'image' => ($events->user) ? $events->user->user_avatar : '',
'user' => ($events->user) ? $events->user->first_name .' '. $events->user->last_name : '',
'user_id' => ($events->user) ? $events->user->id : '',
'key' => '',
'client' => $events->booking->booking_name,
'status' => 'created',
'updated_at' => $events->created_at,
'old_value' =>'',
'new_value' =>'',
'time_diff' => $date,
'priority' => $events->status,
'location' => $events->booking->location_trashed->name,
'event_type' => $events->contactus->event_type_trashed->name
];
}
usort($data, function ($a, $b){
$dateA = \DateTime::createFromFormat('Y-m-d H:i:s', $a['updated_at']);
$dateB = \DateTime::createFromFormat('Y-m-d H:i:s', $b['updated_at']);
return $dateB >= $dateA;
});
return $data;
}
Sorry if this is a bad question and please tell me if I need to include anything else
This error occurs when there's no relational record found. Like in your code you've a relation "locationTrashed". When lead don't have any record related to it in location_trashed table it'll return error. In Laravel 8 you can return default model result if not results available against record or just simply use ($param ?? null).
You have a lot going on there! I have some suggestions that might make your life easier and your code cleaner. But first, the missing 'name' error...
'location' => $leads->locationTrashed->name,
Relationship Name
First thing I noticed about this relationship is the name should be 'location_trashed' instead of the camelCase 'locationTrashed'. In your other models, it is 'location_trashed', so make sure this is correct. I'll continue to reference it as locationTrashed, since this is what your current code reflects.
Finding the Cause
You're probably correct in identifying this line as causing the error. The issue is probably that the locationTrashed doesn't exist. You should look for the locationTrashed_id (or location_trashed_id) to figure out what the id is, and make sure the item exists in the database and hasn't been deleted.
Which Object Doesn't Exist?
If you aren't sure which object is causing the error, you can try dumping the object if the relationship doesn't exist. Laravel has convenient methods like has() and doesntHave() to make this easy. Put this right above the '$data[] = [' line.
https://laravel.com/docs/8.x/eloquent-relationships#querying-relationship-existence
if($leads->doesntHave('locationTrashed')) {
dd($leads);
}
$data[] = [
'id' => $leads->id,
'type' => 'lead',
...
This will let you see the object in question and determine which related 'locationTrashed' is missing.
Or... ignore it
If you need the relationship, use the above to try to figure it out. However, if the relationship isn't there because it doesn't always exist, set it to be ignored.
You can use an exists() method on the relationship to check if it exists.
'location' => ($leads->locationTrashed()->exists()) ? $leads->locationTrashed->name : ''
Now some suggestions:
Carbon for Dates
You should really look into using Carbon. This will take the 8 lines of code you have and reduce it to:
$date_diff = Carbon::parse($history->updated_at)->diffForHumans();
https://carbon.nesbot.com/docs/#api-humandiff
Attribute Accessors
Your user model has a first_name and last_name attribute that you're putting together to display their full name. If you do this often, you can set up an attribute accessor so you don't have keep doing it.
// User Model
public function getFullNameAttribute() {
return $this->first_name.' '.$this->last_name;
}
// Controller
'user' => $history->userResponsible->full_name
https://laravel.com/docs/8.x/eloquent-mutators#accessors-and-mutators
Laravel Collections
When working with arrays, you can instead convert them into collections and take advantage of the many useful methods that it provides. Instead of ordering the data items yourself, you can use the sortBy() method:
$collection = collect($data)->sortBy('updated_at');
$collection = collect($data)->sortByDesc('updated_at'); // or descending
https://laravel.com/docs/8.x/collections

PHP - Get associative array value by date range without loop

I'm using the following PHP code to show a different text (just one) every week:
<?php
$items = [
[
'start' => '2020-02-03',
'end' => '2020-02-09',
'html' => 'Text #1'
],
[
'start' => '2020-02-10',
'end' => '2020-02-16',
'html' => 'Text #2'
],
[
'start' => '2020-02-17',
'end' => '2020-02-23',
'html' => 'Text #3'
],
];
$currentDate = date('Y-m-d');
foreach ($items as $item) {
if ($currentDate >= $item[start] && $currentDate <= $item[end]) echo $item[html];
}
It works.
But is there a better (i.e. cleaner, faster) way to achieve the same result? Is loop really necessary?
Thanks.
UPDATE
Inspired by Progrock's answer (which I thank), I would modify my code as follows:
$items =
[
'06' => 'Text #1',
'07' => 'Text #2',
'08' => 'Text #3',
'09' => 'Text #4'
];
$date = new DateTime(date('Y-m-d'));
echo $items[$date->format('W')];
I think it's a better solution (for what I need).
As your ranges are monday->sunday you could use ISO-8601 week numbering. Though here the data is harder to interpret without comments.
<?php
$items =
[
'06' => 'Text #1',
'07' => 'Text #2',
'08' => 'Text #3'
];
$iso_week = date('W', strtotime('2020-02-12'));
echo $items[$iso_week];
Output:
Text #2
https://www.php.net/manual/en/function.array-filter.php
$currentDate = date('Y-m-d');
$filteredItems = array_filter($items, function($item) use ($currentDate) {
return $currentDate >= $item['start'] && $currentDate <= $item['end'];
});
Though, you're still going to have to loop the filtered items for output, eventually.

Input multiple time range then return all another time range

I'm finding a solution that when I input a time range (or many time ranges) as an array, then it will return all another time ranges except the time range that I inputted.
For example:
I input [ 0 => ['start' => '8:30:00', 'end' => '9:00:00'], 1 => ['start' => '11:30:00', 'end' => '12:30:00']]
(8:30:00 - 9:00:00), (11:30:00 - 12:30:00)
And all the time I want to receive from 8:00:00 to 17:00:00
The expected output that I want it returns is:
[ 0 => ['start' => '8:00:00', 'end' => '8:30:00'], 1 => ['start' => '09:00:00', 'end' => '11:30:00'], 2 => ['start' => '12:30:00', 'end' => '17:00:00']]
(8:00:00 - 8:30:00), (09:00:00 - 11:30:00), (12:30:00 - 17:00:00)
Thank you.
You can simple use this concept. You dont need to use any datetime library or any other concept. Just paly with for loop will work fine.
<?php
$startingTime = '8:00:00';
$endingTime = '1:00:00';
$time = [ ['start' => '8:30:00', 'end' => '9:00:00'],
['start' => '11:30:00', 'end' => '12:30:00']];
$output = [];
for ($x = 0; $x <= count($time); $x++) {
$t = $time[$x];
$interval = [];
$interval['start'] = $startingTime;
$interval['end'] = ($t['start']=== NULL? $endingTime: $t['start'] );
array_push($output,$interval);
$startingTime = $t['end'];
}
print_r($output);
$aInputDate = [['start' => '08:30:00', 'end' => '09:00:00'],
['start' => '11:30:00', 'end' => '12:30:00']];
$sStartTime = strtotime(date('Y-m-d 08:00:00'));
$sEndTime = strtotime(date('Y-m-d 17:00:00'));
while ($sStartTime <= $sEndTime) {
$aAllTime[] = date('H:i:s', $sStartTime);
$sStartTime = strtotime('+30 minutes', $sStartTime);
}
foreach ($aInputDate as $key => $value) {
$iStartKey = array_search($value['start'], $aAllTime);
unset($aAllTime[$iStartKey]);
$iStartKey = array_search($value['end'], $aAllTime);
unset($aAllTime[$iStartKey]);
}
var_dump($aAllTime);

Sorting strings in format HH:mm in array with PHP

I have an Array like this one:
$x = array(
array(
'inicio' => '09:00',
'fim' => '09:30'
),
array(
'inicio' => '09:30',
'fim' => '10:00'
),
array(
'inicio' => '08:30',
'fim' => '09:00'
),
array(
'inicio' => '11:30',
'fim' => '12:00'
)
);
I have to ordenate this by inicio field. I am trying to use usort liket this but I only get 1 as result:
$y = usort($x, function($a, $b) {
return (explode(':', $b['inicio'])[0]*60 + explode(':', $b['inicio'])[1]) - (explode(':', $a['inicio'])[0]*60 + explode(':', $a['inicio'])[1]);
});
print_r($y);
What am I doing wrong here?
1st, usort does not return array, it is sorted in place. Doc says:
Returns TRUE on success or FALSE on failure.
2nd, you can just compare inicio fields
usort($x, function ($a, $b) { return strcmp($a['inicio'], $b['inicio']); });
print_r($x);

PHP arrays: summing values with matching dates

i'm trying to figure out how to sum certain values of a multi-dimensional array if they have similar dates.
Here's my array:
<?$myArray=array(
array(
'year' => 2011,
'month ' => 5,
'day' => 13,
'value' => 2
),
array(
'year' => 2011,
'month '=> 5,
'day' => 14,
'value' => 5
),
array(
'year' => 2011,
'month ' => 5,
'day' => 13,
'value' => 1
),
array(
'year' => 2011,
'month ' => 5,
'day' => 14,
'value' => 9
)
);?>
here's how i'd like the output to look:
<?$output=array(
array(
'year' => 2011,
'month ' => 5,
'day' => 13,
'value' => 3 //the sum of 1+2
),
array(
'year' => 2011,
'month '=> 5,
'day' => 14,
'value' => 14 //the sum of 5+9
)
);?>
Notice how the 4 sub-arrays were matched on year/month/day and then only the value was summed. I've seen other SO threads on this topic but can't find one where only the value is summed and not the year/month/day values too.
Thoughts?
It may be easiest to initially index your output array with a combination of the year/month/day:
Note: Your example array above has all its month keys with a trailing space. I'm just using month here with no trailing space.
// Initialize output array...
$out = array();
// Looping over each input array item
foreach ($myArray as $elem) {
// Initialize a new element in the output keyed as yyyy-mm-dd if it doesn't already exist
if (!isset($out[$elem['year'] . "-" . $elem['month '] . "-" . $elem['day']])) {
$out[$elem['year'] . "-" . $elem['month '] . "-" . $elem['day']] = array(
// Set the date keys...
'year' => $elem['year'],
'month' => $elem['month '],
'day' => $elem['day'],
// With the current value...
'value' => $elem['value']
);
}
// If it already exists, just add the current value onto it...
else {
$out[$elem['year'] . "-" . $elem['month '] . "-" . $elem['day']]['value'] += $elem['value'];
}
}
// Now your output array is keyed by date. Use array_values() to strip off those keys if it matters:
$out = array_values($out);
Outputs (before calling array_values()):
array(2) {
'2011-5-13' =>
array(4) {
'year' =>
int(2011)
'month' =>
int(5)
'day' =>
int(13)
'value' =>
int(3)
}
'2011-5-14' =>
array(4) {
'year' =>
int(2011)
'month' =>
int(5)
'day' =>
int(14)
'value' =>
int(14)
}
}
Update:
To do the same thing with single-key dates (rather than 3-parts) it is easier without the concatenation:
$myArray=array(
array(
'date' => '2011-05-13',
'value' => 2
),
array(
'date' => '2011-05-14',
'value' => 5
),
array(
'date' => '2011-05-13',
'value' => 7
),
array(
'date' => '2011-05-14',
'value' => 3
),
);
foreach ($myArray as $elem) {
// Initialize a new element in the output if it doesn't already exist
if (!isset($out[$elem['date']])) {
$out[$elem['date'] = array(
// Set the date keys...
'date' => $elem['date'],
// With the current value...
'value' => $elem['value']
);
}
else {
$out[$elem['date']]['value'] += $elem['value'];
}
}
Here's how I would do it. The result will be in $newArray with datetime objects as keys. If you just want it as an indexed array it should be pretty easy to do.
// Example array
$myArray = array(
array(
'date' => new DateTime('1993-08-11'),
'value' => 3
),
array(
'date' => new DateTime('1993-08-11'),
'value' => 5
)
);
$newArray = array();
foreach($myArray as $element)
{
$iterationValue = $element['value'];
$iterationDate = $element['date'];
$dateKey = $iterationDate->format('Y-m-d');
if(array_key_exists($dateKey, $newArray))
{
// If we've already added this date to the new array, add the value
$newArray[$dateKey]['value'] += $iterationValue;
}
else
{
// Otherwise create a new element with datetimeobject as key
$newArray[$dateKey]['date'] = $iterationDate;
$newArray[$dateKey]['value'] = $iterationValue;
}
}
nl2br(print_r($newArray));
Actually ended up doing the pretty much the same thing as #MichaelBerkowski solution. Still, having DateTime objects is always more flexible when you wan't to do things with the dates later in your application.
Edit: Now tested it and fixed errors

Categories