Sort an array by Month

I am manually building up an array for events and spitting them out, but I am having a hard time sorting it by month (I know Kirby can do this with its built in features but it has to be manual because i’m combining Kirby data with external data).

My array looks like this…

array(7) {
  ["May"]=>
  array(1) {
    [0]=>
    array(13) {
      ["ETitle"]=>
      string(17) "Test Future Event"
      ["EStart"]=>
      string(19) "2018-05-31T09:00:00"
      ["EHstart"]=>
      string(6) "31 May"
      ["EHstartTime"]=>
      string(5) "09:00"
      ["EEnd"]=>
      string(19) "2018-05-31T17:00:00"
      ["ELink"]=>
      string(42) "http://local.salt/events/event-one"
      ["EStartMonth"]=>
      string(3) "May"
      ["EStartYear"]=>
      string(4) "2018"
      ["EID"]=>
      string(9) "event-one"
      ["ESrc"]=>
      string(6) "kcalev"
      ["EImg"]=>
      string(74) "http://local.salt/thumbs/events/event-one/slide-events-400x300.jpg"
      ["EDesc"]=>
      object(Field)#182 (3) {
        ["page"]=>
        string(16) "events/event-one"
        ["key"]=>
        string(7) "summary"
        ["value"]=>
        string(205) "<p>In et sapien nec felis euismod dictum. Duis nulla tortor, fringilla in augue non, ultricies euismod orci. Praesent nunc lacus, lobortis in urna ut, ultricies lacinia massa. Aenean ac faucibus purus.</p>"
      }
      ["EStartHuman"]=>
      string(24) "Thursday, May 31: 9:00am"
    }
  }
  ["June"]=>
  array(2) {
    [1]=>
    array(13) {
      ["ETitle"]=>
      string(14) "Another Future"
      ["EStart"]=>
      string(19) "2018-06-01T15:00:00"
      ["EHstart"]=>
      string(7) "01 June"
      ["EHstartTime"]=>
      string(5) "15:00"
      ["EEnd"]=>
      string(19) "2018-06-01T20:00:00"
      ["ELink"]=>
      string(42) "http://local.salt/events/event-two"
      ["EStartMonth"]=>
      string(4) "June"
      ["EStartYear"]=>
      string(4) "2018"
      ["EID"]=>
      string(9) "event-two"
      ["ESrc"]=>
      string(6) "kcalev"
      ["EImg"]=>
      string(74) "http://local.salt/thumbs/events/event-two/slide-events-400x300.jpg"
      ["EDesc"]=>
      object(Field)#203 (3) {
        ["page"]=>
        string(16) "events/event-two"
        ["key"]=>
        string(7) "summary"
        ["value"]=>
        string(209) "<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nullam tempus elit in elit pulvinar ornare. Quisque scelerisque tellus vel arcu luctus, in lacinia turpis blandit.</p>"
      }
      ["EStartHuman"]=>
      string(22) "Friday, June 1: 3:00pm"
    }
    [2]=>
    array(13) {
      ["ETitle"]=>
      string(17) "Test Future Event"
      ["EStart"]=>
      string(19) "2018-06-08T09:00:00"
      ["EHstart"]=>
      string(7) "08 June"
      ["EHstartTime"]=>
      string(5) "09:00"
      ["EEnd"]=>
      string(19) "2018-05-11T17:00:00"
      ["ELink"]=>
      string(44) "http://local.salt/events/event-three"
      ["EStartMonth"]=>
      string(4) "June"
      ["EStartYear"]=>
      string(4) "2018"
      ["EID"]=>
      string(11) "event-three"
      ["ESrc"]=>
      string(6) "kcalev"
      ["EImg"]=>
      string(76) "http://local.salt/thumbs/events/event-three/slide-events-400x300.jpg"
      ["EDesc"]=>
      object(Field)#222 (3) {
        ["page"]=>
        string(18) "events/event-three"
        ["key"]=>
        string(7) "summary"
        ["value"]=>
        string(205) "<p>In et sapien nec felis euismod dictum. Duis nulla tortor, fringilla in augue non, ultricies euismod orci. Praesent nunc lacus, lobortis in urna ut, ultricies lacinia massa. Aenean ac faucibus purus.</p>"
      }
      ["EStartHuman"]=>
      string(22) "Friday, June 8: 9:00am"
    }
  }
  ["October"]=>
  array(2) {
    [3]=>
    array(13) {
      ["ETitle"]=>
      string(14) "Another Future"
      ["EStart"]=>
      string(19) "2018-10-26T15:00:00"
      ["EHstart"]=>
      string(10) "26 October"
      ["EHstartTime"]=>
      string(5) "15:00"
      ["EEnd"]=>
      string(19) "2018-10-26T20:00:00"
      ["ELink"]=>
      string(43) "http://local.salt/events/event-four"
      ["EStartMonth"]=>
      string(7) "October"
      ["EStartYear"]=>
      string(4) "2018"
      ["EID"]=>
      string(10) "event-four"
      ["ESrc"]=>
      string(6) "kcalev"
      ["EImg"]=>
      string(75) "http://local.salt/thumbs/events/event-four/slide-events-400x300.jpg"
      ["EDesc"]=>
      object(Field)#241 (3) {
        ["page"]=>
        string(17) "events/event-four"
        ["key"]=>
        string(7) "summary"
        ["value"]=>
        string(209) "<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nullam tempus elit in elit pulvinar ornare. Quisque scelerisque tellus vel arcu luctus, in lacinia turpis blandit.</p>"
      }
      ["EStartHuman"]=>
      string(26) "Friday, October 26: 3:00pm"
    }
    [8]=>
    array(13) {
      ["ETitle"]=>
      string(17) "Test Future Event"
      ["EStart"]=>
      string(19) "2018-10-06T09:00:00"
      ["EHstart"]=>
      string(10) "06 October"
      ["EHstartTime"]=>
      string(5) "09:00"
      ["EEnd"]=>
      string(19) "2018-10-06T17:00:00"
      ["ELink"]=>
      string(43) "http://local.salt/events/event-nine"
      ["EStartMonth"]=>
      string(7) "October"
      ["EStartYear"]=>
      string(4) "2018"
      ["EID"]=>
      string(10) "event-nine"
      ["ESrc"]=>
      string(6) "kcalev"
      ["EImg"]=>
      string(75) "http://local.salt/thumbs/events/event-nine/slide-events-400x300.jpg"
      ["EDesc"]=>
      object(Field)#336 (3) {
        ["page"]=>
        string(17) "events/event-nine"
        ["key"]=>
        string(7) "summary"
        ["value"]=>
        string(205) "<p>In et sapien nec felis euismod dictum. Duis nulla tortor, fringilla in augue non, ultricies euismod orci. Praesent nunc lacus, lobortis in urna ut, ultricies lacinia massa. Aenean ac faucibus purus.</p>"
      }
      ["EStartHuman"]=>
      string(27) "Saturday, October 6: 9:00am"
    }
  }

