(Also see my post on how to get individual values from Smart Date fields).
I've been working on a site with events, and tearing my hair out over how to print formatted dates with the correct timezone value in Twig templates, specifically node.html.twig and views-view-fields.html.twig.
The issue was first, getting the formatted date to display in the template; and secondly, when I managed to get it displaying, it was not in the correct timezone; it was off by several hours. The accepted answer to this StackExchange question was the key to cracking the problem.
Basically, Drupal internally stores dates in UTC format. When the date is displayed, it gets converted to the user-chosen or site timezone, depending on the site settings.
If you print the raw value of the field in a Twig template, you get the UTC time. What you need is the DrupalDateTime object, the correctly timezone-formatted value of the field.
Daterange field values
For a daterange field (which collects a start and end date), you can get those converted values separately like so (assuming your field's machine name is event_date):
In node.html.twig:
node.field_event_date.start_date
node.field_event_date.end_date
(NOT node.field_event_date.value
or node.field_event_date.end_value
, which will give you the unconverted UTC values).
In views-view-fields.html.twig:
row._entity.field_event_date.start_date
row._entity.field_event_date.end_date
Datetime field value
For a date or datetime field, which do not collect an end date, the value is date: node.field_event_date.date
or row._entity.field_event_date.date
.
Date fields on other entities
For other entities, the base will be different but the concept is the same, for example, for a daterange field on a Commerce product it's product_entity.field_date.start_date
.
Examples of usage
Node.html.twig
In the site I'm working on, I want the date to display differently on the event nodes based on whether it's a single-day or multi-day event. If it's single-day, it will display like this:
May 8, 2020 from 9:10 am to 11:10 am
Multi-day: May 21, 2019 9:00 am to May 28, 2019 5:00 pm
How to do this? First, we set up a couple of variables in the node template. We want variables for the start day and end day, formatted to a numerical string so they can be compared, excluding the time, because we only need to see if the day is different (the end time will always be different even if it's a single-day event).
Here are our variables:
{% set start_day = node.field_event_date.start_date|date("Ynj") %}
{% set end_day = node.field_event_date.end_date|date("Ynj") %}
We're taking those converted start and end date values and formatting them as a string with the 4-digit year, the single-digit month number, then the single-digit number of the day of the month (you can find all the possible values in the PHP date manual).
This creates a string like: "2019521". We can then use those values for comparison. If the end day is greater than the start day, we print the date one way; if not, it's a single-day event and we print it another way:
{% if end_day > start_day %}
{{ node.field_event_date.start_date|date("F j, Y g:i a") }}
{{ 'to'|t }}
{{ node.field_event_date.end_date|date("F j, Y g:i a") }}
{% else %}
{{ node.field_event_date.start_date|date("F j, Y") }}
{{ 'from'|t }}
{{ node.field_event_date.start_date|date("g:i a") }}
{{ 'to'|t }}
{{ node.field_event_date.end_date|date("g:i a") }}
{% endif %}
This results in "May 8, 2020 from 9:10 am to 11:10 am" for a single day event or "May 21, 2019 9:00 am to May 28, 2019 5:00 pm" for multi-day.
Note that |date
is a Twig filter. Instead of directly passing a date format to that filter, you can give it the value 'U' which converts it to a Unix timestamp, then add the |format_date
filter and pass it the machine name of a Drupal date format, e.g.:
{{ node.field_event_date.start_date|date('U')|format_date('long_date') }}
Views-view-fields.html.twig
Let's say we want to create a Views listing of events with a nicely-formatted calendar date block. This is how I achieved it in views-view-fields--events--page.html.twig:
<div class="cal--thumb">
<div class="cal--month">
{{ row._entity.field_event_date.start_date|date("F") }}
</div>
<div class="cal--day">
{{ row._entity.field_event_date.start_date|date("j") }}
</div>
<div class="cal--year">
{{ row._entity.field_event_date.start_date|date("Y") }}
</div>
</div>
This results in something like this (with CSS styling applied, of course):
Comments
Awesome! I was stuck at this point while getting start and end values. Thank you!
Glad to help!
I, too, was stuck on this for a Datetime Range. Thank you!
No problem, it seems like such a common need that I was surprised there wasn't more info out there about how to do it.
In a views--view-fields template, that syntax does not work for me. Using this (for a single-date field, not a range):
row._entity.field_start_date|date('F j','America/Los_Angeles')
I get the following error:
Object of class Drupal\datetime\Plugin\Field\FieldType\DateTimeFieldItemList could not be converted to string in twig_date_converter() (line 493 of /var/www/vendor/twig/twig/src/Extension/CoreExtension.php)
Hi DrupalNoob, you've got an extra double quote in the middle of your date there, and you're missing .date
. You also can't put the timezone name in there. From the PHP date manual it looks like the value to get the timezone is 'e'. So you should try something like row._entity.field_start_date.date|date('F j, e')
.
Well, that wasn't a double quote, it was single-comma-single. I thought I had tried the syntax you suggested, but I tried so many I lost count. In any case, it worked that time. Thanx!
Ah, I see now. In any case, glad the suggestion worked for you!
Thank you so much for this!
I have a more general question: How do you even find out that it's.. "row._entity.field_event_date.start_date"?
By now I'm pretty confident with Twig and have Twig debugging working in PhpStorm, but I don't understand how you find out where one can find the value you need in the render array. Is this something you can find out by dumping an array? Where do you start to look in situations like this?
Thank you!
I feel like I come back to this post about twice per month—super useful, so thanks for writing it. Hope you're doing well! :)