3 namespace Drupal\datetime_range\Plugin\Field\FieldWidget;
5 use Drupal\Core\Datetime\DrupalDateTime;
6 use Drupal\Core\Field\FieldItemListInterface;
7 use Drupal\Core\Form\FormStateInterface;
8 use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
9 use Drupal\datetime\Plugin\Field\FieldWidget\DateTimeWidgetBase;
10 use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
13 * Base class for the 'daterange_*' widgets.
15 class DateRangeWidgetBase extends DateTimeWidgetBase {
20 public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
21 $element = parent::formElement($items, $delta, $element, $form, $form_state);
23 // Wrap all of the select elements with a fieldset.
24 $element['#theme_wrappers'][] = 'fieldset';
26 $element['#element_validate'][] = [$this, 'validateStartEnd'];
27 $element['value']['#title'] = $this->t('Start date');
29 $element['end_value'] = [
30 '#title' => $this->t('End date'),
31 ] + $element['value'];
33 if ($items[$delta]->start_date) {
34 /** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */
35 $start_date = $items[$delta]->start_date;
36 $element['value']['#default_value'] = $this->createDefaultValue($start_date, $element['value']['#date_timezone']);
39 if ($items[$delta]->end_date) {
40 /** @var \Drupal\Core\Datetime\DrupalDateTime $end_date */
41 $end_date = $items[$delta]->end_date;
42 $element['end_value']['#default_value'] = $this->createDefaultValue($end_date, $element['end_value']['#date_timezone']);
51 public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
52 // The widget form element type has transformed the value to a
53 // DrupalDateTime object at this point. We need to convert it back to the
54 // storage timezone and format.
55 foreach ($values as &$item) {
56 if (!empty($item['value']) && $item['value'] instanceof DrupalDateTime) {
57 /** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */
58 $start_date = $item['value'];
59 switch ($this->getFieldSetting('datetime_type')) {
60 case DateRangeItem::DATETIME_TYPE_DATE:
61 $format = DateTimeItemInterface::DATE_STORAGE_FORMAT;
64 case DateRangeItem::DATETIME_TYPE_ALLDAY:
65 // All day fields start at midnight on the starting date, but are
66 // stored like datetime fields, so we need to adjust the time.
67 // This function is called twice, so to prevent a double conversion
68 // we need to explicitly set the timezone.
69 $start_date->setTimeZone(timezone_open(drupal_get_user_timezone()));
70 $start_date->setTime(0, 0, 0);
71 $format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
75 $format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
78 // Adjust the date for storage.
79 $start_date->setTimezone(new \DateTimezone(DateTimeItemInterface::STORAGE_TIMEZONE));
80 $item['value'] = $start_date->format($format);
83 if (!empty($item['end_value']) && $item['end_value'] instanceof DrupalDateTime) {
84 /** @var \Drupal\Core\Datetime\DrupalDateTime $end_date */
85 $end_date = $item['end_value'];
86 switch ($this->getFieldSetting('datetime_type')) {
87 case DateRangeItem::DATETIME_TYPE_DATE:
88 $format = DateTimeItemInterface::DATE_STORAGE_FORMAT;
91 case DateRangeItem::DATETIME_TYPE_ALLDAY:
92 // All day fields end at midnight on the end date, but are
93 // stored like datetime fields, so we need to adjust the time.
94 // This function is called twice, so to prevent a double conversion
95 // we need to explicitly set the timezone.
96 $end_date->setTimeZone(timezone_open(drupal_get_user_timezone()));
97 $end_date->setTime(23, 59, 59);
98 $format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
102 $format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
105 // Adjust the date for storage.
106 $end_date->setTimezone(new \DateTimezone(DateTimeItemInterface::STORAGE_TIMEZONE));
107 $item['end_value'] = $end_date->format($format);
115 * #element_validate callback to ensure that the start date <= the end date.
117 * @param array $element
118 * An associative array containing the properties and children of the
119 * generic form element.
120 * @param \Drupal\Core\Form\FormStateInterface $form_state
121 * The current state of the form.
122 * @param array $complete_form
123 * The complete form structure.
125 public function validateStartEnd(array &$element, FormStateInterface $form_state, array &$complete_form) {
126 $start_date = $element['value']['#value']['object'];
127 $end_date = $element['end_value']['#value']['object'];
129 if ($start_date instanceof DrupalDateTime && $end_date instanceof DrupalDateTime) {
130 if ($start_date->getTimestamp() !== $end_date->getTimestamp()) {
131 $interval = $start_date->diff($end_date);
132 if ($interval->invert === 1) {
133 $form_state->setError($element, $this->t('The @title end date cannot be before the start date', ['@title' => $element['#title']]));