I need to sort the on the array by month ie [“May”] and then sort the events by date within each month (earliest first). How can i do it? Google hasn’t helped much.

My Code looks like this:

<div class="all-events">

<?php

// Rearrange the array by months
$fullmonty = array();

// get all the month keys
foreach($eventroot as $key => $item)
{
  $fullmonty[$item['EStartMonth']][$key] = $item;
}

var_dump($fullmonty);

// Spit the list out
foreach ($fullmonty as $monthName => $month) {
  echo '<header class="month-header"><h2>' . $monthName . '</h2></header>';
  echo '<div class="events--list">';
  foreach ($month as $event) {
  echo '<div class="event--item">  <a href="' . $event['ELink'] . '">' . '<img class="event-thumb" src="' . $event['EImg'] . '">' . '</a> ' . '<h3>' .  $event['ETitle'] . '</h3>' . '<h4>' . $event['EStartHuman'] . '</h4>'  . $event['EDesc'] . ' </div>' ;
  }
  echo '</div>';
}
?>

</div>

Nothing to do with your problem, but your life and that of other who have to read your code will be much easier if you don’t echo HTML tags…

Edit: Looks like you already have a month key in your array? Why do you have to sort it again by month?

Because its in alpha order not calendar month order. I trimmed the array so i didnt make a huge post. Atleast it was until i took asort() out.

The order currently is may, June, October, July, August…

Well, yes I know but i borrowed it from an older project when i was younger and more stupid. Ill clean it up eventually. :slight_smile:

You need a helper array:

$months = ['January' => 0,  'February' => 1, ...];

Then use uksort() to sort by key.

There must be an example here somewhere, don’t have the time to search for it now.

How would i do this the standard kirby way, with a normal collection rather than a manual array?. I also use a CMS called Textpattern… is there a Kirby equivalent to different?

I basically just want to group articles under month headings.

If you have a standard Kirby collection, you can use group() with a callback, see the documentation example. Instead of using the month name to group by, use the month number. Then use a months array to get the corresponding month name.

As I said, there is an example somewhere, were Today, Future etc. should have been ordered. You just have to find it…

Think this was it: Custom Grouping Order

The problem here is not the grouping but the sort order, I don’t see how differentwould solve this issue.

<?php 

// group items by month
$groupedItems = $page->children()->visible()->group(function($p){
  return $p->date('n'); // group by numeric representation of a month, without leading zeros
});

Then in your template:

$months = [1 => 'January' ,  2 =>  'February',  3 => 'March', '...'];
foreach ($groupedItems as $monthNumber => $monthList): ?>
  <h2><?= $months[$monthNumber] ?></h2>
  <ul>
    <?php foreach($monthList as $article): ?>
      <li><a href="<?= $article->url() ?>"><?= $article->title() ?></a></li>
    <?php endforeach ?>
  </ul>
<?php endforeach ?>

