Jekyll/Liquid Templating: How to group blog posts by year?
If you want to break it down by year, here's the code:
{% for post in site.posts %}
{% capture this_year %}{{ post.date | date: "%Y" }}{% endcapture %}
{% capture next_year %}{{ post.previous.date | date: "%Y" }}{% endcapture %}
{% if forloop.first %}
<h2 id="{{ this_year }}-ref">{{this_year}}</h2>
<ul>
{% endif %}
<li><a href="{{ post.url }}">{{ post.title }}</a></li>
{% if forloop.last %}
</ul>
{% else %}
{% if this_year != next_year %}
</ul>
<h2 id="{{ next_year }}-ref">{{next_year}}</h2>
<ul>
{% endif %}
{% endif %}
{% endfor %}
If you want to break it down to year and months it can be achieved like this:
{% for post in site.posts %}
{% capture this_year %}{{ post.date | date: "%Y" }}{% endcapture %}
{% capture this_month %}{{ post.date | date: "%B" }}{% endcapture %}
{% capture next_year %}{{ post.previous.date | date: "%Y" }}{% endcapture %}
{% capture next_month %}{{ post.previous.date | date: "%B" }}{% endcapture %}
{% if forloop.first %}
<h2 id="{{ this_year }}-ref">{{this_year}}</h2>
<h3 id="{{ this_year }}-{{ this_month }}-ref">{{ this_month }}</h3>
<ul>
{% endif %}
<li><a href="{{ post.url }}">{{ post.title }}</a></li>
{% if forloop.last %}
</ul>
{% else %}
{% if this_year != next_year %}
</ul>
<h2 id="{{ next_year }}-ref">{{next_year}}</h2>
<h3 id="{{ next_year }}-{{ next_month }}-ref">{{ next_month }}</h3>
<ul>
{% else %}
{% if this_month != next_month %}
</ul>
<h3 id="{{ this_year }}-{{ next_month }}-ref">{{ next_month }}</h3>
<ul>
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
It is only a matter of where do you make the cut on the loop.
These previous solutions are fantastic but luckily in late 2016, Jekyll added a group_by_exp
filter that can do this much more cleanly.
{% assign postsByYear =
site.posts | group_by_exp:"post", "post.date | date: '%Y'" %}
{% for year in postsByYear %}
<h1>{{ year.name }}</h1>
<ul>
{% for post in year.items %}
<li><a href="{{ post.url }}">{{ post.title }}-{{ post.date }}</a></li>
{% endfor %}
</ul>
{% endfor %}
Documentation can be found on the Jekyll Templates page.
It can be done with much, much less Liquid code than in the existing answers:
{% for post in site.posts %}
{% assign currentdate = post.date | date: "%Y" %}
{% if currentdate != date %}
<li id="y{{currentdate}}">{{ currentdate }}</li>
{% assign date = currentdate %}
{% endif %}
<li><a href="{{ post.url }}">{{ post.title }}</a></li>
{% endfor %}
This will return exactly the HTML specified in your question:
<li id="y2013">2013</li>
<li><a href="/2013/01/01/foo/">foo</a></li>
<li id="y2012">2012</li>
<li><a href="/2012/02/01/bar/">bar</a></li>
<li><a href="/2012/01/01/baz/">baz</a></li>
However, this is not the optimal solution, because the year numbers are "only" list items as well.
It's not much more Liquid code to put the year into a headline and to begin a new <ul>
for each year's posts:
{% for post in site.posts %}
{% assign currentdate = post.date | date: "%Y" %}
{% if currentdate != date %}
{% unless forloop.first %}</ul>{% endunless %}
<h1 id="y{{post.date | date: "%Y"}}">{{ currentdate }}</h1>
<ul>
{% assign date = currentdate %}
{% endif %}
<li><a href="{{ post.url }}">{{ post.title }}</a></li>
{% if forloop.last %}</ul>{% endif %}
{% endfor %}
The generated HTML:
<h1 id="y2013">2013</h1>
<ul>
<li><a href="/2013/01/01/foo/">foo</a></li>
</ul>
<h1 id="y2012">2012</h1>
<ul>
<li><a href="/2012/02/01/bar/">bar</a></li>
<li><a href="/2012/01/01/baz/">baz</a></li>
</ul>
You can also group by month and year instead (so that the headlines are February 2012
, January 2012
and so on).
To do this, you just need to replace date: "%Y"
(in the second line of both above examples) by date: "%B %Y"
.
(%B
is the full month name, see the documentation)
Some solutions above are very complex but then as @Trevor pointed out that we can levarage Jekyll's group_by_exp
filter. Also I liked the solution but what I needed was grouped by Year and then inside that list grouped by Month. So, I tweaked it a little bit.
{% assign postsByYear = site.posts | group_by_exp:"post", "post.date | date: '%Y'" %}
{% for year in postsByYear %}
<h1>{{ year.name }}</h1>
{% assign postsByMonth = year.items | group_by_exp:"post", "post.date | date: '%B'" %}
{% for month in postsByMonth %}
<h2>{{ month.name }}</h2>
<ul>
{% for post in month.items %}
<li><a href="{{ post.url }}">{{ post.title }}-{{ post.date }}</a></li>
{% endfor %}
</ul>
{% endfor %}
{% endfor %}