Ever wanted to add a condition on the value of a date field on an entity query in Drupal 8? Here is how it works reliably.
For Drupal 8.5.x and later:
use Drupal\Core\Datetime\DrupalDateTime; use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; // Get a date string suitable for use with entity query. $date = new DrupalDateTime(); $date->setTimezone(new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE)); $date = $date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT); // Set the condition. $query->condition('field_date.value', $date, '>');
For Drupal 8.4.x and earlier:
use Drupal\Core\Datetime\DrupalDateTime; // Get a date string suitable for use with entity query. $date = new DrupalDateTime(); $date->setTimezone(new \DateTimeZone(DATETIME_STORAGE_TIMEZONE)); $date = $date->format(DATETIME_DATETIME_STORAGE_FORMAT); // Set the condition. $query->condition('field_date.value', $date, '>');
The trick is to use a DrupalDateTime object, on which we set the proper time zone used by the storage (provided by the constant DATETIME_STORAGE_TIMEZONE
) and format that in the proper date format used by the storage (provided by the constant DATETIME_DATETIME_STORAGE_FORMAT
). That way, we'll get reliable results. In the example above I just wanted to filter by the current time, but you can pass a date string to the constructor of DrupalDateTime as well, if you want something else.
Please note: Starting with Drupal 8.5.x, the constants mentioned above have been deprecated in favor of constants on a new DateTimeItemInterface
. They will be removed in Drupal 9.x.
You can also get these objects directly from a date field's value by using getDateTime():
foreach ($node->field_date_range as $item) { foreach ($item as $index => $value) { $date = $value->getDateTime(); } }
If you're using experimental date range fields, another thing you need to know is, that the end date is stored in an end_value
column.
Here's a more complete example querying for event nodes in the future:
use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Language\LanguageInterface; // @note: In Drupal 8.5.x and later also include the following line: use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; $storage = \Drupal::service('entity_type.manager')->getStorage('node'); $query = $storage->getQuery() ->condition('type', 'event') ->condition('status', 1) ->sort('field_date_range.value', 'ASC') ->sort('field_date_range.end_value', 'ASC') ->sort('created', 'ASC'); $query->condition('langcode', [ \Drupal::service('language_manager')->getCurrentLanguage()->getId(), LanguageInterface::LANGCODE_NOT_APPLICABLE, LanguageInterface::LANGCODE_NOT_SPECIFIED, ], 'IN'); // @note BEGIN: Code for Drupal 8.5.x and later: $date = new DrupalDateTime(); $date->setTimezone(new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE)); $date = $date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT); // @node END: Code for Drupal 8.5.x and later. // @note BEGIN: Code for Drupal 8.4.x and earlier: $date = new DrupalDateTime(); $date->setTimezone(new \DateTimeZone(DATETIME_STORAGE_TIMEZONE)); $date = $date->format(DATETIME_DATETIME_STORAGE_FORMAT); // @node END: Code for Drupal 8.4.x and earlier. $and = $query->andConditionGroup() ->condition('field_date_range.value', $date, '>') ->notExists('field_date_range.end_value'); $or = $query->orConditionGroup() ->condition($and) ->condition('field_date_range.end_value', $date, '>'); $query->condition($or); $ids = $query->execute(); $nodes = $storage->loadMultiple($ids);
Note, that there's currently an issue in Drupal Core with nested condition groups in entity query, which are used in the example above.
Update: This post has been updated to reflect changes in Drupal 8.5.x (see Global constants in datetime.module are deprecated and DateTimeItemInterface has been introduced).