diff --git a/core/modules/datetime/config/schema/datetime.schema.yml b/core/modules/datetime/config/schema/datetime.schema.yml index 406a2fd..aade88f 100644 --- a/core/modules/datetime/config/schema/datetime.schema.yml +++ b/core/modules/datetime/config/schema/datetime.schema.yml @@ -1,5 +1,7 @@ # Schema for the configuration files of the Datetime module. +# datetime + field.storage_settings.datetime: type: mapping label: 'Datetime settings' @@ -83,3 +85,78 @@ field.widget.settings.datetime_datelist: field.widget.settings.datetime_default: type: mapping label: 'Datetime default display format settings' + +# daterange + +field.storage_settings.daterange: + type: mapping + label: 'Daterange settings' + mapping: + datetime_type: + type: string + label: 'Date type' + +field.field_settings.daterange: + type: mapping + label: 'Daterange settings' + +field.value.daterange: + type: mapping + label: 'Default value' + mapping: + default_date_type: + type: string + label: 'Default date type' + default_date: + type: string + label: 'Default date value' + +field.formatter.settings.daterange_base: + type: mapping + mapping: + separator: + type: string + label: 'Separator' + timezone_override: + type: string + label: 'Time zone override' + +field.formatter.settings.daterange_default: + type: field.formatter.settings.daterange_base + label: 'Daterange default display format settings' + mapping: + format_type: + type: string + label: 'Date format' + +field.formatter.settings.daterange_plain: + type: field.formatter.settings.daterange_base + label: 'Daterange plain display format settings' + +field.formatter.settings.daterange_custom: + type: field.formatter.settings.daterange_base + label: 'Daterange custom display format settings' + mapping: + date_format: + type: string + label: 'Date/time format' + translatable: true + translation context: 'PHP date format' + +field.widget.settings.daterange_datelist: + type: mapping + label: 'Daterange select list display format settings' + mapping: + increment: + type: integer + label: 'Time increments' + date_order: + type: string + label: 'Date part order' + time_type: + type: string + label: 'Time type' + +field.widget.settings.daterange_default: + type: mapping + label: 'Daterange default display format settings' diff --git a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateRangeCustomFormatter.php b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateRangeCustomFormatter.php new file mode 100644 index 0000000..3586fb7 --- /dev/null +++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateRangeCustomFormatter.php @@ -0,0 +1,106 @@ + DATETIME_DATETIME_STORAGE_FORMAT, + ) + parent::defaultSettings(); + } + + /** + * {@inheritdoc} + */ + public function viewElements(FieldItemListInterface $items, $langcode) { + $elements = []; + $separator = $this->getSetting('separator'); + + foreach ($items as $delta => $item) { + if ($item->start_date && $item->end_date) { + /** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */ + $start_date = $item->start_date; + /** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */ + $end_date = $item->end_date; + + if ($this->getFieldSetting('daterange_type') == 'date') { + // A date without time will pick up the current time, use the default. + datetime_date_default_time($start_date); + datetime_date_default_time($end_date); + } + + $this->setTimeZone($start_date); + $this->setTimeZone($end_date); + + $elements[$delta] = [ + '#cache' => [ + 'contexts' => [ + 'timezone', + ], + ], + '#plain_text' => $this->formatDate($start_date) . ' ' . $separator . ' ' . $this->formatDate($end_date), + ]; + } + } + + return $elements; + } + + /** + * {@inheritdoc} + */ + protected function formatDate($date) { + $format = $this->getSetting('date_format'); + $timezone = $this->getSetting('timezone_override'); + return $this->dateFormatter->format($date->getTimestamp(), 'custom', $format, $timezone != '' ? $timezone : NULL); + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $form = parent::settingsForm($form, $form_state); + + $form['date_format'] = array( + '#type' => 'textfield', + '#title' => $this->t('Date/time format'), + '#description' => $this->t('See the documentation for PHP date formats.'), + '#default_value' => $this->getSetting('date_format'), + ); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $summary = parent::settingsSummary(); + + $date = DrupalDateTime::createFromTimestamp(REQUEST_TIME); + $this->setTimeZone($date); + $summary[] = $this->t('Format: @display', array('@display' => $this->formatDate($date))); + + return $summary; + } + +} diff --git a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateRangeDefaultFormatter.php b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateRangeDefaultFormatter.php new file mode 100644 index 0000000..e239936 --- /dev/null +++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateRangeDefaultFormatter.php @@ -0,0 +1,141 @@ + 'medium', + ) + parent::defaultSettings(); + } + + /** + * {@inheritdoc} + */ + public function viewElements(FieldItemListInterface $items, $langcode) { + $elements = []; + $separator = $this->getSetting('separator'); + + foreach ($items as $delta => $item) { + if ($item->start_date && $item->end_date) { + /** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */ + $start_date = $item->start_date; + /** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */ + $end_date = $item->end_date; + + if ($this->getFieldSetting('daterange_type') == 'date') { + // A date without time will pick up the current time, use the default. + datetime_date_default_time($start_date); + datetime_date_default_time($end_date); + } + + // Create the ISO dates in Universal Time. + $start_iso_date = $start_date->format("Y-m-d\TH:i:s") . 'Z'; + $end_iso_date = $end_date->format("Y-m-d\TH:i:s") . 'Z'; + + $this->setTimeZone($start_date); + $this->setTimeZone($end_date); + + // Display the dates using theme datetime. + $elements[$delta] = [ + '#cache' => [ + 'contexts' => [ + 'timezone', + ], + ],[ + '#theme' => 'time', + '#text' => $this->formatDate($start_date), + '#html' => FALSE, + '#attributes' => [ + 'datetime' => $start_iso_date, + ] + ],[ + '#plain_text' => ' ' . $separator . ' ', + ],[ + '#theme' => 'time', + '#text' => $this->formatDate($end_date), + '#html' => FALSE, + '#attributes' => [ + 'datetime' => $end_iso_date, + ] + ], + ]; + if (!empty($item->_attributes)) { + $elements[$delta]['#attributes'] += $item->_attributes; + // Unset field item attributes since they have been included in the + // formatter output and should not be rendered in the field template. + unset($item->_attributes); + } + } + } + + return $elements; + } + + /** + * {@inheritdoc} + */ + protected function formatDate($date) { + $format_type = $this->getSetting('format_type'); + $timezone = $this->getSetting('timezone_override'); + return $this->dateFormatter->format($date->getTimestamp(), $format_type, '', $timezone != '' ? $timezone : NULL); + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $form = parent::settingsForm($form, $form_state); + + $format_types = $this->dateFormatStorage->loadMultiple(); + $options = []; + + foreach ($format_types as $type => $type_info) { + $format = $this->dateFormatter->format(REQUEST_TIME, $type); + $options[$type] = $type_info->label() . ' (' . $format . ')'; + } + + $form['format_type'] = array( + '#type' => 'select', + '#title' => t('Date format'), + '#description' => $this->t('Choose a format for displaying the dates. Be sure to set a format appropriate for the field, i.e. omitting time for a field that only has a date.'), + '#options' => $options, + '#default_value' => $this->getSetting('format_type'), + ); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $summary = parent::settingsSummary(); + + $date = DrupalDateTime::createFromTimestamp(REQUEST_TIME); + $this->setTimeZone($date); + $summary[] = $this->t('Format: @display', array('@display' => $this->formatDate($date))); + + return $summary; + } + +} diff --git a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateRangeFormatterBase.php b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateRangeFormatterBase.php new file mode 100644 index 0000000..f0b6b04 --- /dev/null +++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateRangeFormatterBase.php @@ -0,0 +1,174 @@ +dateFormatter = $date_formatter; + $this->dateFormatStorage = $date_format_storage; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $plugin_id, + $plugin_definition, + $configuration['field_definition'], + $configuration['settings'], + $configuration['label'], + $configuration['view_mode'], + $configuration['third_party_settings'], + $container->get('date.formatter'), + $container->get('entity.manager')->getStorage('date_format') + ); + } + + /** + * {@inheritdoc} + */ + public static function defaultSettings() { + return array( + 'separator' => '-', + 'timezone_override' => '', + ) + parent::defaultSettings(); + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $form = parent::settingsForm($form, $form_state); + + $form['separator'] = array( + '#type' => 'textfield', + '#title' => $this->t('Date separator'), + '#description' => $this->t('The string to separate the start and end dates'), + '#default_value' => $this->getSetting('separator'), + ); + + $form['timezone_override'] = array( + '#type' => 'select', + '#title' => $this->t('Time zone override'), + '#description' => $this->t('The time zone selected here will always be used'), + '#options' => system_time_zones(TRUE), + '#default_value' => $this->getSetting('timezone_override'), + ); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $summary = parent::settingsSummary(); + + if ($separator = $this->getSetting('separator')) { + $summary[] = $this->t('Separator: %separator', array('%separator' => $separator)); + } + + if ($override = $this->getSetting('timezone_override')) { + $summary[] = $this->t('Time zone: @timezone', array('@timezone' => $override)); + } + + return $summary; + } + + /** + * Creates a formatted date as a string. + * + * @param \Drupal\Core\Datetime\DrupalDateTime $date + * The date. + * + * @return string + * A formatted date range string using the chosen format. + */ + abstract protected function formatDate($date); + + /** + * Sets the proper time zone on a DrupalDateTime object for the current user. + * + * A DrupalDateTime object loaded from the database will have the UTC time + * zone applied to it. This method will apply the time zone for the current + * user, based on system and user settings. + * + * @see drupal_get_user_timezone() + * + * @param \Drupal\Core\Datetime\DrupalDateTime $date + * A DrupalDateTime object. + */ + protected function setTimeZone(DrupalDateTime $date) { + $date->setTimeZone(timezone_open(drupal_get_user_timezone())); + } + + /** + * Gets a settings array suitable for DrupalDateTime::format(). + * + * @return array + * The settings array that can be passed to DrupalDateTime::format(). + */ + protected function getFormatSettings() { + $settings = []; + + if ($this->getSetting('timezone_override') != '') { + $settings['timezone'] = $this->getSetting('timezone_override'); + } + + return $settings; + } + +} diff --git a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateRangePlainFormatter.php b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateRangePlainFormatter.php new file mode 100644 index 0000000..a548e05 --- /dev/null +++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateRangePlainFormatter.php @@ -0,0 +1,66 @@ +getSetting('separator'); + + foreach ($items as $delta => $item) { + if ($item->start_date && $item->end_date) { + /** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */ + $start_date = $item->start_date; + /** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */ + $end_date = $item->end_date; + + if ($this->getFieldSetting('daterange_type') == 'date') { + // A date without time will pick up the current time, use the default. + datetime_date_default_time($start_date); + datetime_date_default_time($end_date); + } + + $this->setTimeZone($start_date); + $this->setTimeZone($end_date); + + $elements[$delta] = [ + '#cache' => [ + 'contexts' => [ + 'timezone', + ], + ], + '#plain_text' => $this->formatDate($start_date) . ' ' . $separator . ' ' . $this->formatDate($end_date), + ]; + } + } + + return $elements; + } + + /** + * {@inheritdoc} + */ + protected function formatDate($date) { + $format = $this->getFieldSetting('daterange_type') == 'date' ? DATETIME_DATE_STORAGE_FORMAT : DATETIME_DATETIME_STORAGE_FORMAT; + $timezone = $this->getSetting('timezone_override'); + return $this->dateFormatter->format($date->getTimestamp(), 'custom', $format, $timezone != '' ? $timezone : NULL); + } + +} diff --git a/core/modules/datetime/src/Plugin/Field/FieldType/DateRangeFieldItemList.php b/core/modules/datetime/src/Plugin/Field/FieldType/DateRangeFieldItemList.php new file mode 100644 index 0000000..1f9c6bb --- /dev/null +++ b/core/modules/datetime/src/Plugin/Field/FieldType/DateRangeFieldItemList.php @@ -0,0 +1,115 @@ +getFieldDefinition()->getDefaultValueCallback())) { + $default_value = $this->getFieldDefinition()->getDefaultValueLiteral(); + + $element = array( + '#parents' => array('default_value_input'), + 'default_date_type' => array( + '#type' => 'select', + '#title' => t('Default dates'), + '#description' => t('Set a default value for these dates.'), + '#default_value' => isset($default_value[0]['default_date_type']) ? $default_value[0]['default_date_type'] : '', + '#options' => array( + static::DEFAULT_VALUE_NOW => t('Current date'), + static::DEFAULT_VALUE_CUSTOM => t('Relative date'), + ), + '#empty_value' => '', + ), + 'default_date' => array( + '#type' => 'textfield', + '#title' => t('Relative default value'), + '#description' => t("Describe a time by reference to the current day, like '+90 days' (90 days from the day the field is created) or '+1 Saturday' (the next Saturday). See strtotime for more details."), + '#default_value' => (isset($default_value[0]['default_date_type']) && $default_value[0]['default_date_type'] == static::DEFAULT_VALUE_CUSTOM) ? $default_value[0]['default_date'] : '', + '#states' => array( + 'visible' => array( + ':input[id="edit-default-value-input-default-date-type"]' => array('value' => static::DEFAULT_VALUE_CUSTOM), + ) + ) + ) + ); + + return $element; + } + } + + /** + * {@inheritdoc} + */ + public function defaultValuesFormValidate(array $element, array &$form, FormStateInterface $form_state) { + if ($form_state->getValue(['default_value_input', 'default_date_type']) == static::DEFAULT_VALUE_CUSTOM) { + $is_strtotime = @strtotime($form_state->getValue(array('default_value_input', 'default_date'))); + if (!$is_strtotime) { + $form_state->setErrorByName('default_value_input][default_date', t('The relative date value entered is invalid.')); + } + } + } + + /** + * {@inheritdoc} + */ + public function defaultValuesFormSubmit(array $element, array &$form, FormStateInterface $form_state) { + if ($form_state->getValue(array('default_value_input', 'default_date_type'))) { + if ($form_state->getValue(array('default_value_input', 'default_date_type')) == static::DEFAULT_VALUE_NOW) { + $form_state->setValueForElement($element['default_date'], static::DEFAULT_VALUE_NOW); + } + return array($form_state->getValue('default_value_input')); + } + return array(); + } + + /** + * {@inheritdoc} + */ + public static function processDefaultValue($default_value, FieldableEntityInterface $entity, FieldDefinitionInterface $definition) { + $default_value = parent::processDefaultValue($default_value, $entity, $definition); + + if (isset($default_value[0]['default_date_type'])) { + // A default value should be in the format and timezone used for date + // storage. + $date = new DrupalDateTime($default_value[0]['default_date'], DATETIME_STORAGE_TIMEZONE); + $storage_format = $definition->getSetting('daterange_type') == DateRangeItem::DATERANGE_TYPE_DATE ? DATERANGE_DATE_STORAGE_FORMAT : DATERANGE_DATETIME_STORAGE_FORMAT; + $value = $date->format($storage_format); + // We only provide a default value for the first item, as do all fields. + // Otherwise, there is no way to clear out unwanted values on multiple + // value fields. + $default_value = array( + array( + 'start_value' => $value, + 'start_date' => $date, + 'end_value' => $value, + 'end_date' => $date, + ) + ); + } + return $default_value; + } + +} diff --git a/core/modules/datetime/src/Plugin/Field/FieldType/DateRangeItem.php b/core/modules/datetime/src/Plugin/Field/FieldType/DateRangeItem.php new file mode 100644 index 0000000..6aca3b2 --- /dev/null +++ b/core/modules/datetime/src/Plugin/Field/FieldType/DateRangeItem.php @@ -0,0 +1,162 @@ + 'daterange', + ) + parent::defaultStorageSettings(); + } + + /** + * Value for the 'daterange_type' setting: store only a date. + */ + const DATERANGE_TYPE_DATE = 'date'; + + /** + * Value for the 'daterange_type' setting: store a date and time. + */ + const DATERANGE_TYPE_DATETIME = 'datetime'; + + /** + * {@inheritdoc} + */ + public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) { + $properties['start_value'] = DataDefinition::create('datetime_iso8601') + ->setLabel(t('Start date value')) + ->setRequired(TRUE); + + $properties['start_date'] = DataDefinition::create('any') + ->setLabel(t('Computed start date')) + ->setDescription(t('The computed start DateTime object.')) + ->setComputed(TRUE) + ->setClass('\Drupal\datetime\DateTimeComputed') + ->setSetting('date source', 'start_value'); + + $properties['end_value'] = DataDefinition::create('datetime_iso8601') + ->setLabel(t('End date value')) + ->setRequired(TRUE); + + $properties['end_date'] = DataDefinition::create('any') + ->setLabel(t('Computed end date')) + ->setDescription(t('The computed end DateTime object.')) + ->setComputed(TRUE) + ->setClass('\Drupal\datetime\DateTimeComputed') + ->setSetting('date source', 'end_value'); + + return $properties; + } + + /** + * {@inheritdoc} + */ + public static function schema(FieldStorageDefinitionInterface $field_definition) { + return array( + 'columns' => array( + 'start_value' => array( + 'description' => 'The start date value.', + 'type' => 'varchar', + 'length' => 20, + ), + 'end_value' => array( + 'description' => 'The end date value.', + 'type' => 'varchar', + 'length' => 20, + ), + ), + 'indexes' => array( + 'start_value' => array('start_value'), + 'end_value' => array('end_value'), + ), + ); + } + + /** + * {@inheritdoc} + */ + public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) { + $element = array(); + + $element['datetime_type'] = array( + '#type' => 'select', + '#title' => $this->t('Date type'), + '#description' => $this->t('Choose the type of date to create.'), + '#default_value' => $this->getSetting('datetime_type'), + '#options' => array( + static::DATERANGE_TYPE_DATETIME => t('Date and time'), + static::DATERANGE_TYPE_DATE => t('Date only'), + ), + '#disabled' => $has_data, + ); + + return $element; + } + + /** + * {@inheritdoc} + */ + public static function generateSampleValue(FieldDefinitionInterface $field_definition) { + $type = $field_definition->getSetting('daterange_type'); + + // Just pick a date in the past year. No guidance is provided by this Field + // type. + $start = REQUEST_TIME - mt_rand(0, 86400 * 365) - 86400; + $end = $start + 86400; + if ($type == static::DATERANGE_TYPE_DATE) { + $values['start_value'] = gmdate(DATETIME_DATE_STORAGE_FORMAT, $start); + $values['end_value'] = gmdate(DATETIME_DATE_STORAGE_FORMAT, $end); + } + else { + $values['start_value'] = gmdate(DATETIME_DATETIME_STORAGE_FORMAT, $start); + $values['end_value'] = gmdate(DATETIME_DATE_STORAGE_FORMAT, $end); + } + return $values; + } + + /** + * {@inheritdoc} + */ + public function isEmpty() { + $start_value = $this->get('start_value')->getValue(); + $end_value = $this->get('end_value')->getValue(); + return ($start_value === NULL || $start_value === '') && ($end_value === NULL || $end_value === ''); + } + + /** + * {@inheritdoc} + */ + public function onChange($property_name, $notify = TRUE) { + // Enforce that the computed date is recalculated. + if ($property_name == 'start_value') { + $this->start_date = NULL; + } + elseif ($property_name == 'end_value') { + $this->end_date = NULL; + } + parent::onChange($property_name, $notify); + } + +} diff --git a/core/modules/datetime/src/Plugin/Field/FieldWidget/DateRangeDatelistWidget.php b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateRangeDatelistWidget.php new file mode 100644 index 0000000..ffc54f5 --- /dev/null +++ b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateRangeDatelistWidget.php @@ -0,0 +1,155 @@ + '15', + 'date_order' => 'YMD', + 'time_type' => '24', + ) + parent::defaultSettings(); + } + + /** + * {@inheritdoc} + */ + public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { + $element = parent::formElement($items, $delta, $element, $form, $form_state); + + $date_order = $this->getSetting('date_order'); + + if ($this->getFieldSetting('daterange_type') == 'datetime') { + $time_type = $this->getSetting('time_type'); + $increment = $this->getSetting('increment'); + } + else { + $time_type = ''; + $increment = ''; + } + + // Set up the date part order array. + switch ($date_order) { + case 'YMD': + $date_part_order = array('year', 'month', 'day'); + break; + + case 'MDY': + $date_part_order = array('month', 'day', 'year'); + break; + + case 'DMY': + $date_part_order = array('day', 'month', 'year'); + break; + } + switch ($time_type) { + case '24': + $date_part_order = array_merge($date_part_order, array('hour', 'minute')); + break; + + case '12': + $date_part_order = array_merge($date_part_order, array('hour', 'minute', 'ampm')); + break; + + case 'none': + break; + } + + $element['start_value'] = array( + '#type' => 'datelist', + '#date_increment' => $increment, + '#date_part_order' => $date_part_order, + ) + $element['start_value']; + + $element['end_value'] = array( + '#type' => 'datelist', + '#date_increment' => $increment, + '#date_part_order' => $date_part_order, + ) + $element['end_value']; + + return $element; + } + + /** + * {@inheritdoc} + */ + function settingsForm(array $form, FormStateInterface $form_state) { + $element = parent::settingsForm($form, $form_state); + + $element['date_order'] = array( + '#type' => 'select', + '#title' => t('Date part order'), + '#default_value' => $this->getSetting('date_order'), + '#options' => array('MDY' => t('Month/Day/Year'), 'DMY' => t('Day/Month/Year'), 'YMD' => t('Year/Month/Day')), + ); + + if ($this->getFieldSetting('daterange_type') == 'datetime') { + $element['time_type'] = array( + '#type' => 'select', + '#title' => t('Time type'), + '#default_value' => $this->getSetting('time_type'), + '#options' => array('24' => t('24 hour time'), '12' => t('12 hour time')), + ); + + $element['increment'] = [ + '#type' => 'select', + '#title' => t('Time increments'), + '#default_value' => $this->getSetting('increment'), + '#options' => [ + 1 => t('1 minute'), + 5 => t('5 minute'), + 10 => t('10 minute'), + 15 => t('15 minute'), + 30 => t('30 minute'), + ], + ]; + } + else { + $element['time_type'] = array( + '#type' => 'hidden', + '#value' => 'none', + ); + + $element['increment'] = [ + '#type' => 'hidden', + '#value' => $this->getSetting('increment'), + ]; + } + + return $element; + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $summary = array(); + + $summary[] = t('Date part order: @order', array('@order' => $this->getSetting('date_order'))); + if ($this->getFieldSetting('daterange_type') == 'datetime') { + $summary[] = t('Time type: @time_type', array('@time_type' => $this->getSetting('time_type'))); + $summary[] = t('Time increments: @increment', array('@increment' => $this->getSetting('increment'))); + } + + return $summary; + } + +} diff --git a/core/modules/datetime/src/Plugin/Field/FieldWidget/DateRangeDefaultWidget.php b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateRangeDefaultWidget.php new file mode 100644 index 0000000..e304b1f --- /dev/null +++ b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateRangeDefaultWidget.php @@ -0,0 +1,102 @@ +dateStorage = $date_storage; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $plugin_id, + $plugin_definition, + $configuration['field_definition'], + $configuration['settings'], + $configuration['third_party_settings'], + $container->get('entity.manager')->getStorage('date_format') + ); + } + + /** + * {@inheritdoc} + */ + public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { + $element = parent::formElement($items, $delta, $element, $form, $form_state); + + debug([$this->getSettings()]); + + // Identify the type of date and time elements to use. + switch ($this->getFieldSetting('daterange_type')) { + case DateRangeItem::DATERANGE_TYPE_DATE: + $date_type = 'date'; + $time_type = 'none'; + $date_format = $this->dateStorage->load('html_date')->getPattern(); + $time_format = ''; + break; + + default: + $date_type = 'date'; + $time_type = 'time'; + $date_format = $this->dateStorage->load('html_date')->getPattern(); + $time_format = $this->dateStorage->load('html_time')->getPattern(); + break; + } + + $element['start_value'] += array( + '#date_date_format' => $date_format, + '#date_date_element' => $date_type, + '#date_date_callbacks' => array(), + '#date_time_format' => $time_format, + '#date_time_element' => $time_type, + '#date_time_callbacks' => array(), + ); + + $element['end_value'] += array( + '#date_date_format' => $date_format, + '#date_date_element' => $date_type, + '#date_date_callbacks' => array(), + '#date_time_format' => $time_format, + '#date_time_element' => $time_type, + '#date_time_callbacks' => array(), + ); + + return $element; + } + +} diff --git a/core/modules/datetime/src/Plugin/Field/FieldWidget/DateRangeWidgetBase.php b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateRangeWidgetBase.php new file mode 100644 index 0000000..28c7e12 --- /dev/null +++ b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateRangeWidgetBase.php @@ -0,0 +1,117 @@ + $this->t('Start'), + '#type' => 'datetime', + '#default_value' => NULL, + '#date_increment' => 1, + '#date_timezone' => drupal_get_user_timezone(), + '#required' => $element['#required'], + ); + + $element['end_value'] = array( + '#title' => $this->t('End'), + '#type' => 'datetime', + '#default_value' => NULL, + '#date_increment' => 1, + '#date_timezone' => drupal_get_user_timezone(), + '#required' => $element['#required'], + ); + + if ($items[$delta]->start_date) { + /** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */ + $start_date = $items[$delta]->start_date; + // The date was created and verified during field_load(), so it is safe to + // use without further inspection. + if ($this->getFieldSetting('daterange_type') == DateRangeItem::DATERANGE_TYPE_DATE) { + // A date without time will pick up the current time, use the default + // time. + datetime_date_default_time($start_date); + } + $start_date->setTimezone(new \DateTimeZone($element['start_value']['#date_timezone'])); + $element['start_value']['#default_value'] = $start_date; + + if ($items[$delta]->end_date) { + /** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */ + $end_date = $items[$delta]->end_date; + // The date was created and verified during field_load(), so it is safe to + // use without further inspection. + if ($this->getFieldSetting('daterange_type') == DateRangeItem::DATERANGE_TYPE_DATE) { + // A date without time will pick up the current time, use the default + // time. + datetime_date_default_time($end_date); + } + $end_date->setTimezone(new \DateTimeZone($element['end_value']['#date_timezone'])); + $element['end_value']['#default_value'] = $end_date; + } + } + + return $element; + } + + /** + * {@inheritdoc} + */ + public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { + // The widget form element type has transformed the value to a + // DrupalDateTime object at this point. We need to convert it back to the + // storage timezone and format. + foreach ($values as &$item) { + if (!empty($item['start_value']) && $item['start_value'] instanceof DrupalDateTime) { + /** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */ + $start_date = $item['start_value']; + switch ($this->getFieldSetting('daterange_type')) { + case DateRangeItem::DATERANGE_TYPE_DATE: + // If this is a date-only field, set it to the default time so the + // timezone conversion can be reversed. + datetime_date_default_time($start_date); + $format = DATETIME_DATE_STORAGE_FORMAT; + break; + + default: + $format = DATETIME_DATETIME_STORAGE_FORMAT; + break; + } + // Adjust the date for storage. + $start_date->setTimezone(new \DateTimezone(DATETIME_STORAGE_TIMEZONE)); + $item['start_value'] = $start_date->format($format); + + if (!empty($item['end_value']) && $item['end_value'] instanceof DrupalDateTime) { + /** @var \Drupal\Core\Datetime\DrupalDateTime $end_date */ + $end_date = $item['end_value']; + // Adjust the date for storage. + $end_date->setTimezone(new \DateTimezone(DATETIME_STORAGE_TIMEZONE)); + $item['end_value'] = $end_date->format($format); + } + } + } + return $values; + } + +} diff --git a/core/modules/datetime/src/Tests/DateRangeFieldTest.php b/core/modules/datetime/src/Tests/DateRangeFieldTest.php new file mode 100644 index 0000000..5ef4c71 --- /dev/null +++ b/core/modules/datetime/src/Tests/DateRangeFieldTest.php @@ -0,0 +1,155 @@ +config('system.date') + ->set('timezone.user.configurable', 0) + ->set('timezone.default', 'Asia/Tokyo') + ->save(); + + $web_user = $this->drupalCreateUser(array( + 'access content', + 'view test entity', + 'administer entity_test content', + 'administer entity_test form display', + 'administer content types', + 'administer node fields', + )); + $this->drupalLogin($web_user); + + // Create a field with settings to validate. + $field_name = Unicode::strtolower($this->randomMachineName()); + $this->fieldStorage = FieldStorageConfig::create(array( + 'field_name' => $field_name, + 'entity_type' => 'entity_test', + 'type' => 'daterange', + 'settings' => array('daterange_type' => 'date'), + )); + $this->fieldStorage->save(); + $this->field = FieldConfig::create([ + 'field_storage' => $this->fieldStorage, + 'bundle' => 'entity_test', + 'required' => TRUE, + ]); + $this->field->save(); + + entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default') + ->setComponent($field_name, array( + 'type' => 'daterange_default', + )) + ->save(); + + $this->defaultSettings = array( + 'separator' => '-', + 'timezone_override' => '', + ); + + $this->displayOptions = array( + 'type' => 'daterange_default', + 'label' => 'hidden', + 'settings' => array('format_type' => 'medium') + $this->defaultSettings, + ); + entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + } + + /** + * Tests date field functionality. + */ + function testDateRangeField() { + $this->fail('Write a real test.'); + } + + /** + * Tests date and time field. + */ + function testDatetimeRangeField() { + $this->fail('Write a real test.'); + } + + /** + * Tests Date Range List Widget functionality. + */ + function testDatelistWidget() { + $this->fail('Write a real test.'); + } + + /** + * Test default value functionality. + */ + function testDefaultValue() { + $this->fail('Write a real test.'); + } + + /** + * Test that invalid values are caught and marked as invalid. + */ + function testInvalidField() { + $this->fail('Write a real test.'); + } + + /** + * Tests that 'Date' field storage setting form is disabled if field has data. + */ + public function testDateStorageSettings() { + $this->fail('Write a real test.'); + } + +}