Array_filter guidance

Hi

Continuing from a previous thread which became slightly off topic, and thanks to fretburner.

What I’m trying to do is filter the associative array so only the events from today onwards will be shown inside my foreach loop.

My code:

$queryEvents = "
  SELECT ID
  , DTEND
  , DTSTAMP
  , LOCATION
  , DTSTART
  , SUMMARY
  FROM events_test
  ORDER BY DTSTART ASC";
$resultEvents = $mysqli->query($queryEvents);
$row_cnt = $resultEvents->num_rows;
$eventsArray = array();
while ($row = $resultEvents->fetch_assoc()) {
  $row['endTime'] = date('g:ia', strtotime($row['DTEND']));
  $row['fullDate'] = date('l jS F Y', strtotime($row['DTSTART']));
  $row['startTime'] = date('g:ia', strtotime($row['DTSTART']));
  
  $eventsArray[] = $row;
}

And further down the page I show the results which lists all the events from the DB, including out of date events:

foreach($eventsArray as $value){
  echo "<li><a href='event/{$value['ID']}'>
  {$value['SUMMARY']}<br>
  {$value['LOCATION']}<br>
  {$value['startTime']} - {$value['endTime']}</a></li>";
}

I would like to filter this array so I only show events for today and any future upcoming events.

I have tried the below though returning array(0) ?
I am trying to query this filter based on $row['startTime'] a column every record has.

$today = new DateTime();
$current_events = array_filter($eventsArray, function($event) use ($today) {
  $event_start = $row['startTime'];
  return $event_start >= $today;
});
              
var_dump($current_events);

Any ideas how to fix this and build the filter into my foreach?


My main goal is to have a list like below showing events in order under the date no older than today:

23rd June 1016
Event name
Event name
Event name

24th June 2016
Event name
Event name
Event name

Thank you,
Barry

I’ve update my code:

$today = new DateTime();
$current_events = array_filter($eventsArray, function($event, $key) use ($today) {
  $event_start = new DateTime($row['startTime']);
    return $event_start >= $today;
}, ARRAY_FILTER_USE_BOTH);
              
 print_r($current_events);

Error

Warning: array_filter() expects at most 2 parameters, 3 given in…

I’ve just realised that my PHP version is 5.3.29… ARRAY_FILTER_USE_BOTH was only introduced in PHP version 5.6 :frowning:

Is there a work around?

Barry

I’ve started using array_map to try and accomplish the same result… due to the older version of PHP I’m using.

$array_map = $eventsArray;
              $today = new DateTime();
              
              function sort_by_date($n) {
                global $today;
                return $n['DTSTART'];
              }
              
              $new_array = array_map('sort_by_date', $array_map);
              
              var_dump($new_array);

Which prints out…

