3 namespace Drupal\datetime\Plugin\views\filter;
5 use Drupal\Component\Datetime\DateTimePlus;
6 use Drupal\Core\Datetime\DateFormatterInterface;
7 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
8 use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
9 use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
10 use Drupal\views\FieldAPIHandlerTrait;
11 use Drupal\views\Plugin\views\filter\Date as NumericDate;
12 use Symfony\Component\DependencyInjection\ContainerInterface;
13 use Symfony\Component\HttpFoundation\RequestStack;
16 * Date/time views filter.
18 * Even thought dates are stored as strings, the numeric filter is extended
19 * because it provides more sensible operators.
21 * @ingroup views_filter_handlers
23 * @ViewsFilter("datetime")
25 class Date extends NumericDate implements ContainerFactoryPluginInterface {
27 use FieldAPIHandlerTrait;
30 * The date formatter service.
32 * @var \Drupal\Core\Datetime\DateFormatterInterface
34 protected $dateFormatter;
37 * Date format for SQL conversion.
41 * @see \Drupal\views\Plugin\views\query\Sql::getDateFormat()
43 protected $dateFormat = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
46 * Determines if the timezone offset is calculated.
50 protected $calculateOffset = TRUE;
53 * The request stack used to determine current time.
55 * @var \Symfony\Component\HttpFoundation\RequestStack
57 protected $requestStack;
60 * Constructs a new Date handler.
62 * @param array $configuration
63 * A configuration array containing information about the plugin instance.
64 * @param string $plugin_id
65 * The plugin ID for the plugin instance.
66 * @param mixed $plugin_definition
67 * The plugin implementation definition.
68 * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
69 * The date formatter service.
70 * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
71 * The request stack used to determine the current time.
73 public function __construct(array $configuration, $plugin_id, $plugin_definition, DateFormatterInterface $date_formatter, RequestStack $request_stack) {
74 parent::__construct($configuration, $plugin_id, $plugin_definition);
75 $this->dateFormatter = $date_formatter;
76 $this->requestStack = $request_stack;
78 $definition = $this->getFieldStorageDefinition();
79 if ($definition->getSetting('datetime_type') === DateTimeItem::DATETIME_TYPE_DATE) {
80 // Date format depends on field storage format.
81 $this->dateFormat = DateTimeItemInterface::DATE_STORAGE_FORMAT;
82 // Timezone offset calculation is not applicable to dates that are stored
84 $this->calculateOffset = FALSE;
91 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
96 $container->get('date.formatter'),
97 $container->get('request_stack')
102 * Override parent method, which deals with dates as integers.
104 protected function opBetween($field) {
105 $timezone = $this->getTimezone();
106 $origin_offset = $this->getOffset($this->value['min'], $timezone);
108 // Although both 'min' and 'max' values are required, default empty 'min'
109 // value as UNIX timestamp 0.
110 $min = (!empty($this->value['min'])) ? $this->value['min'] : '@0';
112 // Convert to ISO format and format for query. UTC timezone is used since
113 // dates are stored in UTC.
114 $a = new DateTimePlus($min, new \DateTimeZone($timezone));
115 $a = $this->query->getDateFormat($this->query->getDateField("'" . $this->dateFormatter->format($a->getTimestamp() + $origin_offset, 'custom', DateTimeItemInterface::DATETIME_STORAGE_FORMAT, DateTimeItemInterface::STORAGE_TIMEZONE) . "'", TRUE, $this->calculateOffset), $this->dateFormat, TRUE);
116 $b = new DateTimePlus($this->value['max'], new \DateTimeZone($timezone));
117 $b = $this->query->getDateFormat($this->query->getDateField("'" . $this->dateFormatter->format($b->getTimestamp() + $origin_offset, 'custom', DateTimeItemInterface::DATETIME_STORAGE_FORMAT, DateTimeItemInterface::STORAGE_TIMEZONE) . "'", TRUE, $this->calculateOffset), $this->dateFormat, TRUE);
119 // This is safe because we are manually scrubbing the values.
120 $operator = strtoupper($this->operator);
121 $field = $this->query->getDateFormat($this->query->getDateField($field, TRUE, $this->calculateOffset), $this->dateFormat, TRUE);
122 $this->query->addWhereExpression($this->options['group'], "$field $operator $a AND $b");
126 * Override parent method, which deals with dates as integers.
128 protected function opSimple($field) {
129 $timezone = $this->getTimezone();
130 $origin_offset = $this->getOffset($this->value['value'], $timezone);
132 // Convert to ISO. UTC timezone is used since dates are stored in UTC.
133 $value = new DateTimePlus($this->value['value'], new \DateTimeZone($timezone));
134 $value = $this->query->getDateFormat($this->query->getDateField("'" . $this->dateFormatter->format($value->getTimestamp() + $origin_offset, 'custom', DateTimeItemInterface::DATETIME_STORAGE_FORMAT, DateTimeItemInterface::STORAGE_TIMEZONE) . "'", TRUE, $this->calculateOffset), $this->dateFormat, TRUE);
136 // This is safe because we are manually scrubbing the value.
137 $field = $this->query->getDateFormat($this->query->getDateField($field, TRUE, $this->calculateOffset), $this->dateFormat, TRUE);
138 $this->query->addWhereExpression($this->options['group'], "$field $this->operator $value");
142 * Get the proper time zone to use in computations.
144 * Date-only fields do not have a time zone associated with them, so the
145 * filter input needs to use UTC for reference. Otherwise, use the time zone
146 * for the current user.
149 * The time zone name.
151 protected function getTimezone() {
152 return $this->dateFormat === DateTimeItemInterface::DATE_STORAGE_FORMAT
153 ? DateTimeItemInterface::STORAGE_TIMEZONE
154 : drupal_get_user_timezone();
158 * Get the proper offset from UTC to use in computations.
160 * @param string $time
161 * A date/time string compatible with \DateTime. It is used as the
162 * reference for computing the offset, which can vary based on the time
164 * @param string $timezone
165 * The time zone that $time is in.
168 * The computed offset in seconds.
170 protected function getOffset($time, $timezone) {
171 // Date-only fields do not have a time zone or offset from UTC associated
172 // with them. For relative (i.e. 'offset') comparisons, we need to compute
173 // the user's offset from UTC for use in the query.
175 if ($this->dateFormat === DateTimeItemInterface::DATE_STORAGE_FORMAT && $this->value['type'] === 'offset') {
176 $origin_offset = $origin_offset + timezone_offset_get(new \DateTimeZone(drupal_get_user_timezone()), new \DateTime($time, new \DateTimeZone($timezone)));
179 return $origin_offset;