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..b5d96aa
--- /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 datetime 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 = 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 <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' => 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('datetime_type') == DateTimeItem::DATETIME_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 = 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..101973f
--- /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 array(
+      'daterange_type' => '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' => t('Date type'),
+      '#description' => 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/DateRangeWidgetBase.php b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateRangeWidgetBase.php
new file mode 100644
index 0000000..1c47514
--- /dev/null
+++ b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateRangeWidgetBase.php
@@ -0,0 +1,115 @@
+<?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 'datetime_*' 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['start_value'] = array(
+      '#type' => 'datetime',
+      '#default_value' => NULL,
+      '#date_increment' => 1,
+      '#date_timezone' => drupal_get_user_timezone(),
+      '#required' => $element['#required'],
+    );
+
+    $element['end_value'] = array(
+      '#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;
+  }
+
+}
