grav: Can't use custom date header

Hi!

I found a problem. If I have date: 09.02.2017 in blog post header and this code:

content:
    items: @self.children
    order:
        by: date
        dir: desc

than my sorting works correctly. {{ dump(post.header.date) }} shows me timestamp date.

But if I have custom date header like releaseDate and order by this header, then sorting breaks down. For example, 01.09.2012 is older then 09.02.2017. {{ dump(post.header.releaseDate) }} shows me string date as is.

Later @flaviocopes told me to use Y.m.d format (2017.02.09 for example). This temporarily resolved my problem until I want to output the date in the post. I put {{ dump(page.header.releaseDate|date('d')) }} line in twig and see an error:

An exception has been thrown during the rendering of a template ("DateTime::__construct(): 
Failed to parse time string (2017.02.09) at position 5 (0): Double time specification").

How to fix?

p.s. Same problem in the forum: https://discourse.getgrav.org/t/date-format/1555/8

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Comments: 25 (24 by maintainers)

Most upvoted comments

I modified Sogl’s solution a little to be compatible with the https://github.com/getgrav/grav-plugin-pagination Plugin, I know it’s a bit dirty recreating the collection, but I did not found any better solution without adding a “setItmes” method to the Collection class:

`<?php namespace Grav\Theme;

use Grav\Common\Grav; use Grav\Common\Page\Collection;

class CollectionSorter { protected $grav; protected $field; protected $asc;

public function __construct() { $this->grav = Grav::instance(); }

public function byDate($collection, $field, $asc = true) { $this->field = $field; $this->asc = $asc;

$array = [];
foreach($collection as $p) {
  $array[] = $p;
}

usort($array, function($a, $b) {

  if (!isset($a->header()->{$this->field}, $b->header()->{$this->field})) {
    return 0;
  }

  $valA = $a->header()->{$this->field};
  $valB = $b->header()->{$this->field};

  if ($valA == $valB) {
    return 0;
  }

  if ($this->asc) {
    return strtotime($valA) - strtotime($valB);
  }
  return strtotime($valB) - strtotime($valA);
});

$items = array();
foreach ($array as $delta => $page) {
  $items[$page->path()] = array('slug' => $page->slug());
}

return new Collection($items);

} }`

The rest remains as described in https://github.com/getgrav/grav/issues/1641#issuecomment-344122465

In combination with pagination: In my usecase I do have a custom collection with a seperate limit field, configurable by the user which I pass as second parameter to paginate {% set collection = sorter.byDate(collection, 'event_date', false) %} {% do paginate(collection, page.header.custom_collection.limit) %}

Date fields on the page frontmatter are strings and as such, they are always ordered as strings, not dates. There is really no way to do anything about it as pages aren’t aware of field content type, which makes the proper comparison not possible.

I am personally aware of this issue and we have plans to replace current page logic with something better in upcoming versions (= 2.0).

BTW: be careful when using dashes in the dates: it’s in American format: mm/dd/yyyy. For European dates you need to use dd.mm.yyyy instead. That said, you’re right: neither of those format order properly as strings and you need to use yyyy-mm-dd instead.

@rhukster In latest Grav 1.3.8 I can use this code {{ dump(page.header.myDate|date('d')) }} successfully without errors.

But I’m still can’t order collections by custom date header.

I found a workaround:

  1. Create a new file in theme: themename/class/CollectionSorter.php
<?php
namespace Grav\Theme;

use Grav\Common\Grav;

class CollectionSorter
{
    protected $grav;
    protected $field;
    protected $asc;

    public function __construct() {
        $this->grav = Grav::instance();
    }

    public function byDate($collection, $field, $asc = true)
    {
        $this->field = $field;
        $this->asc = $asc;

        $array = [];
        foreach($collection as $p) {
            $array[] = $p;
        }

        usort($array, function($a, $b) {

            if (!isset($a->header()->{$this->field}, $b->header()->{$this->field})) {
                return 0;
            }

            $valA = $a->header()->{$this->field};
            $valB = $b->header()->{$this->field};

            if ($valA == $valB) {
                return 0;
            }

            if ($this->asc) {
                return strtotime($valA) - strtotime($valB);
            }
            return strtotime($valB) - strtotime($valA);
        });

        return $array;
    }
}
  1. Add this to themename.php file:
public function onTwigSiteVariables()
{
    require_once __DIR__ . '/class/CollectionSorter.php';
    $this->grav['twig']->twig_vars['sorter'] = new CollectionSorter();
}
  1. Use in twig:
{% set asc_order = sorter.byDate(page.collection, 'custom_date_field') %}
{% set desc_order = sorter.byDate(page.collection, 'custom_date_field', false) %}

But what if I want to handle things like dateRange, limit etc… do everything in custom PHP functions?

@rhukster Maybe you can add strtotime Twig filter into Grav? This will solve problems with converting custom dates to timestamps.