Excessively Adequate

Displaying the Time Elapsed Since Publishing a Jekyll Post

Static site generators like Jekyll produce significantly snappier sites than your run-of-the-mill content management system – but at a cost: By definition, there cannot be any server-side dynamic content. That means: no comments, no suggestions based on browsing activity, and (more trivially) no way of displaying how long ago a post was published.

When I first started this blog, I happily used jekyll-timeago until I realized – embarrassingly late – that the “posted N days ago” line below every post won’t update until the page is regenerated, which happens pretty much only whenever I publish a new post because of the way I deploy my blog.

JavaScript to the rescue!

If the problem can’t be solved on the server, let’s make the client do the work instead! In this post, I’ll show you how.

Somewhere in your theme’s _layouts/post.html (or similar), you probably have some code1 that outputs the date on which a given post was published:

<time datetime="{{ page.date | date_to_xmlschema }}">
    posted on {{ page.date | date: "%B %-d, %Y" }}
</time>

Replace it with the following snippet:

<time id="postedon" datetime="{{ page.date | date_to_xmlschema }}">
    posted <span id="postedago"></span>
    on {{ page.date | date: "%B %-d, %Y" }}
</time>

The id attributes allows for convenient access to the <time> and <span> elements by means of the JavaScript function getElementById(). In addition, the inserted <span> serves as a placeholder for the “N days ago” text we’ll add using JavaScript2.

Now, with the markup in place, we’ll need to actually make it work. Add the following piece of JavaScript code3 to your _layouts/post.html file, either inside a <script> element or in an external file included via <script src="...">. Note that you need to paste it after the above code:

function ago(date) {
    function render(n, unit) {
        return n + " " + unit + ((n == 1) ? "" : "s") + " ago";
    }

    var seconds = Math.floor((new Date() - date) / 1000);

    var interval = Math.floor(seconds / (60 * 60 * 24 * 365));
    if (Math.floor(seconds / (60 * 60 * 24 * 30 * 365)) >= 1) {
        return render(interval, "year");
    }
    interval = Math.floor(seconds / (60 * 60 * 24 * 30));
    if (interval >= 1) {
        return render(interval, "month");
    }
    interval = Math.floor(seconds / (60 * 60 * 24));
    if (interval >= 1) {
        return render(interval, "day");
    }
    interval = Math.floor(seconds / (60 * 60));
    if (interval >= 1) {
        return render(interval, "hour");
    }
    interval = Math.floor(seconds / 60);
    if (interval >= 1) {
        return render(interval, "minute");
    }
    interval = Math.floor(seconds);
    return render(interval, "second");
}

var date = Date.parse(document.getElementById("postedon").getAttribute("datetime"));
document.getElementById("postedago").innerHTML = ago(date);

I’m sure the ago() function could be refactored4 to be more elegant, but it’s reasonably self-explanatory and works fine the way it is.

Summary

After incorporating and deploying the changes outlined above, the posts on your Jekyll site will be annotated with a human-readable representation how long ago they were published. This is an example of progressive enhancement: users who choose to disable JavaScript will still see the absolute date, so they aren’t really “losing” anything.

  1. For example, my theme has something along those lines in the meta section right below the title of each post. 

  2. If the user’s browser doesn’t speak JavaScript (or, more commonly, if the user chooses to disable it), the <span> won’t show up and they won’t know that anything’s missing. The cool kids call this kind of approach progressive enhancement

  3. Loosely based on a Stack Overflow answer by Sky Sanders

  4. Indeed – a while after the initial publication of this post, someone asked a question on Stack Overflow regarding their Jekyll setup, which included this very function, and some folks responded with more concise solutions