Drupal 8 Theming: How to Render Custom Datetime formats For Events With Twig
Working with custom datetime elements in Drupal can sometimes be a bit tricky when you need a very specific date format to render. Even trickier can be date ranges that span multiple days. Add on top of that, date ranges that span over 2 different months and you might have your work cut out for you.
I am currently working on theming a Drupal 8 site that will feature events and event dates in a prominent way. In this article, I will create a recipe for how I am coding this within a Twig template. The context here is a landing page view where we show a listing of events with a basic date - month, day, and year.
The format I want to achieve in the end is:
- Multi-Day Event - e.g. Jan 29 - 31 2020
- Multi-Day Event (spanning over 2 different months) e.g. Jan 29 - Feb 03 2020
- One Day Event - e.g. Jan 29 2020
We'll be using Twig's date formatter so if you need a worldwide date format, i.e. non-American, it will be a case of just re-arranging when we code up our custom formats. For example:
format_date('custom', 'M d Y')
would be:
format_date('custom', 'd M Y')
Getting started
For this project, I am using Drupal 8, a few core modules, and one contrib module. This article assumes the following knowledge:
- Installing Drupal modules with Composer
- Setting up a local development environment (I use Docksal)
- Using Xdebug in your IDE (I use PHPStorm)
- Knowledge of writing Twig code
- Knowledge of basic Drupal 8 site building
Core
- Drupal core 8.7.11
- Datetime
- Datetime Range
Contrib
- Twig Xdebug (Optional)
As you can see, we have Datetime as well as Datetime Range which allows us to have a field formatter for event dates that span over multiple days. Once you're up and running, get twig_xdebug module with composer:
composer require drupal/twig_xdebug
Now, enable the modules:
drush en datetime datetime_range twig_xdebug -y
Create an event content type with date field
The next step is to create an event content type, Events, with a date field using Date range
as the type. We don't need to be concerned with the display settings here as we will be doing our formatting in our Twig template.
Create a custom view mode / events view
The next step is to create a custom view mode for our events landing page. I'll call it "Event List." That custom view modes are now a part of D8 core is a nice plus here. Now create a basic view for the event content type and set the format to Content > Event List
, whereby we use the new view mode just created. For the view, we can choose either a page or block display depending on the use case.
Create a custom Twig template
The next step is to create a few event nodes so we can load up our new events list landing page and start to debug. The first thing is to make sure you have Twig template debugging enabled in your local development environment. Since we want a custom template for theming, I look at the twig template suggestion debugging output and see this:
<!-- FILE NAME SUGGESTIONS:
* node--view--events--block-1.html.twig
* node--view--events.html.twig
* node--3--event-list.html.twig
* node--3.html.twig
* node--events--event-list.html.twig
* node--events.html.twig
* node--event-list.html.twig
x node.html.twig
-->
As you can see from the debug output above, we have a nice variety of template suggestions to choose from. I'll use node--events--event-list.html.twig
which is specific to the content type, view, and view mode we are using.
Note that a huge advantage of theming views in this manner using view modes is that you can theme as if it were a regular node template. In that regard, I grab Classy's basic node.html.twig
file and place it in my custom theme's templates folder using the custom name as above. Now run drush cr
and re-check the Twig template output and ensure that the new template has taken effect.
Debug with Twig Xdebug
For debugging with Twig, I like to use Twig Xdebug
as mentioned above, which piggybacks on top of Xdebug so you can debug right in your Twig template with one line of code, {{ breakpoint() }}
.
When I start to debug, the output provides me with a wealth of information that we can leverage to create some custom date variables right within our twig template. (Note that you could also use standard Xdebug within a .theme
or .module
file via a preprocess function.)
$context["content"]["field_event_date"][0]["start_date"]["#attributes"]["datetime"] = 2020-02-19T12:00:00Z
From the above debugging output, we have the date and time in what's known as UTC Time ISO-8601 Format. We'll be converting that later to a Unix timestamp with Twig so that in turn, we can create custom, nicely formatted dates.
Setting variables in Twig
Now that we have debugging info, we can set some variables right in Twig like this:
{# Define event start / end dates. #}
{% set event_start_date = content.field_event_date.0.start_date["#attributes"]["datetime"] %}
{% set event_end_date = content.field_event_date.0.end_date["#attributes"]["datetime"] %}
Now we will do some comparisons to check if the event is a single day, muti-day, or a muti-day event that takes place in two different months.
First we will check for a single day event by comparing the start and end day.
{% if event_start_date | date('U') | format_date('custom', 'd') == event_end_date | date('U') | format_date('custom', 'd') %}
From the above code, we are doing a few things. First, we convert the UTC ISO formatted date into a Unix timestamp and then from there, we leverage a custom format to compare the event start and end day using Twig's comparison operator, ==
.
If this is true, then we write the twig code as:
{# Check for and render a single day date. #}
{{ event_start_date | date('U') | format_date('custom', 'M d Y') }}
... which will render like this Feb 29 2020
. Next, we will add some logic if the event is muti-day and takes place within the same month:
{# If the start date month and end date month match. #}
{% elseif event_start_date | date('U') | format_date('custom', 'M') == event_end_date | date('U') | format_date('custom', 'M') %}
{{ event_start_date | date('U') | format_date('custom', 'M d - ') }}
{{ event_end_date | date('U') | format_date('custom', 'd Y') }}
For the above code, we once again use a comparison operator to check if the month start and end date is the same. If so, then the date renders within here as Jan 12 - 16 2020
. Finally, if the event takes place within two different months, we add the end date month to our custom date formatter:
{# If the start date month and end date month DO NOT match. #}
{% elseif event_start_date | date('U') | format_date('custom', 'M') != event_end_date | date('U') | format_date('custom', 'M') %}
{{ event_start_date | date('U') | format_date('custom', 'M d - ') }}
{{ event_end_date | date('U') | format_date('custom', 'M d Y') }}
This will render as Jan 29 - Feb 03 2020
. And there you have it, nicely formatted custom event dates all within a Twig template.
Summary
I should note that Twig's custom date formats draw from PHP datetime so when you see something like format_date('custom', 'M d Y')
, those time formats might seem familiar. Coding up dates in Twig in this manner was a great learning experiment for me. As with all things Drupal, there are so many different ways to achieve the same end result so I am sure there are no doubt other ways to do this. In fact, probably the Date and time UI within Drupal would probably suffice in most cases as you can create custom date formats in there as well. In the end, this was a great learning experience for me.