Thanks for the above, i will give it ago. I tried with the example in the docs…still does by year though, not month…

<?php

// function that returns the formatted date
$callback = function($p) {
  return $p->startdate()->date('F');
};
// group items using $callback
$groupedItems = page('events')->children()->visible()->group($callback);

// output items by Month
foreach($groupedItems as $month => $itemsPerMonth): ?>
    <h2><?= $month ?></h2>
    <ul>
      <?php foreach($itemsPerMonth as $item) : ?>
      <li><?= $item->title() ?></li>
      <?php endforeach; ?>
    </ul>
<?php endforeach ?>

Off topic, but with TXP different in a loop (aka the article tag) this is enough to do what i want. I just wondered if there was an equivalent in Kirby:

<!-- show the month -->
<txp:if_different>
<h3><txp:posted format="%B" /></h3>
</txp:if_different>
<!-- article title and link -->
<txp:permlink><txp:title /></txp:permlink> 

This won’t work, unless you have created a date field method…

The syntax is like this: $p->date('F', 'startdate')- But F`gives you a textual representation of the month and then you are back to your sorting problem.

Well, Textpattern is different, they are using some kind of template language, so you can’t really compare that.

Sure i get that, but wondered if there was a function in Kirby that could detect when a value changes to something in a collection and start a new list automatically.

id love to be able to do something like:

<?php foreach ($site->find('events')->children()->visible() as $event): ?>
  <h2><?= $event->different($event->date('F', 'startdate')) ?></h2>
  <?= $event->title() ?>
<?php endforeach ?>

That is what group() does, only differently. I still don’t see how this would solve your sorting problem.

Maybe have a look at what Textpattern does internally, then write yourself a little plugin to achieve this if you don’t like Kirby’s grouping method.

I love Kirby! :heart: :peace_symbol:

I’m just curious how to solve problems I usually have to solve the Kirby way. TXP uses a database so i’m pretty sure it handles all the sorting as part of the SQL query based on what you feed it.

I’ll investivate group() now that I know that it exists.

There is often more than one way to solve a problem. You could also loop through an array of months and see if there are any corresponding articles:

$articles = page('events')->children()->visible()
$months = ['January' ,   'February',   'March', '...'];
foreach($months as $month):
  $articles = $articles->filter(function($p) { return $p->date('F', 'startdate') == $month; });
    foreach($articles as $article):
    // bla bla
    endforeach;
endforeach;

Or if there wasn’t a filter function, we could get even more basic:

$articles = page('events')->children()->visible()
$months = ['January' ,   'February',   'March', '...'];
foreach($months as $month):
  foreach($articles as $article):
    if(!$article->date('F', 'startdate') == $month) continue;
      echo $article->title():
    endforeach;
endforeach;

I went with your earlier suggestion and tried this:

<div class="all-events">
<?php
// group items by month
$groupedItems = $page->children()->visible()->group(function($p){
  return $p->date('n', 'startdate'); // group by numeric representation of a month, without leading zeros
});

$months = [1 => 'January', 2 => 'February', 3 => 'March', 4 => 'April', 5 => 'May', 6 => 'June', 7 => 'July', 8 => 'August', 9 => 'September', 10 => 'October', 11 => 'November', 12 => 'December'];

foreach ($groupedItems as $monthNumber => $monthList): ?>
  <header class="month-header"><h2><?= $months[$monthNumber] ?></h2></header>
  <div class="events--list">
    <?php foreach($monthList as $event):
      $estartdate = $event->startdate();
      $estarttime = $event->starttime();
      $estartisoDate = date('Y-m-d\TH:i:s', strtotime("$estartdate $estarttime"));
      $englishdate = date('l, F j: g:ia', strtotime($estartisoDate));
    ?>

      <div class="event--item">
      <a href="<?= $event->url() ?>"><img class="event-thumb" src="<?= $event->poster()->toFile()->width(400)->url() ?>"></a>
      <h3><?= $event->title() ?></h3>
      <h4><?= $englishdate ?></h4>
      <?= $event->summary()->kirbytext() ?>
    </div>

    <?php endforeach ?>
  </div>
<?php endforeach ?>
</div>

But the outputted list is still in the wrong order :frowning: have i missed something?

I think I’m too tired today and should really do something else. Forget everything I said above. Everything. I was still with your original array in my thoughts…

If you first sort by date and then group, you might as well group by month name and it should all work out perfectly.

Ah… oh dear. It’s ok. Well thanks for the help. Go and have a little :sleeping_bed: :sleeping:

So i took break and came back with a fresh brain. This worked…

<?php
$callback = function($p) {
    return $p->date('F', 'startdate');
};
$groupedEvents = $site->find('events')->children()->sortBy('startdate', 'asc')->group($callback);

foreach($groupedEvents as $month => $events): ?>
  <h2><?= $month ?></h2>
  <?php
    // loop through the events
    foreach($events as $event): ?>
    <h3><?= $event->title() ?></h3>
  <?php endforeach ?>
<?php endforeach ?>