array(141) { [0]=> string(16) “20160515T173000Z” [1]=> string(16) “20160516T183000Z” [2]=> string(16) “20160518T160000Z” [3]=> string(16) “20160519T183000Z” [4]=> string(16) “20160520T170000Z” [5]=> string(16) “20160520T180000Z”…

No errors, so I now things are starting to get on track.

The problem is, out of the 141 events, only 20+ are current - upcoming events.

I have tried adding:

return $n['DTSTART'] >= $today;

Still 141 ?

Any ideas?

Thnaks, Barry

Hey Barry,

Sorry I haven’t had time to reply until now. You can get the original array_filter example to work on your version of PHP with a simple change:

$current_events = array_filter($eventsArray, function($event) use ($today) {
  $event_start = new DateTime($row['startTime']);
    return $event_start >= $today;
});

I originally misunderstood the docs and thought that you needed to include the flag as a 3rd argument, but it turns out you can just leave it out and you get the behavior we need anyway.

You can’t substitute array_filter with array_map, because mapping is a different operation - it transforms each element of the input array into an output, which is why you end up with an output array of the same length.

If you always want to filter the events in the same way (only displaying those later than the current date) then I’d do this in your SQL query, as it’s more efficient. The only issue you’d have there is the date format (e.g. 20160515T173000Z) which I’m assuming you’ve got stored in the DB as a string? AFAIK, there’s no easy way to manipulate dates stored like that using SQL. What you’d have to do is alter your import script to convert the dates to a format MySQL can work with (2016-05-15 17:30:00, in the case of the previous example) and store in a datetime field. You can then query the DB for only the events within the data range you want, and order them chronologically.

No worries, thought you’d had enough ha
Been pulling my hair out for over a week on this… welcome back :smile:

This is exactly what I was trying for days, only for the DB to keep showing 0000-00-00 00:00:00 - couldn’t get any values from the JSON.

Correct, because of what I mentioned above.
I’ve been working on this for the past couple of hours after what you suggested below… :grinning:

What you’d have to do is alter your import script to convert the dates to a format MySQL can work with (2016-05-15 17:30:00, in the case of the previous example) and store in a datetime field.

Works great now. I have the correct values inside the DB as datetime and time columns as you mention “convert the dates to a format MySQL can work with” - this done the job :sunglasses:

This is what I’ve done:

DTSTAMP | datetime | 0000-00-00 00:00:00
DTEND | time | 00:00:00
DTSTART | time | 00:00:00
$dtend = date("g:ia", strtotime($row['DTEND']));
$dtstamp = date("Y-m-d", strtotime($row['DTSTAMP']));
$location = stripslashes_deep($row['LOCATION']);
$dtstart = date("g:ia", strtotime($row['DTSTART']));

Seems to work ok. I had to use time for the time columns instead of datetime.
I’ve also added a stripslashes function to the location and some other columns which removes the \ \ which I was having trouble with. Is this the right place for this?

And does this look ok to you fretburner, things done correctly?

Thanks, Barry

After much testing… it turned out the DTSTAMP was wrong so I needed to INSERT DTSTART into this column and needed to change time back to datetime as am/pm times where incorrect.

DTSTART | datetime | 0000-00-00 00:00:00
$dtstart = date(“Y-m-d H:ia”, strtotime($row[‘DTSTART’]));

I think it’s now safe to say, array_filter and array_map are no longer needed as we have the MYSQL working, though a good learning curve which will come in handy for future work, thanks again for information on this.

MYSQL SELECT - this does exactly what we need and works good, showing 37 events :smile:

FROM events_test
WHERE DTSTAMP >= CURDATE()
ORDER BY DTSTAMP ASC

Unless you can see any issues with the posts above?

The next problem is trying to break up this array into smaller sections displayed underneath the correct dates, example as follows:

2nd July 2016
Event name
Event name
Event name

3rd July 2016
Event name
Event name
Event name

If I remember correctly we can do this using a foreach inside another foreach, though not sure how this works. Any ideas on the right approach using the current snippet below?

The while loop

while ($row = $resultEvents->fetch_assoc()) {
  $row['endTime'] = date('g:ia', strtotime($row['DTEND']));
  $row['fullDate'] = date('l jS F Y', strtotime($row['DTSTAMP']));
  $row['startTime'] = date('g:ia', strtotime($row['DTSTART']));
  
  $eventsArray[] = $row;
}

And the output on the page

foreach($eventsArray as $value){
    echo "<li><a href='event/{$value['ID']}'>
    {$value['SUMMARY']}<br>
    {$value['LOCATION']}<br>
    {$value['fullDate']}<br>
    {$value['startTime']} - {$value['endTime']}</a></li>";
}

Thanks, Barry

Just a draft… though displaying exactly what I need :sunglasses:

Thursday 2nd June 2016
7:30pm - Event Summary
11:00pm - Event Summary

Thursday 3rd June 2016
8:00pm - Event Summary
11:00pm - Event Summary

What do you think @fretburner - can you see any issues with this?

I have also ORDER BY DTSTART ASC which sorts the event times in order. The dates are ordered in ASC order automatically… do you know why that it is? A good thing don’t get me wrong, just wondering why this has happened.

And what is your view on using mysqli_fetch_object over using mysqli_fetch_array

$result = $mysqli->query("
  SELECT ID
  , LOCATION
  , DTSTAMP
  , DTSTART
  , SUMMARY
  FROM events_test
  WHERE DTSTAMP >= CURDATE()
  ORDER BY DTSTART ASC");

$set = array();

while ($record = mysqli_fetch_object($result)) {
  $record->DTSTART = date('g:ia', strtotime($record->DTSTART));
  $set[$record->DTSTAMP][] = $record;
}

foreach ($set as $DTSTAMP => $records) {
  $DTSTAMP = date('l jS F Y', strtotime($DTSTAMP));
  print "<dl>\n";
  print "<dt><h3>{$DTSTAMP}</h3></dt>\n";
  foreach ($records as $record) {
    print "<dd>\n";
    print $record->DTSTART . ' - ' .$record->LOCATION;
    print "</dd>\n";
  }
  print "</dl>\n";
}

Thanks, Barry

Hey Barry,

Hope you had a good weekend.

Nothing jumps out, but my PHP is a little rusty these days.

I believe in MySQL that ORDER BY is ASC by default, unless you specify otherwise.

The same caveats would apply that I mentioned when deciding whether to decode JSON into objects or associative arrays. For what you’re doing here it doesn’t look like it would matter either way, so it just comes down to preference.

Cheers

Cool!

Thanks for getting back, I’ll bring this to a close then.
Nice to hear everything looks good :sunglasses:

Yes I’ll track back and have a look.

Speak soon,
Barry

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.