diff --git a/core/modules/datetime/config/schema/datetime.schema.yml b/core/modules/datetime/config/schema/datetime.schema.yml
index 406a2fd..e627ce0 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:
+    daterange_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/datetime.views.inc b/core/modules/datetime/datetime.views.inc
index d3b0d18..b1742cf 100644
--- a/core/modules/datetime/datetime.views.inc
+++ b/core/modules/datetime/datetime.views.inc
@@ -11,42 +11,44 @@
  * Implements hook_field_views_data().
  */
 function datetime_field_views_data(FieldStorageConfigInterface $field_storage) {
-  // @todo This code only covers configurable fields, handle base table fields
-  //   in https://www.drupal.org/node/2489476.
-  $data = views_field_default_views_data($field_storage);
-  foreach ($data as $table_name => $table_data) {
-    // Set the 'datetime' filter type.
-    $data[$table_name][$field_storage->getName() . '_value']['filter']['id'] = 'datetime';
+  if ($field_storage->getType() == 'datetime') {
+    // @todo This code only covers configurable fields, handle base table fields
+    //   in https://www.drupal.org/node/2489476.
+    $data = views_field_default_views_data($field_storage);
+    foreach ($data as $table_name => $table_data) {
+      // Set the 'datetime' filter type.
+      $data[$table_name][$field_storage->getName() . '_value']['filter']['id'] = 'datetime';
 
-    // Set the 'datetime' argument type.
-    $data[$table_name][$field_storage->getName() . '_value']['argument']['id'] = 'datetime';
+      // Set the 'datetime' argument type.
+      $data[$table_name][$field_storage->getName() . '_value']['argument']['id'] = 'datetime';
 
-    // Create year, month, and day arguments.
-    $group = $data[$table_name][$field_storage->getName() . '_value']['group'];
-    $arguments = [
-      // Argument type => help text.
-      'year' => t('Date in the form of YYYY.'),
-      'month' => t('Date in the form of MM (01 - 12).'),
-      'day' => t('Date in the form of DD (01 - 31).'),
-      'week' => t('Date in the form of WW (01 - 53).'),
-      'year_month' => t('Date in the form of YYYYMM.'),
-      'full_date' => t('Date in the form of CCYYMMDD.'),
-    ];
-    foreach ($arguments as $argument_type => $help_text) {
-      $data[$table_name][$field_storage->getName() . '_value_' . $argument_type] = [
-        'title' => $field_storage->getLabel() . ' (' . $argument_type . ')',
-        'help' => $help_text,
-        'argument' => [
-          'field' => $field_storage->getName() . '_value',
-          'id' => 'datetime_' . $argument_type,
-        ],
-        'group' => $group,
+      // Create year, month, and day arguments.
+      $group = $data[$table_name][$field_storage->getName() . '_value']['group'];
+      $arguments = [
+        // Argument type => help text.
+        'year' => t('Date in the form of YYYY.'),
+        'month' => t('Date in the form of MM (01 - 12).'),
+        'day' => t('Date in the form of DD (01 - 31).'),
+        'week' => t('Date in the form of WW (01 - 53).'),
+        'year_month' => t('Date in the form of YYYYMM.'),
+        'full_date' => t('Date in the form of CCYYMMDD.'),
       ];
+      foreach ($arguments as $argument_type => $help_text) {
+        $data[$table_name][$field_storage->getName() . '_value_' . $argument_type] = [
+          'title' => $field_storage->getLabel() . ' (' . $argument_type . ')',
+          'help' => $help_text,
+          'argument' => [
+            'field' => $field_storage->getName() . '_value',
+            'id' => 'datetime_' . $argument_type,
+          ],
+          'group' => $group,
+        ];
+      }
+
+      // Set the 'datetime' sort handler.
+      $data[$table_name][$field_storage->getName() . '_value']['sort']['id'] = 'datetime';
     }
 
-    // Set the 'datetime' sort handler.
-    $data[$table_name][$field_storage->getName() . '_value']['sort']['id'] = 'datetime';
+    return $data;
   }
-
-  return $data;
 }
diff --git a/core/modules/datetime/src/DateTimeComputed.php b/core/modules/datetime/src/DateTimeComputed.php
index 6939994..49f0512 100644
--- a/core/modules/datetime/src/DateTimeComputed.php
+++ b/core/modules/datetime/src/DateTimeComputed.php
@@ -40,10 +40,12 @@ public function getValue($langcode = NULL) {
       return $this->date;
     }
 
+    /** @var \Drupal\Core\Field\FieldItemBase $item */
     $item = $this->getParent();
     $value = $item->{($this->definition->getSetting('date source'))};
+    $type = $item->getFieldDefinition()->getType();
 
-    $storage_format = $item->getFieldDefinition()->getSetting('datetime_type') == 'date' ? DATETIME_DATE_STORAGE_FORMAT : DATETIME_DATETIME_STORAGE_FORMAT;
+    $storage_format = $item->getFieldDefinition()->getSetting($type . '_type') == 'date' ? DATETIME_DATE_STORAGE_FORMAT : DATETIME_DATETIME_STORAGE_FORMAT;
     try {
       $date = DrupalDateTime::createFromFormat($storage_format, $value, DATETIME_STORAGE_TIMEZONE);
       if ($date instanceof DrupalDateTime && !$date->hasErrors()) {
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..a0c107f
--- /dev/null
+++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateRangeCustomFormatter.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace Drupal\datetime\Plugin\Field\FieldFormatter;
+
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Plugin implementation of the 'Custom' formatter for 'daterange' fields.
+ *
+ * @FieldFormatter(
+ *   id = "daterange_custom",
+ *   label = @Translation("Custom"),
+ *   field_types = {
+ *     "daterange"
+ *   }
+ *)
+ */
+class DateRangeCustomFormatter extends DateRangeFormatterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultSettings() {
+    return [
+      'date_format' => 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'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Date/time format'),
+      '#description' => $this->t('See <a href="http://php.net/manual/function.date.php" target="_blank">the documentation for PHP date formats</a>.'),
+      '#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', ['@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..dd0a19d
--- /dev/null
+++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateRangeDefaultFormatter.php
@@ -0,0 +1,141 @@
+<?php
+
+namespace Drupal\datetime\Plugin\Field\FieldFormatter;
+
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Plugin implementation of the 'Default' formatter for 'daterange' fields.
+ *
+ * @FieldFormatter(
+ *   id = "daterange_default",
+ *   label = @Translation("Default"),
+ *   field_types = {
+ *     "daterange"
+ *   }
+ * )
+ */
+class DateRangeDefaultFormatter extends DateRangeFormatterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultSettings() {
+    return [
+      'format_type' => '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'] = [
+      '#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', ['@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..9db9053
--- /dev/null
+++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateRangeFormatterBase.php
@@ -0,0 +1,174 @@
+<?php
+
+namespace Drupal\datetime\Plugin\Field\FieldFormatter;
+
+use Drupal\Core\Datetime\DateFormatterInterface;
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FormatterBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+
+/**
+ * Base class for 'DateRange Field formatter' plugin implementations.
+ */
+abstract class DateRangeFormatterBase extends FormatterBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The date formatter service.
+   *
+   * @var \Drupal\Core\Datetime\DateFormatterInterface
+   */
+  protected $dateFormatter;
+
+  /**
+   * The date format entity storage.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $dateFormatStorage;
+
+  /**
+   * Constructs a new DateTimeDefaultFormatter.
+   *
+   * @param string $plugin_id
+   *   The plugin_id for the formatter.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+   *   The definition of the field to which the formatter is associated.
+   * @param array $settings
+   *   The formatter settings.
+   * @param string $label
+   *   The formatter label display setting.
+   * @param string $view_mode
+   *   The view mode.
+   * @param array $third_party_settings
+   *   Third party settings.
+   * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
+   *   The date formatter service.
+   * @param \Drupal\Core\Entity\EntityStorageInterface $date_format_storage
+   *   The date format entity storage.
+   */
+  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, DateFormatterInterface $date_formatter, EntityStorageInterface $date_format_storage) {
+    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
+
+    $this->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 [
+      'separator' => '-',
+      'timezone_override' => '',
+    ] + parent::defaultSettings();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form, FormStateInterface $form_state) {
+    $form = parent::settingsForm($form, $form_state);
+
+    $form['separator'] = [
+      '#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'] = [
+      '#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', ['%separator' => $separator]);
+    }
+
+    if ($override = $this->getSetting('timezone_override')) {
+      $summary[] = $this->t('Time zone: @timezone', ['@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 @@
+<?php
+
+namespace Drupal\datetime\Plugin\Field\FieldFormatter;
+
+use Drupal\Core\Field\FieldItemListInterface;
+
+/**
+ * Plugin implementation of the 'Plain' formatter for 'daterange' fields.
+ *
+ * @FieldFormatter(
+ *   id = "daterange_plain",
+ *   label = @Translation("Plain"),
+ *   field_types = {
+ *     "daterange"
+ *   }
+ *)
+ */
+class DateRangePlainFormatter extends DateRangeFormatterBase {
+
+  /**
+   * {@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->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..fd4904b
--- /dev/null
+++ b/core/modules/datetime/src/Plugin/Field/FieldType/DateRangeFieldItemList.php
@@ -0,0 +1,115 @@
+<?php
+
+namespace Drupal\datetime\Plugin\Field\FieldType;
+
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldItemList;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Represents a configurable entity daterange field.
+ */
+class DateRangeFieldItemList extends FieldItemList {
+
+  /**
+   * Defines the default value as now.
+   */
+  const DEFAULT_VALUE_NOW = 'now';
+
+  /**
+   * Defines the default value as relative.
+   */
+  const DEFAULT_VALUE_CUSTOM = 'relative';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultValuesForm(array &$form, FormStateInterface $form_state) {
+    if (empty($this->getFieldDefinition()->getDefaultValueCallback())) {
+      $default_value = $this->getFieldDefinition()->getDefaultValueLiteral();
+
+      $element = [
+        '#parents' => ['default_value_input'],
+        'default_date_type' => [
+          '#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' => [
+            static::DEFAULT_VALUE_NOW => t('Current date'),
+            static::DEFAULT_VALUE_CUSTOM => t('Relative date'),
+          ],
+          '#empty_value' => '',
+        ],
+        'default_date' => [
+          '#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 <a href=\"http://php.net/manual/function.strtotime.php\">strtotime</a> 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' => [
+            'visible' => [
+              ':input[id="edit-default-value-input-default-date-type"]' => ['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(['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(['default_value_input', 'default_date_type'])) {
+      if ($form_state->getValue(['default_value_input', 'default_date_type']) == static::DEFAULT_VALUE_NOW) {
+        $form_state->setValueForElement($element['default_date'], static::DEFAULT_VALUE_NOW);
+      }
+      return [$form_state->getValue('default_value_input')];
+    }
+    return [];
+  }
+
+  /**
+   * {@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 ? DATETIME_DATE_STORAGE_FORMAT : DATETIME_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 = [
+        [
+          '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..3e8aafa
--- /dev/null
+++ b/core/modules/datetime/src/Plugin/Field/FieldType/DateRangeItem.php
@@ -0,0 +1,162 @@
+<?php
+
+namespace Drupal\datetime\Plugin\Field\FieldType;
+
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\TypedData\DataDefinition;
+use Drupal\Core\Field\FieldItemBase;
+
+/**
+ * Plugin implementation of the 'daterange' field type.
+ *
+ * @FieldType(
+ *   id = "daterange",
+ *   label = @Translation("Date Range"),
+ *   description = @Translation("Create and store date ranges."),
+ *   default_widget = "daterange_default",
+ *   default_formatter = "daterange_default",
+ *   list_class = "\Drupal\datetime\Plugin\Field\FieldType\DateRangeFieldItemList"
+ * )
+ */
+class DateRangeItem extends FieldItemBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultStorageSettings() {
+    return [
+      'daterange_type' => 'datetime',
+    ] + 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 [
+      'columns' => [
+        'start_value' => [
+          'description' => 'The start date value.',
+          'type' => 'varchar',
+          'length' => 20,
+        ],
+        'end_value' => [
+          'description' => 'The end date value.',
+          'type' => 'varchar',
+          'length' => 20,
+        ],
+      ],
+      'indexes' => [
+        'start_value' => ['start_value'],
+        'end_value' => ['end_value'],
+      ],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
+    $element = [];
+
+    $element['daterange_type'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Date type'),
+      '#description' => $this->t('Choose the type of date to create.'),
+      '#default_value' => $this->getSetting('daterange_type'),
+      '#options' => [
+        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..4dea3e7
--- /dev/null
+++ b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateRangeDatelistWidget.php
@@ -0,0 +1,157 @@
+<?php
+
+namespace Drupal\datetime\Plugin\Field\FieldWidget;
+
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Plugin implementation of the 'daterange_datelist' widget.
+ *
+ * @FieldWidget(
+ *   id = "daterange_datelist",
+ *   label = @Translation("Select list"),
+ *   field_types = {
+ *     "daterange"
+ *   }
+ * )
+ */
+class DateRangeDatelistWidget extends DateRangeWidgetBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultSettings() {
+    return array(
+      'increment' => '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) {
+      default:
+      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) {
+      default:
+      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'] = [
+      '#type' => 'datelist',
+      '#date_increment' => $increment,
+      '#date_part_order' => $date_part_order,
+    ] + $element['start_value'];
+
+    $element['end_value'] = [
+      '#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..20228a6
--- /dev/null
+++ b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateRangeDefaultWidget.php
@@ -0,0 +1,100 @@
+<?php
+
+namespace Drupal\datetime\Plugin\Field\FieldWidget;
+
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\datetime\Plugin\Field\FieldType\DateRangeItem;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Plugin implementation of the 'daterange_default' widget.
+ *
+ * @FieldWidget(
+ *   id = "daterange_default",
+ *   label = @Translation("Date and time range"),
+ *   field_types = {
+ *     "daterange"
+ *   }
+ * )
+ */
+class DateRangeDefaultWidget extends DateRangeWidgetBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The date format storage.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $dateStorage;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityStorageInterface $date_storage) {
+    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
+
+    $this->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);
+
+    // 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..ed535d1
--- /dev/null
+++ b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateRangeWidgetBase.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace Drupal\datetime\Plugin\Field\FieldWidget;
+
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Field\WidgetBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\datetime\Plugin\Field\FieldType\DateRangeItem;
+
+/**
+ * Base class for the 'daterange_*' widgets.
+ */
+class DateRangeWidgetBase extends WidgetBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
+    // We are nesting some sub-elements inside the parent, so we need a wrapper.
+    // We also need to add another #title attribute at the top level for ease in
+    // identifying this item in error messages. We do not want to display this
+    // title because the actual title display is handled at a higher level by
+    // the Field module.
+
+    $element['#theme_wrappers'][] = 'datetime_wrapper';
+    $element['#attributes']['class'][] = 'container-inline';
+    $element['#element_validate'][] = [$this, 'validateStartEnd'];
+
+    $element['start_value'] = array(
+      '#title' => $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'];
+        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($end_date);
+            $format = DATETIME_DATE_STORAGE_FORMAT;
+            break;
+
+          default:
+            $format = DATETIME_DATETIME_STORAGE_FORMAT;
+            break;
+        }
+        // Adjust the date for storage.
+        $end_date->setTimezone(new \DateTimezone(DATETIME_STORAGE_TIMEZONE));
+        $item['end_value'] = $end_date->format($format);
+      }
+    }
+
+    return $values;
+  }
+
+  /**
+   * Validates that the start <= the end date.
+   */
+  public function validateStartEnd($element, FormStateInterface $form_state) {
+    $start_date = $element['start_value']['#value']['object'];
+    $end_date = $element['end_value']['#value']['object'];
+
+    if ($start_date instanceof DrupalDateTime && $end_date instanceof DrupalDateTime) {
+      /** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */
+      /** @var \Drupal\Core\Datetime\DrupalDateTime $end_date */
+
+      if ($start_date->format('U') !== $end_date->format('U')) {
+        $interval = $start_date->diff($end_date);
+        if ($interval->invert === 1) {
+          $form_state->setError($element, $this->t('Start date should be equal to, or before, end date'));
+        }
+      }
+    }
+  }
+
+}
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 @@
+<?php
+
+namespace Drupal\datetime\Tests;
+
+use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\Entity\Entity\EntityViewDisplay;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\node\Entity\Node;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests Datetime field functionality.
+ *
+ * @group datetime
+ */
+class DateRangeFieldTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('node', 'entity_test', 'datetime', 'field_ui');
+
+  /**
+   * The default display settings to use for the formatters.
+   */
+  protected $defaultSettings;
+
+  /**
+   * An array of display options to pass to entity_get_display()
+   *
+   * @var array
+   */
+  protected $displayOptions;
+
+  /**
+   * A field storage to use in this test class.
+   *
+   * @var \Drupal\field\Entity\FieldStorageConfig
+   */
+  protected $fieldStorage;
+
+  /**
+   * The field used in this test class.
+   *
+   * @var \Drupal\field\Entity\FieldConfig
+   */
+  protected $field;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Set an explicit site timezone, and disallow per-user timezones.
+    $this->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.');
+  }
+
+}
