diff --git a/core/lib/Drupal/Core/Datetime/Element/Datetime.php b/core/lib/Drupal/Core/Datetime/Element/Datetime.php
index 0bc1a4d631..1e12f9fe68 100644
--- a/core/lib/Drupal/Core/Datetime/Element/Datetime.php
+++ b/core/lib/Drupal/Core/Datetime/Element/Datetime.php
@@ -62,6 +62,7 @@ public function getInfo() {
       '#date_year_range' => '1900:2050',
       '#date_increment' => 1,
       '#date_timezone' => '',
+      '#expose_timezone' => FALSE,
     ];
   }
 
@@ -74,6 +75,11 @@ public static function valueCallback(&$element, $input, FormStateInterface $form
       $time_input = $element['#date_time_element'] != 'none' && !empty($input['time']) ? $input['time'] : '';
       $date_format = $element['#date_date_element'] != 'none' ? static::getHtml5DateFormat($element) : '';
       $time_format = $element['#date_time_element'] != 'none' ? static::getHtml5TimeFormat($element) : '';
+
+      // Timezone.
+      if ($element['#expose_timezone'] && $input['timezone']) {
+        $element['#date_timezone'] = $input['timezone'];
+      }
       $timezone = !empty($element['#date_timezone']) ? $element['#date_timezone'] : NULL;
 
       // Seconds will be omitted in a post in case there's no entry.
@@ -92,6 +98,7 @@ public static function valueCallback(&$element, $input, FormStateInterface $form
       $input = [
         'date'   => $date_input,
         'time'   => $time_input,
+        'timezone' => $timezone,
         'object' => $date,
       ];
     }
@@ -101,6 +108,7 @@ public static function valueCallback(&$element, $input, FormStateInterface $form
         $input = [
           'date'   => $date->format($element['#date_date_format']),
           'time'   => $date->format($element['#date_time_format']),
+          'timezone' => $date->getTimezone()->getName(),
           'object' => $date,
         ];
       }
@@ -108,6 +116,7 @@ public static function valueCallback(&$element, $input, FormStateInterface $form
         $input = [
           'date'   => '',
           'time'   => '',
+          'timezone' => '',
           'object' => NULL,
         ];
       }
@@ -195,6 +204,8 @@ public static function valueCallback(&$element, $input, FormStateInterface $form
    *     provided, this value will be ignored, the timezone in the default date
    *     takes precedence. Defaults to the value returned by
    *     drupal_get_user_timezone().
+   *   - #expose_timezone: a boolean that if set to TRUE, will expose a timezone
+   *     select list. Defaults to FALSE.
    *
    * Example usage:
    * @code
@@ -227,7 +238,9 @@ public static function processDatetime(&$element, FormStateInterface $form_state
       $element['#date_timezone'] = $date->getTimezone()->getName();
     }
     elseif (empty($element['#timezone'])) {
-      $element['#date_timezone'] = drupal_get_user_timezone();
+      // todo This is a temporary hack to get new formatter and widget tests to
+      // pass while working on #2632040. It will not be needed once #2799987 is fixed.
+      // $element['#date_timezone'] = drupal_get_user_timezone();
     }
 
     $element['#tree'] = TRUE;
@@ -332,6 +345,23 @@ public static function processAjaxForm(&$element, FormStateInterface $form_state
       }
     }
 
+    // Expose a timezone selector.
+    if (!empty($element['#expose_timezone']) && $element['#expose_timezone']) {
+      $timezone_names = system_time_zones();
+
+      // Replace underscores with spaces within name of the timezone.
+      array_walk($timezone_names, function (&$e, $i) {
+        $e = str_replace('_', ' ', $e);
+      });
+      $element['timezone'] = [
+        '#type' => 'select',
+        '#options' => array_combine(system_time_zones(), $timezone_names),
+        // Default to user's timezone.
+        '#default_value' => $element['#date_timezone'],
+        '#required' => $element['#required'],
+      ];
+    }
+
     return $element;
   }
 
diff --git a/core/modules/datetime/config/schema/datetime.schema.yml b/core/modules/datetime/config/schema/datetime.schema.yml
index a00901160b..28d143c244 100644
--- a/core/modules/datetime/config/schema/datetime.schema.yml
+++ b/core/modules/datetime/config/schema/datetime.schema.yml
@@ -7,6 +7,9 @@ field.storage_settings.datetime:
     datetime_type:
       type: string
       label: 'Date type'
+    timezone_storage:
+      type: boolean
+      label: 'Time zone storage'
 
 field.field_settings.datetime:
   type: mapping
@@ -26,9 +29,15 @@ field.value.datetime:
 field.formatter.settings.datetime_base:
   type: mapping
   mapping:
+    timezone_default:
+      type: string
+      label: 'Time zone default'
     timezone_override:
       type: string
-      label: 'Time zone override'
+      label: 'Timezone override'
+    timezone_per_date:
+      type: boolean
+      label: 'Timezone per date'
 
 field.formatter.settings.datetime_default:
   type: field.formatter.settings.datetime_base
@@ -64,8 +73,21 @@ field.formatter.settings.datetime_time_ago:
       type: integer
       label: 'Granularity'
 
-field.widget.settings.datetime_datelist:
+field.widget.settings.datetime_base:
   type: mapping
+  mapping:
+    timezone_default:
+      type: string
+      label: 'Time zone default'
+    timezone_override:
+      type: string
+      label: 'Timezone override'
+    timezone_per_date:
+      type: boolean
+      label: 'Timezone per date'
+
+field.widget.settings.datetime_datelist:
+  type: field.widget.settings.datetime_base
   label: 'Datetime select list display format settings'
   mapping:
     increment:
@@ -79,5 +101,5 @@ field.widget.settings.datetime_datelist:
       label: 'Time type'
 
 field.widget.settings.datetime_default:
-  type: mapping
+  type: field.widget.settings.datetime_base
   label: 'Datetime default display format settings'
diff --git a/core/modules/datetime/datetime.install b/core/modules/datetime/datetime.install
new file mode 100644
index 0000000000..aa401fb634
--- /dev/null
+++ b/core/modules/datetime/datetime.install
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * @file
+ * Update hooks for the Datetime module.
+ */
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface;
+use Drupal\field\Entity\FieldStorageConfig;
+
+/**
+ * Add the 'timezone' field to all datetime field tables.
+ */
+function datetime_update_8001(array &$sandbox) {
+
+  if (!isset($sandbox['progress'])) {
+    $sandbox['progress'] = 0;
+    $sandbox['current_id'] = '';
+    $sandbox['processed'] = [];
+
+    // Find all required updates.
+    $change_list = [];
+    foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type_id => $entity_type) {
+      $storage = \Drupal::entityTypeManager()->getStorage($entity_type_id);
+      if ($storage instanceof DynamicallyFieldableEntityStorageSchemaInterface) {
+        $field_changes = [];
+        $field_storage_definitions = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($entity_type_id);
+        $original_storage_definitions = \Drupal::service('entity.last_installed_schema.repository')->getLastInstalledFieldStorageDefinitions($entity_type_id);
+        foreach (array_intersect_key($field_storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) {
+          if ($storage_definition->getType() === 'datetime' && $storage->requiresFieldStorageSchemaChanges($storage_definition, $original_storage_definitions[$field_name])) {
+            /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
+            $table_mapping = $storage->getTableMapping();
+            $field_changes[$field_name] = [
+              'table' => $table_mapping->getFieldTableName($field_name),
+              'revision_table' => $table_mapping->getDedicatedRevisionTableName($storage_definition),
+            ];
+          }
+        }
+
+        if (!empty($field_changes)) {
+          $change_list[$entity_type_id] = $field_changes;
+        }
+      }
+    }
+
+    $sandbox['change_list'] = $change_list;
+    $sandbox['max'] = count($change_list);
+  }
+
+  $remaining = array_diff(array_keys($sandbox['change_list']), $sandbox['processed']);
+  if (!empty($remaining)) {
+    $entity_type_id = array_pop($remaining);
+    $field_changes = $sandbox['change_list'][$entity_type_id];
+
+    $field_spec = [
+      'description' => 'The date timezone.',
+      'type' => 'varchar',
+      'length' => 50,
+    ];
+    $schema = Database::getConnection()->schema();
+
+    foreach ($field_changes as $field_name => $field_tables) {
+      $timezone_field_name = $field_name . '_timezone';
+      $field_schema = [
+        'fields' => [
+          // Include the 'value' field in order to properly create the index.
+          $field_name . '_value' => [
+            'description' => 'The date value.',
+            'type' => 'varchar',
+            'length' => 20,
+          ],
+          $timezone_field_name => $field_spec,
+        ],
+        'indexes' => [
+          'value_timezone' => [$field_name . '_value', $timezone_field_name],
+        ],
+      ];
+      $schema->addField($field_tables['table'], $timezone_field_name, $field_spec);
+      $schema->addIndex($field_tables['table'], 'value_timezone', $field_schema['indexes']['value_timezone'], $field_schema);
+
+      if ($field_tables['revision_table']) {
+        $schema->addField($field_tables['revision_table'], $timezone_field_name, $field_spec);
+        $schema->addIndex($field_tables['revision_table'], 'value_timezone', $field_schema['indexes']['value_timezone'], $field_schema);
+      }
+
+      // Set timezone storage to FALSE, which is the behavior prior to the
+      // introduction of configurable per-date time zone storage.
+      $field_storage = FieldStorageConfig::load($entity_type_id . '.' . $field_name);
+      $field_storage->setSetting('timezone_storage', FALSE);
+      $field_storage->save();
+      \Drupal::entityDefinitionUpdateManager()->updateFieldStorageDefinition($field_storage);
+    }
+    $sandbox['processed'][] = $entity_type_id;
+  }
+
+  $sandbox['#finished'] = empty($remaining);
+}
diff --git a/core/modules/datetime/src/Plugin/Field/ConfigurableTimezoneInterface.php b/core/modules/datetime/src/Plugin/Field/ConfigurableTimezoneInterface.php
new file mode 100644
index 0000000000..47450c74a2
--- /dev/null
+++ b/core/modules/datetime/src/Plugin/Field/ConfigurableTimezoneInterface.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Drupal\datetime\Plugin\Field;
+
+/**
+ * Interface definition for field formatters and widgets that allow configuring
+ * the timezone used.
+ */
+interface ConfigurableTimezoneInterface {
+
+  /**
+   * Timezone uses the site's timezone, regardless of the user's timezone.
+   */
+  const TIMEZONE_SITE = 'site';
+
+  /**
+   * Timezone uses the user's timezone.
+   *
+   * @see drupal_get_user_timezone()
+   */
+  const TIMEZONE_USER = 'user';
+
+  /**
+   * A fixed timezone is used.
+   */
+  const TIMEZONE_FIXED = 'none';
+
+  /**
+   * Get the time zone used as the default by a widget or formatter.
+   *
+   * This is determined using the widget or formatter settings and (if there
+   * is one) the preferred time zone stored with the date item.
+   *
+   * @param string $itemTimezone
+   *   A time zone stored in the field for this item, or NULL.
+   *
+   * @return string
+   *   The default time zone.
+   */
+  public function getDefaultTimezone($itemTimezone);
+
+}
diff --git a/core/modules/datetime/src/Plugin/Field/ConfigurableTimezoneTrait.php b/core/modules/datetime/src/Plugin/Field/ConfigurableTimezoneTrait.php
new file mode 100644
index 0000000000..03930cfc5c
--- /dev/null
+++ b/core/modules/datetime/src/Plugin/Field/ConfigurableTimezoneTrait.php
@@ -0,0 +1,133 @@
+<?php
+
+namespace Drupal\datetime\Plugin\Field;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
+use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
+
+/**
+ * Time zone settings common to datetime widgets and formatters.
+ */
+trait ConfigurableTimezoneTrait {
+
+  /**
+   * Add time zone settings to a widget or formatter settings form.
+   *
+   * @param array $form
+   *   The form where the settings form is being included in.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @return array
+   *   The form structure with time zone settings elements added.
+   */
+  protected function timezoneSettingsForm(array $form, FormStateInterface $form_state) {
+    // Timezone display is only applicable to datetime items.
+    $datetime_type = $this->fieldDefinition->getFieldStorageDefinition()
+      ->getSetting('datetime_type');
+    if ($datetime_type === DateTimeItem::DATETIME_TYPE_DATETIME) {
+      $form['timezone_default'] = [
+        '#type' => 'select',
+        '#title' => $this->t('Default time zone'),
+        '#description' => $this->t('The time zone to use by default when displaying this date.'),
+        '#options' => [
+          ConfigurableTimezoneInterface::TIMEZONE_USER => $this->t("The user's account time zone"),
+          ConfigurableTimezoneInterface::TIMEZONE_SITE => $this->t("The site's default time zone"),
+          ConfigurableTimezoneInterface::TIMEZONE_FIXED => $this->t("A fixed time zone"),
+        ],
+        '#default_value' => $this->getSetting('timezone_default'),
+      ];
+
+      $form['timezone_override'] = [
+        '#type' => 'select',
+        '#title' => $this->t('Fixed time zone'),
+        '#options' => system_time_zones(TRUE),
+        '#default_value' => $this->getSetting('timezone_override'),
+        '#states' => ['visible' => [':input[name="fields[' . $this->fieldDefinition->getName() . '][settings_edit_form][settings][timezone_default]"]' => ['value' => ConfigurableTimezoneInterface::TIMEZONE_FIXED]]],
+      ];
+
+      // If this field is using per-date time zone storage, give the option of
+      // allowing that to override the default.
+      $timezone_storage = $this->fieldDefinition->getFieldStorageDefinition()
+        ->getSetting('timezone_storage');
+      if ($timezone_storage === TRUE) {
+        $form['timezone_per_date'] = [
+          '#type' => 'checkbox',
+          '#title' => 'Preferred time zone for each date',
+          '#default_value' => $this->getSetting('timezone_per_date'),
+        ];
+      }
+    }
+
+    return $form;
+  }
+
+  /**
+   * A time zone settings summary for a widget or formatter.
+   *
+   * @param array $per_date_summary
+   *   An array with 2 keys ('use' and 'default') giving the summary text to use
+   *   when per-date time zones are used by the widget or formatter.
+   *
+   * @return array
+   *   A short summary of the time zone settings.
+   */
+  protected function timezoneSettingsSummary($per_date_summary) {
+    $summary = [];
+    $datetime_type = $this->fieldDefinition->getFieldStorageDefinition()
+      ->getSetting('datetime_type');
+    if ($datetime_type === DateTimeItem::DATETIME_TYPE_DATETIME) {
+      // Determine the default time zone summary text.
+      $timezone_default = $this->getSetting('timezone_default');
+      $timezone_override = $this->getSetting('timezone_override');
+      if ($timezone_override && $timezone_default === ConfigurableTimezoneInterface::TIMEZONE_FIXED) {
+        $default_timezone_text = $timezone_override;
+      }
+      elseif ($timezone_default === ConfigurableTimezoneInterface::TIMEZONE_SITE) {
+        $default_timezone_text = $this->t("the site's default time zone");
+      }
+      else {
+        $default_timezone_text = $this->t("the user's account time zone");
+      }
+
+      // Prepare the final summary.
+      if ($this->getSetting('timezone_per_date') === TRUE) {
+        $summary[] = $this->t($per_date_summary['use']);
+        $summary[] = $this->t($per_date_summary['default'], ['@timezone' => $default_timezone_text]);
+      }
+      else {
+        $summary[] = $this->t('Time zone: @timezone', ['@timezone' => $default_timezone_text]);
+      }
+    }
+    return $summary;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefaultTimezone($itemTimezone = NULL) {
+    if ($this->getFieldSetting('datetime_type') === DateTimeItem::DATETIME_TYPE_DATE) {
+      // A date without time has no time zone conversion.
+      $timezone = DateTimeItemInterface::STORAGE_TIMEZONE;
+    }
+    else {
+      $timezone_default = $this->getSetting('timezone_default');
+      $timezone_override = $this->getSetting('timezone_override');
+      if ($this->getSetting('timezone_per_date') && !empty($itemTimezone)) {
+        $timezone = $itemTimezone;
+      }
+      elseif ($timezone_override && $timezone_default === ConfigurableTimezoneInterface::TIMEZONE_FIXED) {
+        $timezone = $timezone_override;
+      }
+      elseif ($timezone_default === ConfigurableTimezoneInterface::TIMEZONE_SITE) {
+        $timezone = $this->config->get('system.date')->get('timezone.default');
+      }
+      else {
+        $timezone = drupal_get_user_timezone();
+      }
+    }
+    return $timezone;
+  }
+
+}
diff --git a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeCustomFormatter.php b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeCustomFormatter.php
index e3b9daf2ee..322f93283e 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeCustomFormatter.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeCustomFormatter.php
@@ -16,7 +16,7 @@
  *   field_types = {
  *     "datetime"
  *   }
- *)
+ * )
  */
 class DateTimeCustomFormatter extends DateTimeFormatterBase {
 
@@ -43,7 +43,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
         /** @var \Drupal\Core\Datetime\DrupalDateTime $date */
         $date = $item->date;
 
-        $elements[$delta] = $this->buildDate($date);
+        $elements[$delta] = $this->buildDate($date, $item->timezone);
       }
     }
 
@@ -55,8 +55,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
    */
   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);
+    return $this->dateFormatter->format($date->getTimestamp(), 'custom', $format, $date->getTimezone()->getName());
   }
 
   /**
@@ -83,7 +82,7 @@ public function settingsSummary() {
 
     $date = new DrupalDateTime();
     $this->setTimeZone($date);
-    $summary[] = $date->format($this->getSetting('date_format'), $this->getFormatSettings());
+    $summary[] = $date->format($this->getSetting('date_format'));
 
     return $summary;
   }
diff --git a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeDefaultFormatter.php b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeDefaultFormatter.php
index fa6549665a..cb6839c63b 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeDefaultFormatter.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeDefaultFormatter.php
@@ -32,8 +32,7 @@ public static function defaultSettings() {
    */
   protected function formatDate($date) {
     $format_type = $this->getSetting('format_type');
-    $timezone = $this->getSetting('timezone_override') ?: $date->getTimezone()->getName();
-    return $this->dateFormatter->format($date->getTimestamp(), $format_type, '', $timezone != '' ? $timezone : NULL);
+    return $this->dateFormatter->format($date->getTimestamp(), $format_type, '', $date->getTimezone()->getName());
   }
 
   /**
@@ -68,6 +67,8 @@ public function settingsSummary() {
     $summary = parent::settingsSummary();
 
     $date = new DrupalDateTime();
+    $this->setTimeZone($date);
+
     $summary[] = t('Format: @display', ['@display' => $this->formatDate($date)]);
 
     return $summary;
diff --git a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeFormatterBase.php b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeFormatterBase.php
index a6680f6b4d..0579264e69 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeFormatterBase.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeFormatterBase.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\datetime\Plugin\Field\FieldFormatter;
 
+use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Datetime\DateFormatterInterface;
 use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\Core\Entity\EntityStorageInterface;
@@ -10,14 +11,16 @@
 use Drupal\Core\Field\FormatterBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
-use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
-use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\datetime\Plugin\Field\ConfigurableTimezoneTrait;
+use Drupal\datetime\Plugin\Field\ConfigurableTimezoneInterface;
 
 /**
  * Base class for 'DateTime Field formatter' plugin implementations.
  */
-abstract class DateTimeFormatterBase extends FormatterBase implements ContainerFactoryPluginInterface {
+abstract class DateTimeFormatterBase extends FormatterBase implements ConfigurableTimezoneInterface, ContainerFactoryPluginInterface {
+
+  use ConfigurableTimezoneTrait;
 
   /**
    * The date formatter service.
@@ -33,6 +36,13 @@
    */
   protected $dateFormatStorage;
 
+  /**
+   * A config factory for retrieving required config settings.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $config;
+
   /**
    * Constructs a new DateTimeDefaultFormatter.
    *
@@ -54,12 +64,15 @@
    *   The date formatter service.
    * @param \Drupal\Core\Entity\EntityStorageInterface $date_format_storage
    *   The date format entity storage.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The factory for configuration objects.
    */
-  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) {
+  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, ConfigFactoryInterface $config_factory) {
     parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
 
     $this->dateFormatter = $date_formatter;
     $this->dateFormatStorage = $date_format_storage;
+    $this->config = $config_factory;
   }
 
   /**
@@ -75,7 +88,8 @@ public static function create(ContainerInterface $container, array $configuratio
       $configuration['view_mode'],
       $configuration['third_party_settings'],
       $container->get('date.formatter'),
-      $container->get('entity.manager')->getStorage('date_format')
+      $container->get('entity.manager')->getStorage('date_format'),
+      $container->get('config.factory')
     );
   }
 
@@ -84,7 +98,9 @@ public static function create(ContainerInterface $container, array $configuratio
    */
   public static function defaultSettings() {
     return [
+      'timezone_default' => ConfigurableTimezoneInterface::TIMEZONE_USER,
       'timezone_override' => '',
+      'timezone_per_date' => FALSE,
     ] + parent::defaultSettings();
   }
 
@@ -92,7 +108,9 @@ public static function defaultSettings() {
    * {@inheritdoc}
    */
   public function settingsForm(array $form, FormStateInterface $form_state) {
-    $form = parent::settingsForm($form, $form_state);
+    $form = $this->timezoneSettingsForm($form, $form_state) +
+    parent::settingsForm($form, $form_state);
+    $form['timezone_per_date']['#description'] = "Where a time zone has been specified for a particular date value, use that instead of the default selected above.";
 
     $form['timezone_override'] = [
       '#type' => 'select',
@@ -109,12 +127,12 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function settingsSummary() {
-    $summary = parent::settingsSummary();
-
-    if ($override = $this->getSetting('timezone_override')) {
-      $summary[] = $this->t('Time zone: @timezone', ['@timezone' => $override]);
-    }
-
+    $per_date_summary = [
+      'use' => 'Use timezones from individual dates',
+      'default' => 'Use @timezone if no individual timezone specified',
+    ];
+    $summary = $this->timezoneSettingsSummary($per_date_summary) +
+      parent::settingsSummary();
     return $summary;
   }
 
@@ -128,7 +146,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
       if ($item->date) {
         /** @var \Drupal\Core\Datetime\DrupalDateTime $date */
         $date = $item->date;
-        $elements[$delta] = $this->buildDateWithIsoAttribute($date);
+        $elements[$delta] = $this->buildDateWithIsoAttribute($date, $item->timezone);
 
         if (!empty($item->_attributes)) {
           $elements[$delta]['#attributes'] += $item->_attributes;
@@ -154,55 +172,37 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
   abstract protected function formatDate($date);
 
   /**
-   * Sets the proper time zone on a DrupalDateTime object for the current user.
+   * Sets the proper time zone on a DrupalDateTime object.
    *
    * 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.
+   * zone applied to it. This method applies the proper timezone based on
+   * the formatter configuration.
    *
    * @see drupal_get_user_timezone()
    *
    * @param \Drupal\Core\Datetime\DrupalDateTime $date
    *   A DrupalDateTime object.
+   * @param string $date_instance_timezone
+   *   (optional) The timezone associated with the specific date field instance.
    */
-  protected function setTimeZone(DrupalDateTime $date) {
-    if ($this->getFieldSetting('datetime_type') === DateTimeItem::DATETIME_TYPE_DATE) {
-      // A date without time has no timezone conversion.
-      $timezone = DateTimeItemInterface::STORAGE_TIMEZONE;
-    }
-    else {
-      $timezone = drupal_get_user_timezone();
-    }
+  protected function setTimeZone(DrupalDateTime $date, $date_instance_timezone = NULL) {
+    $timezone = $this->getDefaultTimezone($date_instance_timezone);
     $date->setTimeZone(timezone_open($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;
-  }
-
   /**
    * Creates a render array from a date object.
    *
    * @param \Drupal\Core\Datetime\DrupalDateTime $date
    *   A date object.
+   * @param string $timezone
+   *   (optional) A timezone to explicitly set the date to.
    *
    * @return array
    *   A render array.
    */
-  protected function buildDate(DrupalDateTime $date) {
-    $this->setTimeZone($date);
+  protected function buildDate(DrupalDateTime $date, $timezone = NULL) {
+    $this->setTimeZone($date, $timezone);
 
     $build = [
       '#markup' => $this->formatDate($date),
@@ -221,15 +221,17 @@ protected function buildDate(DrupalDateTime $date) {
    *
    * @param \Drupal\Core\Datetime\DrupalDateTime $date
    *   A date object.
+   * @param string $timezone
+   *   (optional) A timezone to explicitly set the date to.
    *
    * @return array
    *   A render array.
    */
-  protected function buildDateWithIsoAttribute(DrupalDateTime $date) {
+  protected function buildDateWithIsoAttribute(DrupalDateTime $date, $timezone = NULL) {
     // Create the ISO date in Universal Time.
     $iso_date = $date->format("Y-m-d\TH:i:s") . 'Z';
 
-    $this->setTimeZone($date);
+    $this->setTimeZone($date, $timezone);
 
     $build = [
       '#theme' => 'time',
diff --git a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php
index 8ddfdda2ae..e3a710b303 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php
@@ -15,7 +15,7 @@
  *   field_types = {
  *     "datetime"
  *   }
- *)
+ * )
  */
 class DateTimePlainFormatter extends DateTimeFormatterBase {
 
@@ -30,7 +30,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
         /** @var \Drupal\Core\Datetime\DrupalDateTime $date */
         $date = $item->date;
 
-        $elements[$delta] = $this->buildDate($date);
+        $elements[$delta] = $this->buildDate($date, $item->timezone);
       }
     }
 
@@ -42,8 +42,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
    */
   protected function formatDate($date) {
     $format = $this->getFieldSetting('datetime_type') == DateTimeItem::DATETIME_TYPE_DATE ? DateTimeItemInterface::DATE_STORAGE_FORMAT : DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
-    $timezone = $this->getSetting('timezone_override');
-    return $this->dateFormatter->format($date->getTimestamp(), 'custom', $format, $timezone != '' ? $timezone : NULL);
+    return $this->dateFormatter->format($date->getTimestamp(), 'custom', $format, $date->getTimezone()->getName());
   }
 
 }
diff --git a/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItem.php b/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItem.php
index 3264069008..12559ceeb2 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItem.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItem.php
@@ -29,6 +29,7 @@ class DateTimeItem extends FieldItemBase implements DateTimeItemInterface {
   public static function defaultStorageSettings() {
     return [
       'datetime_type' => 'datetime',
+      'timezone_storage' => FALSE,
     ] + parent::defaultStorageSettings();
   }
 
@@ -57,6 +58,9 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel
       ->setClass('\Drupal\datetime\DateTimeComputed')
       ->setSetting('date source', 'value');
 
+    $properties['timezone'] = DataDefinition::create('string')
+      ->setLabel(t('Timezone'));
+
     return $properties;
   }
 
@@ -64,18 +68,26 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel
    * {@inheritdoc}
    */
   public static function schema(FieldStorageDefinitionInterface $field_definition) {
-    return [
+    $schema = [
       'columns' => [
         'value' => [
           'description' => 'The date value.',
           'type' => 'varchar',
           'length' => 20,
         ],
+        'timezone' => [
+          'description' => 'The date timezone',
+          'type' => 'varchar',
+          'length' => 50,
+        ],
       ],
       'indexes' => [
         'value' => ['value'],
+        'value_timezone' => ['value', 'timezone'],
       ],
     ];
+
+    return $schema;
   }
 
   /**
@@ -96,6 +108,23 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
       '#disabled' => $has_data,
     ];
 
+    $element['timezone_storage'] = [
+      '#type' => 'checkbox',
+      '#title' => t('Store a time zone'),
+      '#description' => 'Allow storing a preferred time zone with each date and time',
+      '#default_value' => $this->getSetting('timezone_storage'),
+      '#states' => [
+        // Hide the field if this is a date-only field.
+        'visible' => [
+          ':input[name="settings[datetime_type]"]' => ['value' => static::DATETIME_TYPE_DATETIME],
+        ],
+        'disabled' => [
+          ':input[name="settings[datetime_type]"]' => ['value' => static::DATETIME_TYPE_DATE],
+        ],
+      ],
+      '#disabled' => $has_data,
+    ];
+
     return $element;
   }
 
diff --git a/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeDefaultWidget.php b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeDefaultWidget.php
index 2a4ffcd297..044a3e2e46 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeDefaultWidget.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeDefaultWidget.php
@@ -9,6 +9,7 @@
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
 
 /**
  * Plugin implementation of the 'datetime_default' widget.
@@ -33,8 +34,8 @@ class DateTimeDefaultWidget extends DateTimeWidgetBase implements ContainerFacto
   /**
    * {@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);
+  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ConfigFactoryInterface $config_factory, EntityStorageInterface $date_storage) {
+    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $config_factory);
 
     $this->dateStorage = $date_storage;
   }
@@ -49,6 +50,7 @@ public static function create(ContainerInterface $container, array $configuratio
       $configuration['field_definition'],
       $configuration['settings'],
       $configuration['third_party_settings'],
+      $container->get('config.factory'),
       $container->get('entity.manager')->getStorage('date_format')
     );
   }
diff --git a/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeWidgetBase.php b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeWidgetBase.php
index d723482824..bd5a0fd0d7 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeWidgetBase.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeWidgetBase.php
@@ -2,17 +2,101 @@
 
 namespace Drupal\datetime\Plugin\Field\FieldWidget;
 
+use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Field\WidgetBase;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\datetime\Plugin\Field\ConfigurableTimezoneInterface;
+use Drupal\datetime\Plugin\Field\ConfigurableTimezoneTrait;
 use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
 use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Base class for the 'datetime_*' widgets.
  */
-class DateTimeWidgetBase extends WidgetBase {
+class DateTimeWidgetBase extends WidgetBase implements ConfigurableTimezoneInterface, ContainerFactoryPluginInterface {
+
+  use ConfigurableTimezoneTrait;
+
+  /**
+   * A config factory for retrieving required config settings.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $config;
+
+  /**
+   * 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 array $third_party_settings
+   *   Third party settings.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The factory for configuration objects.
+   */
+  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ConfigFactoryInterface $config_factory) {
+    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
+    $this->config = $config_factory;
+  }
+
+  /**
+   * {@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('config.factory')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultSettings() {
+    return [
+      'timezone_default' => ConfigurableTimezoneInterface::TIMEZONE_USER,
+      'timezone_override' => '',
+      'timezone_per_date' => FALSE,
+    ] + parent::defaultSettings();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form, FormStateInterface $form_state) {
+    $form = $this->timezoneSettingsForm($form, $form_state) +
+      parent::settingsForm($form, $form_state);
+    $form['timezone_per_date']['#description'] = "Allow users to specify a time zone when entering a date, and store this as the preferred time zone for that date.";
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsSummary() {
+    $per_date_summary = [
+      'use' => 'Allow users to choose a time zone',
+      'default' => 'Default to @timezone',
+    ];
+    $summary = $this->timezoneSettingsSummary($per_date_summary) +
+      parent::settingsSummary();
+    return $summary;
+  }
 
   /**
    * {@inheritdoc}
@@ -22,20 +106,20 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
       '#type' => 'datetime',
       '#default_value' => NULL,
       '#date_increment' => 1,
-      '#date_timezone' => drupal_get_user_timezone(),
+      '#date_timezone' => $this->getDefaultTimezone($items[$delta]->timezone),
       '#required' => $element['#required'],
     ];
 
-    if ($this->getFieldSetting('datetime_type') == DateTimeItem::DATETIME_TYPE_DATE) {
-      // A date-only field should have no timezone conversion performed, so
-      // use the same timezone as for storage.
-      $element['value']['#date_timezone'] = DateTimeItemInterface::STORAGE_TIMEZONE;
+    if ($this->getSetting('timezone_per_date') && $this->getFieldSetting('timezone_storage') === TRUE) {
+      $element['value']['#expose_timezone'] = TRUE;
     }
 
     if ($items[$delta]->date) {
+      /** @var \Drupal\Core\Datetime\DrupalDateTime $date */
       $date = $items[$delta]->date;
       // The date was created and verified during field_load(), so it is safe to
       // use without further inspection.
+      // @todo Remove after #2799987, as then the element will handle this.
       $date->setTimezone(new \DateTimeZone($element['value']['#date_timezone']));
       $element['value']['#default_value'] = $this->createDefaultValue($date, $element['value']['#date_timezone']);
     }
@@ -47,9 +131,10 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
    * {@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.
+    // The widget form element type has transformed the date value to a
+    // DrupalDateTime object at this point. We need to extract the time zone
+    // and store it separately, and then convert the date to Drupal's storage
+    // time zone and format.
 
     $datetime_type = $this->getFieldSetting('datetime_type');
     if ($datetime_type === DateTimeItem::DATETIME_TYPE_DATE) {
@@ -66,13 +151,41 @@ public function massageFormValues(array $values, array $form, FormStateInterface
         /** @var \Drupal\Core\Datetime\DrupalDateTime $date */
         $date = $item['value'];
 
+        // Store the time zone if appropriate.
+        $item['timezone'] = '';
+        if ($this->shouldStoreTimezone($date, $form, $form_state) && $this->getFieldSetting('timezone_storage') === TRUE) {
+          $item['timezone'] = $date->getTimezone()->getName();
+        }
+
+        // Adjust the date for storage once validation is complete.
+        if ($form_state->isValidationComplete()) {
+          $date->setTimezone($storage_timezone);
+        }
         // Adjust the date for storage.
-        $item['value'] = $date->setTimezone($storage_timezone)->format($storage_format);
+        $item['value'] = $date->format($storage_format);
       }
     }
     return $values;
   }
 
+  /**
+   * Determines whether the time zone should be stored.
+   *
+   * @param \Drupal\Core\Datetime\DrupalDateTime $date
+   *   The submitted date value object produced by the widget.
+   * @param array $form
+   *   The form structure where field elements are attached to. This might be a
+   *   full form structure, or a sub-element of a larger form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   *
+   * @return bool
+   *   Whether the time zone should be stored.
+   */
+  protected function shouldStoreTimezone(DrupalDateTime $date, array $form, FormStateInterface $form_state) {
+    return $this->fieldDefinition->getFieldStorageDefinition()->getSetting('timezone_storage');
+  }
+
   /**
    * Creates a date object for use as a default value.
    *
@@ -93,6 +206,7 @@ protected function createDefaultValue($date, $timezone) {
     if ($this->getFieldSetting('datetime_type') === DateTimeItem::DATETIME_TYPE_DATE) {
       $date->setDefaultDateTime();
     }
+    // @todo Remove after #2799987, as then the element will handle this.
     $date->setTimezone(new \DateTimeZone($timezone));
     return $date;
   }
diff --git a/core/modules/datetime/src/Plugin/Validation/Constraint/DateTimeFormatConstraint.php b/core/modules/datetime/src/Plugin/Validation/Constraint/DateTimeFormatConstraint.php
index 5ed340d44a..3143a15370 100644
--- a/core/modules/datetime/src/Plugin/Validation/Constraint/DateTimeFormatConstraint.php
+++ b/core/modules/datetime/src/Plugin/Validation/Constraint/DateTimeFormatConstraint.php
@@ -35,4 +35,18 @@ class DateTimeFormatConstraint extends Constraint {
    */
   public $badValue = "The datetime value '@value' did not parse properly for the format '@format'";
 
+  /**
+   * Message for when the value isn't a string.
+   *
+   * @var string
+   */
+  public $badTimezoneType = "The timezone value must be a string.";
+
+  /**
+   * Message for when the value did not parse properly.
+   *
+   * @var string
+   */
+  public $badTimezoneValue = "The timezone value '@timezone' did not parse properly.";
+
 }
diff --git a/core/modules/datetime/src/Plugin/Validation/Constraint/DateTimeFormatConstraintValidator.php b/core/modules/datetime/src/Plugin/Validation/Constraint/DateTimeFormatConstraintValidator.php
index 4b2ad5d709..f7698e3ba6 100644
--- a/core/modules/datetime/src/Plugin/Validation/Constraint/DateTimeFormatConstraintValidator.php
+++ b/core/modules/datetime/src/Plugin/Validation/Constraint/DateTimeFormatConstraintValidator.php
@@ -51,6 +51,22 @@ public function validate($item, Constraint $constraint) {
           ]);
         }
       }
+      if ($item->getFieldDefinition()->getSetting('timezone_storage')) {
+        $timezone = $item->getValue()['timezone'];
+        if (!is_string($timezone)) {
+          $this->context->addViolation($constraint->badTimezoneType);
+        }
+        else {
+          try {
+            new \DateTimeZone($timezone);
+          }
+          catch (\Exception $e) {
+            $this->context->addViolation($constraint->badTimezoneValue, [
+              '@timezone' => $timezone,
+            ]);
+          }
+        }
+      }
     }
   }
 
diff --git a/core/modules/datetime/src/Tests/DateTestBase.php b/core/modules/datetime/src/Tests/DateTestBase.php
index 6a89003900..49b9b4f7b0 100644
--- a/core/modules/datetime/src/Tests/DateTestBase.php
+++ b/core/modules/datetime/src/Tests/DateTestBase.php
@@ -34,6 +34,13 @@
    */
   protected $displayOptions;
 
+  /**
+   * An entity storage to use in this test class.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $entityStorage;
+
   /**
    * A field storage to use in this test class.
    *
@@ -63,7 +70,7 @@
   protected static $timezones = [
     // UTC-12, no DST.
     'Pacific/Kwajalein',
-    // UTC-11, no DST
+    // UTC-11, no DST.
     'Pacific/Midway',
     // UTC-7, no DST.
     'America/Phoenix',
@@ -71,7 +78,7 @@
     'UTC',
     // UTC+5:30, no DST.
     'Asia/Kolkata',
-    // UTC+12, no DST
+    // UTC+12, no DST.
     'Pacific/Funafuti',
     // UTC+13, no DST.
     'Pacific/Tongatapu',
@@ -100,6 +107,8 @@ protected function setUp() {
     ]);
     $this->drupalLogin($web_user);
 
+    $this->entityStorage = $this->container->get('entity_type.manager')->getStorage('entity_test');
+
     // Create a field with settings to validate.
     $this->createField();
 
diff --git a/core/modules/datetime/tests/fixtures/update/datetime-date_8001-values.php b/core/modules/datetime/tests/fixtures/update/datetime-date_8001-values.php
new file mode 100644
index 0000000000000000000000000000000000000000..e2a6ed6223192b6690c9ed0a8b91be5f2b80cbbb
GIT binary patch
literal 38798
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
X(^_I$i&&Y2)7|_=bMO}Dx4r)X97mo2

literal 0
HcmV?d00001

diff --git a/core/modules/datetime/tests/fixtures/update/field.field.node.page.field_date_1.yml b/core/modules/datetime/tests/fixtures/update/field.field.node.page.field_date_1.yml
new file mode 100644
index 0000000000..6fd580c88e
--- /dev/null
+++ b/core/modules/datetime/tests/fixtures/update/field.field.node.page.field_date_1.yml
@@ -0,0 +1,21 @@
+uuid: 2eaa16eb-0542-4b82-b590-4143798f2088
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.node.field_date_1
+    - node.type.page
+  module:
+    - datetime
+id: node.page.field_date_1
+field_name: field_date_1
+entity_type: node
+bundle: page
+label: date_1
+description: ''
+required: false
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings: {  }
+field_type: datetime
diff --git a/core/modules/datetime/tests/fixtures/update/field.storage.node.field_date_1.yml b/core/modules/datetime/tests/fixtures/update/field.storage.node.field_date_1.yml
new file mode 100644
index 0000000000..2a771e9328
--- /dev/null
+++ b/core/modules/datetime/tests/fixtures/update/field.storage.node.field_date_1.yml
@@ -0,0 +1,20 @@
+uuid: 8bb2593a-bb3d-4ceb-81c9-a992a883a3cc
+langcode: en
+status: true
+dependencies:
+  module:
+    - datetime
+    - node
+id: node.field_date_1
+field_name: field_date_1
+entity_type: node
+type: datetime
+settings:
+  datetime_type: datetime
+module: datetime
+locked: false
+cardinality: -1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/core/modules/datetime/tests/src/Functional/DateTestBase.php b/core/modules/datetime/tests/src/Functional/DateTestBase.php
index 46e23ce150..fef1947520 100644
--- a/core/modules/datetime/tests/src/Functional/DateTestBase.php
+++ b/core/modules/datetime/tests/src/Functional/DateTestBase.php
@@ -30,6 +30,13 @@
    */
   protected $displayOptions;
 
+  /**
+   * An entity storage to use in this test class.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $entityStorage;
+
   /**
    * A field storage to use in this test class.
    *
@@ -59,7 +66,7 @@
   protected static $timezones = [
     // UTC-12, no DST.
     'Pacific/Kwajalein',
-    // UTC-11, no DST
+    // UTC-11, no DST.
     'Pacific/Midway',
     // UTC-7, no DST.
     'America/Phoenix',
@@ -67,7 +74,7 @@
     'UTC',
     // UTC+5:30, no DST.
     'Asia/Kolkata',
-    // UTC+12, no DST
+    // UTC+12, no DST.
     'Pacific/Funafuti',
     // UTC+13, no DST.
     'Pacific/Tongatapu',
@@ -96,6 +103,8 @@ protected function setUp() {
     ]);
     $this->drupalLogin($web_user);
 
+    $this->entityStorage = $this->container->get('entity_type.manager')->getStorage('entity_test');
+
     // Create a field with settings to validate.
     $this->createField();
 
diff --git a/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php b/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php
index a994cc82c8..a72de53f6e 100644
--- a/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php
+++ b/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php
@@ -10,6 +10,8 @@
 use Drupal\field\Entity\FieldConfig;
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\node\Entity\Node;
+use Drupal\user\Entity\User;
+use Drupal\datetime\Plugin\Field\ConfigurableTimezoneInterface;
 
 /**
  * Tests Datetime field functionality.
@@ -23,7 +25,11 @@ class DateTimeFieldTest extends DateTestBase {
    *
    * @var array
    */
-  protected $defaultSettings = ['timezone_override' => ''];
+  protected $defaultSettings = [
+    'timezone_default' => ConfigurableTimezoneInterface::TIMEZONE_USER,
+    'timezone_override' => '',
+    'timezone_per_date' => FALSE,
+  ];
 
   /**
    * {@inheritdoc}
@@ -311,7 +317,7 @@ public function testDatetimeField() {
 
     // Verify that the 'timezone_override' setting works.
     $this->displayOptions['type'] = 'datetime_custom';
-    $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A', 'timezone_override' => 'America/New_York'] + $this->defaultSettings;
+    $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A', 'timezone_default' => ConfigurableTimezoneInterface::TIMEZONE_FIXED, 'timezone_override' => 'America/New_York'] + $this->defaultSettings;
     entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
       ->setComponent($field_name, $this->displayOptions)
       ->save();
@@ -368,6 +374,329 @@ public function testDatetimeField() {
     $this->assertContains((string) $expected, $output, new FormattableMarkup('Formatted date field using datetime_time_ago format displayed as %expected.', ['%expected' => $expected]));
   }
 
+  /**
+   * Test combinations of timezone configurations.
+   */
+  public function testFormatterTimezoneSettings() {
+    // Using different timezones wherever possible helps to highlight leakage.
+    // Giving timezones labels that describes the context in which they are
+    // stored makes it easier to diagnose test failures.
+    $timezones = [
+      'site' => 'Pacific/Kwajalein',
+      'user' => 'America/Phoenix',
+      'input' => 'Africa/Lagos',
+      'override' => 'Asia/Kolkata',
+      'php' => 'Pacific/Funafuti',
+      'storage' => DateTimeItemInterface::STORAGE_TIMEZONE,
+    ];
+
+    // Setup test environment.
+    // Reset php's default timezone to guarantee it's different to others here.
+    date_default_timezone_set($timezones['php']);
+    $this->config('system.date')
+      ->set('timezone.user.configurable', 1)
+      ->set('timezone.default', $timezones['site'])
+      ->save();
+    $this->setLoggedInUserTimezone($timezones['user']);
+
+    // Prepare the date that will be stored and rendered by the formatter.
+    $date = new DrupalDateTime("2012-10-15 17:25:00", 'UTC');
+    $date->setTimezone(new \DateTimezone(DateTimeItemInterface::STORAGE_TIMEZONE));
+    $field_name = $this->fieldStorage->getName();
+    $fields = [
+      $field_name => [
+        'value' => $date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT),
+      ],
+    ];
+
+    // Scenario 1: Stored preference not enabled.
+    // Test the formatter when per-date time zone storage has not been enabled.
+    $this->fieldStorage->setSetting('datetime_type', 'datetime');
+    $this->fieldStorage->setSetting('timezone_storage', FALSE);
+    $this->fieldStorage->save();
+    $entity = $this->entityStorage->create($fields);
+    $entity->save();
+    $this->formatterSettingsTest('not enabled', $entity->id(), $timezones);
+
+    // Scenario 2: Stored preference unspecified.
+    // Test the formatter on the same entity, after enabling per-date storage,
+    // in which case existing entities have NULL as their stored time zone.
+    $this->fieldStorage->setSetting('datetime_type', 'datetime');
+    $this->fieldStorage->setSetting('timezone_storage', TRUE);
+    $this->fieldStorage->save();
+    $this->formatterSettingsTest('no value', $entity->id(), $timezones);
+
+    // Scenario 3: Stored preference specified.
+    // Test the formatter on a new entity, which will now have a preferred
+    // time zone stored in its field.
+    $fields[$field_name]['timezone'] = $timezones['input'];
+    $entity = $this->entityStorage->create($fields);
+    $entity->save();
+    $this->formatterSettingsTest('has value', $entity->id(), $timezones);
+
+  }
+
+  /**
+   * Tests formatter output for different sets of settings.
+   */
+  protected function formatterSettingsTest($timezone_storage, $id, $timezones) {
+    // All the possible formatter timezone settings.
+    $settings = [
+      'per_date' => [
+        'Formatter using preferred time zone for each date' => TRUE,
+        'Formatter not using preferred time zone for each date' => FALSE,
+      ],
+      'default' => [
+        'user' => ConfigurableTimezoneInterface::TIMEZONE_USER,
+        'site' => ConfigurableTimezoneInterface::TIMEZONE_SITE,
+        'override' => ConfigurableTimezoneInterface::TIMEZONE_FIXED,
+      ],
+    ];
+
+    // Gather information about the field.
+    $field_name = $this->fieldStorage->getName();
+    $entity = $this->entityStorage->load($id);
+    $fieldValues = $entity->get($field_name)->getValue()[0];
+    $storedTimezone = $fieldValues['timezone'];
+    $storedDateInStorageFormat = $fieldValues['value'];
+
+    foreach ($settings['per_date'] as $per_date_label => $per_date) {
+      foreach ($settings['default'] as $default_label => $default) {
+        // Prepare the expectations. The timezone used should always be that set
+        // in the 'default' setting, unless the 'Use stored preference' setting
+        // is set AND a timezone is stored in the field for the date value being
+        // rendered.
+        $expected_timezone_label = array_search($default, $settings['default']);
+        if ($timezone_storage === 'has value') {
+          $this->assertEqual($timezones['input'], $storedTimezone, "Verifying the test is correctly setup with the right time zone stored.");
+          if ($per_date) {
+            $expected_timezone_label = 'input';
+          }
+        }
+
+        // Setup the formatter using the settings for the current scenario.
+        // Have the time zone displayed as part of the output.
+        $output_format = 'm/d/Y g:i:s A e';
+        $this->displayOptions['type'] = 'datetime_custom';
+        $this->displayOptions['settings'] = [
+          'date_format' => $output_format,
+          'timezone_default' => $default,
+          'timezone_override' => $timezones['override'],
+          'timezone_per_date' => $per_date,
+        ];
+        entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
+          ->setComponent($field_name, $this->displayOptions)
+          ->save();
+
+        // Try to get the rendered field.
+        $output = $this->renderTestEntity($id);
+        $dom = new \DOMDocument();
+        @$dom->loadHTML($output);
+        $xpath = new \DOMXPath($dom);
+        $fieldXpath = $xpath->query("//div[contains(@class, 'field--type-datetime')]");
+        $fieldFound = ($fieldXpath->length === 1);
+        $this->assertTrue($fieldFound, "Looking for the div.field--type-datetime element");
+
+        // Test the rendered value.
+        if ($fieldFound) {
+          // Extract a date from the rendered value.
+          $actualText = trim($fieldXpath->item(0)->textContent);
+          $actualDate = DrupalDateTime::createFromFormat($output_format, $actualText);
+
+          // Prepare debug messages.
+          $scenario_message = "Per-date timezones in the field: $timezone_storage.";
+          $stored_message = new FormattableMarkup("The stored time zone is '@stored_timezone_label' ('@stored_timezone').", [
+            '@stored_timezone' => $storedTimezone,
+            '@stored_timezone_label' => array_search($storedTimezone, $timezones),
+          ]);
+          $settings_message = new FormattableMarkup("@per_date and 'Default timezone' set to '@default_label' (@default_timezone).", [
+            '@per_date' => $per_date_label,
+            '@default' => $default_label,
+            '@default_timezone' => $timezones[$default_label],
+          ]);
+          $base_message = new FormattableMarkup("Found text: '@actualText'. \n@scenario_message \n@stored_message \n@settings_message", [
+            '@actualText' => $actualText,
+            '@scenario_message' => $scenario_message,
+            '@stored_message' => $stored_message,
+            '@settings_message' => $settings_message,
+          ]);
+
+          // Test that the rendered date matches the stored date.
+          $actualDateInStorageFormat = $actualDate->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, ['timezone' => DateTimeItemInterface::STORAGE_TIMEZONE]);
+          $message = new FormattableMarkup("Rendered date can be interpreted as '@actual',  expected @expected. \n@base_message.", [
+            '@actual' => $actualDateInStorageFormat,
+            '@expected' => $storedDateInStorageFormat,
+            '@base_message' => $base_message,
+          ]);
+          $this->assertEqual($storedDateInStorageFormat, $actualDateInStorageFormat, $message);
+
+          // Test that the rendered date used the expected timezone.
+          // Lookup the logical source of the timezone shown in the rendered date.
+          $actualTimeZone = $actualDate->getTimezone()->getName();
+          if (!$actualTimeSetting = array_search($actualTimeZone, $timezones)) {
+            $actualTimeSetting = $actualTimeZone;
+          }
+          // Compare that with the source expected to be in effect.
+          $message = new FormattableMarkup("Time formatted using '@actual' timezone, expected to use '@expected' timezone. \n @scenario_message @stored_message @settings_message.", [
+            '@actual' => $actualTimeSetting,
+            '@expected' => $expected_timezone_label,
+            '@base_message' => $base_message,
+          ]);
+          $this->assertIdentical($expected_timezone_label, $actualTimeSetting, $message);
+        }
+      }
+    }
+  }
+
+  /**
+   * Tests widget with different settings.
+   */
+  public function testWidgetTimezoneSettings() {
+    // Using different timezones wherever possible helps to highlight leakage.
+    // Giving timezones labels that describes the context in which they are
+    // stored makes it easier to diagnose test failures.
+    $timezones = [
+      'site' => 'Pacific/Kwajalein',
+      'user' => 'America/Phoenix',
+      'input' => 'Africa/Lagos',
+      'override' => 'Asia/Kolkata',
+      'php' => 'Pacific/Funafuti',
+      'storage' => DateTimeItemInterface::STORAGE_TIMEZONE,
+    ];
+
+    // The test date that should be saved by the widget.
+    $date = new DrupalDateTime('2012-10-15 17:25:00', DateTimeItemInterface::STORAGE_TIMEZONE);
+
+    // Store the test date formatted according to the different timezones, for
+    // use in error reporting and debugging.
+    $formattedDates = [];
+    foreach ($timezones as $label => $timezone) {
+      $formattedDates[$label] = $date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $timezone);
+    }
+
+    // Setup test environment.
+    // Reset php's default timezone to guarantee it's different to others here.
+    date_default_timezone_set($timezones['php']);
+    $this->config('system.date')
+      ->set('timezone.user.configurable', 1)
+      ->set('timezone.default', $timezones['site'])
+      ->save();
+    $this->setLoggedInUserTimezone($timezones['user']);
+
+    $scenarios = [
+      'per_date' => [
+        // The field is not configured to have per-date timezone storage.
+        'no storage' => FALSE,
+        // The field storage timezones enabled, but the widget does not.
+        'not allowed' => TRUE,
+        // The widget allows, but the editor leaves at the default timezone.
+        'default' => TRUE,
+        // The editor selects a timezone other than the default.
+        'input' => TRUE,
+      ],
+      'default' => [
+        'user' => ConfigurableTimezoneInterface::TIMEZONE_USER,
+        'site' => ConfigurableTimezoneInterface::TIMEZONE_SITE,
+        'override' => ConfigurableTimezoneInterface::TIMEZONE_FIXED,
+      ],
+    ];
+    $this->setLoggedInUserTimezone($timezones['user']);
+    $field_name = $this->fieldStorage->getName();
+
+    foreach ($scenarios['per_date'] as $per_date_label => $per_date) {
+      foreach ($scenarios['default'] as $default_label => $default) {
+        // Determine the timezone the user will intend for input. We assume the
+        // user behaves according to the site builder's intention. Therefore the
+        // timezone used should always be that set in the 'default' setting,
+        // unless we are testing the scenario where the user can and does
+        // select a per-date timezone other than the default.
+        $timezone_intended_label = $default_label;
+        if ($per_date_label === 'input') {
+          $timezone_intended_label = 'input';
+        }
+        $timezone_intended = $timezones[$timezone_intended_label];
+        // Determine what we expect to be stored in the field's timezone column.
+        // We expect nothing to be stored if the field is not configured to
+        // store timezone.
+        $timezone_expected_label = NULL;
+        $timezone_expected = NULL;
+        if ($per_date) {
+          $timezone_expected_label = $timezone_intended_label;
+          $timezone_expected = $timezones[$timezone_expected_label];
+        }
+
+        // Set up the field.
+        $this->fieldStorage->setSetting('datetime_type', 'datetime');
+        if ($per_date_label === 'no storage') {
+          $this->fieldStorage->setSetting('timezone_storage', FALSE);
+        }
+        else {
+          $this->fieldStorage->setSetting('timezone_storage', TRUE);
+        }
+        $this->fieldStorage->save();
+
+        // Setup the widget.
+        entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
+          ->setComponent($field_name, [
+            'type' => 'datetime_default',
+            'settings' => [
+              'timezone_default' => $default,
+              'timezone_override' => $timezones['override'],
+              'timezone_per_date' => $per_date,
+            ],
+          ])
+          ->save();
+
+        // Prepare the field values.
+        $date_format = DateFormat::load('html_date')->getPattern();
+        $time_format = DateFormat::load('html_time')->getPattern();
+        $field_name = $this->fieldStorage->getName();
+        $edit = [
+          "{$field_name}[0][value][date]" => $date->format($date_format, ['timezone' => $timezone_intended]),
+          "{$field_name}[0][value][time]" => $date->format($time_format, ['timezone' => $timezone_intended]),
+        ];
+        // Only set time zone input if testing the scenario where a time zone
+        // select is exposed and the user selects other than the default.
+        if ($per_date_label === 'input') {
+          $edit += [
+            "{$field_name}[0][value][timezone]" => $timezone_intended,
+          ];
+        }
+
+        // Try to save the date through a widget on an entity.
+        $this->drupalPostForm('entity_test/add', $edit, t('Save'));
+        preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
+        $id = $match[1];
+        $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
+
+        // Test what is stored in the field.
+        if ($id) {
+          $entity = $this->entityStorage->load($id);
+          $fieldValues = $entity->get($field_name)->getValue()[0];
+          $messageBase = new FormattableMarkup("For scenario per-date '@per_date' with default '@default': ", [
+            '@per_date' => $per_date_label,
+            '@default' => $default_label,
+          ]);
+          $this->assertEqual($date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, ['timezone' => DateTimeItemInterface::STORAGE_TIMEZONE]), $fieldValues['value'], $messageBase . "checking correct date is stored.");
+          $message = new FormattableMarkup("the time zone stored is expected to be '@expected', actually is '@actual'.", [
+            '@expected' => $timezone_expected_label,
+            '@actual' => array_search($fieldValues['timezone'], $timezones),
+          ]);
+          $this->assertEqual($timezone_expected, $fieldValues['timezone'], $messageBase . $message);
+        }
+      }
+    }
+  }
+
+  /**
+   * Sets the timezone for the currently logged in user.
+   */
+  protected function setLoggedInUserTimezone($timezone) {
+    $user = User::load($this->loggedInUser->id());
+    $user->set('timezone', $timezone)->save();
+    $this->setCurrentUser($user);
+  }
+
   /**
    * Tests Date List Widget functionality.
    */
@@ -396,11 +725,11 @@ public function testDatelistWidget() {
     $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
     $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
 
-    // Assert that Hour and Minute Elements do not appear on Date Only
+    // Assert that Hour and Minute Elements do not appear on Date Only.
     $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-hour\"]", NULL, 'Hour element not found on Date Only.');
     $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-minute\"]", NULL, 'Minute element not found on Date Only.');
 
-    // Go to the form display page to assert that increment option does not appear on Date Only
+    // Go to the form display page to assert that increment option does not appear on Date Only.
     $fieldEditUrl = 'entity_test/structure/entity_test/form-display';
     $this->drupalGet($fieldEditUrl);
 
@@ -426,7 +755,7 @@ public function testDatelistWidget() {
       ->save();
     \Drupal::entityManager()->clearCachedFieldDefinitions();
 
-    // Go to the form display page to assert that increment option does appear on Date Time
+    // Go to the form display page to assert that increment option does appear on Date Time.
     $fieldEditUrl = 'entity_test/structure/entity_test/form-display';
     $this->drupalGet($fieldEditUrl);
 
@@ -774,6 +1103,7 @@ public function testInvalidField() {
     $this->drupalGet('entity_test/add');
     $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Date element found.');
     $this->assertFieldByName("{$field_name}[0][value][time]", '', 'Time element found.');
+    $this->assertNoFieldByName("{$field_name}[0][timezone]", '', 'No timezone field appears for dates that do not collect timezone information.');
 
     // Submit invalid dates and ensure they is not accepted.
     $date_value = '';
diff --git a/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDateonlyTest.php b/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDateonlyTest.php
index 0fb404d2b9..e70e12af29 100644
--- a/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDateonlyTest.php
+++ b/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDateonlyTest.php
@@ -91,6 +91,7 @@ protected function getExpectedNormalizedEntity() {
       static::$fieldName => [
         [
           'value' => $this->entity->get(static::$fieldName)->value,
+          'timezone' => $this->entity->get(static::$fieldName)->value_timezone,
         ],
       ],
     ];
diff --git a/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDatetimeTest.php b/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDatetimeTest.php
index cdd4d605d0..7f5d0fdc1f 100644
--- a/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDatetimeTest.php
+++ b/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDatetimeTest.php
@@ -91,6 +91,7 @@ protected function getExpectedNormalizedEntity() {
       static::$fieldName => [
         [
           'value' => $this->entity->get(static::$fieldName)->value,
+          'timezone' => $this->entity->get(static::$fieldName)->value_timezone,
         ],
       ],
     ];
diff --git a/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDatetimeTimezoneTest.php b/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDatetimeTimezoneTest.php
new file mode 100644
index 0000000000..81b9174045
--- /dev/null
+++ b/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDatetimeTimezoneTest.php
@@ -0,0 +1,142 @@
+<?php
+
+namespace Drupal\Tests\datetime\Functional\EntityResource\EntityTest;
+
+use Drupal\Core\Url;
+use Drupal\entity_test\Entity\EntityTest;
+use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\Tests\entity_test\Functional\Rest\EntityTestResourceTestBase;
+use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
+use GuzzleHttp\RequestOptions;
+
+/**
+ * Tests the datetime field constraint with 'datetime' items.
+ *
+ * @group datetime
+ */
+class EntityTestDatetimeTimezoneTest extends EntityTestResourceTestBase {
+
+  use AnonResourceTestTrait;
+
+  /**
+   * The ISO date string to use throughout the test.
+   *
+   * @var string
+   */
+  protected static $dateString = '2017-03-01T20:02:00';
+
+  /**
+   * The Timezone string to use throughout the test.
+   */
+  protected static $timezone = 'Australia/Sydney';
+
+  /**
+   * Datetime test field name.
+   *
+   * @var string
+   */
+  protected static $fieldName = 'field_datetime';
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['datetime', 'entity_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // Add datetime field.
+    FieldStorageConfig::create([
+      'field_name' => static::$fieldName,
+      'type' => 'datetime',
+      'entity_type' => static::$entityTypeId,
+      'settings' => ['datetime_type' => DateTimeItem::DATETIME_TYPE_DATETIME, 'timezone_storage' => TRUE],
+    ])
+      ->save();
+
+    FieldConfig::create([
+      'field_name' => static::$fieldName,
+      'entity_type' => static::$entityTypeId,
+      'bundle' => $this->entity->bundle(),
+      'settings' => ['default_value' => static::$dateString],
+    ])
+      ->save();
+
+    // Reload entity so that it has the new field.
+    $this->entity = $this->entityStorage->load($this->entity->id());
+    $this->entity->set(static::$fieldName, ['value' => static::$dateString, 'timezone' => static::$timezone]);
+    $this->entity->save();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function createEntity() {
+    $entity_test = EntityTest::create([
+      'name' => 'Llama',
+      'type' => static::$entityTypeId,
+      static::$fieldName => static::$dateString,
+    ]);
+    $entity_test->setOwnerId(0);
+    $entity_test->save();
+
+    return $entity_test;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getExpectedNormalizedEntity() {
+    return parent::getExpectedNormalizedEntity() + [
+      static::$fieldName => [
+        [
+          'value' => $this->entity->get(static::$fieldName)->value,
+          'timezone' => $this->entity->get(static::$fieldName)->timezone,
+        ],
+      ],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getNormalizedPostEntity() {
+    return parent::getNormalizedPostEntity() + [
+      static::$fieldName => [
+        [
+          'value' => static::$dateString,
+          'timezone' => static::$timezone,
+        ],
+      ],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function assertNormalizationEdgeCases($method, Url $url, array $request_options) {
+    parent::assertNormalizationEdgeCases($method, $url, $request_options);
+
+    if ($this->entity->getEntityType()->hasKey('bundle')) {
+      $fieldName = static::$fieldName;
+
+      // DX: 422 when timezone format is incorrect.
+      $normalization = $this->getNormalizedPostEntity();
+      $value = '2017-03-01T01:02:03';
+      $timezone = 'Mars/Phobos';
+      $normalization[static::$fieldName][0]['value'] = $value;
+      $normalization[static::$fieldName][0]['timezone'] = $timezone;
+
+      $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
+      $response = $this->request($method, $url, $request_options);
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The timezone value '{$timezone}' did not parse properly.\n";
+      $this->assertResourceErrorResponse(422, $message, $response);
+    }
+  }
+
+}
diff --git a/core/modules/datetime/tests/src/Functional/Update/DatetimeUpdateTest.php b/core/modules/datetime/tests/src/Functional/Update/DatetimeUpdateTest.php
new file mode 100644
index 0000000000..374fd9d12f
--- /dev/null
+++ b/core/modules/datetime/tests/src/Functional/Update/DatetimeUpdateTest.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\Tests\datetime\Functional\Update;
+
+use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+use Drupal\node\Entity\Node;
+
+/**
+ * Tests that settings are properly updated during database updates.
+ *
+ * @group datetime
+ */
+class DatetimeUpdateTest extends UpdatePathTestBase {
+
+  /**
+   * The config factory service.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->configFactory = $this->container->get('config.factory');
+    $this->entityFieldManager = $this->container->get('entity_field.manager');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
+      __DIR__ . '/../../../fixtures/update/datetime-date_8001-values.php',
+    ];
+  }
+
+  /**
+   * Tests that timezone changes are applied.
+   *
+   * @see datetime_update_8001()
+   */
+  public function testTimezoneSettings() {
+    // Load the 'node.field_date_1' field storage config, and check that the is
+    // no timezone storage yet.
+    $config = $this->configFactory->get('field.storage.node.field_date_1');
+    $settings = $config->get('settings');
+    $this->assertFalse(array_key_exists('timezone_storage', $settings));
+
+    // Run updates.
+    $this->runUpdates();
+
+    // Check the timezone storage has been added and defaulted to FALSE.
+    $config = $this->configFactory->get('field.storage.node.field_date_1');
+    $settings = $config->get('settings');
+    $this->assertTrue(array_key_exists('timezone_storage', $settings));
+    $this->assertFalse($settings['timezone_storage']);
+
+    // Ensure timezone column added and NULL for existing content.
+    $node = Node::load(1);
+    $this->assertNull($node->field_date_1->timezone);
+  }
+
+}
diff --git a/core/modules/datetime/tests/src/Kernel/DateTimeItemTest.php b/core/modules/datetime/tests/src/Kernel/DateTimeItemTest.php
index 75f892a254..b10649560f 100644
--- a/core/modules/datetime/tests/src/Kernel/DateTimeItemTest.php
+++ b/core/modules/datetime/tests/src/Kernel/DateTimeItemTest.php
@@ -39,6 +39,9 @@ class DateTimeItemTest extends FieldKernelTestBase {
    */
   public static $modules = ['datetime'];
 
+  /**
+   * {@inheritdoc}
+   */
   protected function setUp() {
     parent::setUp();
 
@@ -47,7 +50,10 @@ protected function setUp() {
       'field_name' => 'field_datetime',
       'type' => 'datetime',
       'entity_type' => 'entity_test',
-      'settings' => ['datetime_type' => DateTimeItem::DATETIME_TYPE_DATETIME],
+      'settings' => [
+        'datetime_type' => DateTimeItem::DATETIME_TYPE_DATETIME,
+        'timezone_storage' => FALSE,
+      ],
     ]);
     $this->fieldStorage->save();
     $this->field = FieldConfig::create([
@@ -346,4 +352,28 @@ public function dateonlyValidationProvider() {
     ];
   }
 
+  /**
+   * Tests DateTimeItem with per-date time zone storage.
+   */
+  public function testTimezoneDate() {
+    /** @var \Drupal\field\FieldStorageConfigInterface $field_storage */
+    $field_storage = FieldStorageConfig::load('entity_test.field_datetime');
+    $field_storage->setSetting('timezone_storage', TRUE);
+    $field_storage->save();
+
+    // Use a non-UTC timezone.
+    $timezone = 'America/Yellowknife';
+
+    $entity = EntityTest::create();
+    $value = '2014-01-01T20:00:00Z';
+
+    $entity->set('field_datetime', ['value' => $value, 'timezone' => $timezone]);
+    $entity->save();
+
+    // Load the entity.
+    $id = $entity->id();
+    $entity = EntityTest::load($id);
+    $this->assertEqual($timezone, $entity->field_datetime[0]->timezone, '"timezone" property can be set.');
+  }
+
 }
diff --git a/core/modules/datetime_range/config/schema/datetime_range.schema.yml b/core/modules/datetime_range/config/schema/datetime_range.schema.yml
index f0f93259d3..fa2e57ec19 100644
--- a/core/modules/datetime_range/config/schema/datetime_range.schema.yml
+++ b/core/modules/datetime_range/config/schema/datetime_range.schema.yml
@@ -55,7 +55,7 @@ field.formatter.settings.daterange_custom:
       translation context: 'Date range separator'
 
 field.widget.settings.daterange_datelist:
-  type: mapping
+  type: field.widget.settings.datetime_base
   label: 'Date range select list display format settings'
   mapping:
     increment:
@@ -69,5 +69,5 @@ field.widget.settings.daterange_datelist:
       label: 'Time type'
 
 field.widget.settings.daterange_default:
-  type: mapping
+  type: field.widget.settings.datetime_base
   label: 'Date range default display format settings'
diff --git a/core/modules/datetime_range/src/Plugin/Field/FieldType/DateRangeItem.php b/core/modules/datetime_range/src/Plugin/Field/FieldType/DateRangeItem.php
index 7c34ed127c..c17e194d7f 100644
--- a/core/modules/datetime_range/src/Plugin/Field/FieldType/DateRangeItem.php
+++ b/core/modules/datetime_range/src/Plugin/Field/FieldType/DateRangeItem.php
@@ -55,6 +55,9 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel
       ->setClass(DateTimeComputed::class)
       ->setSetting('date source', 'end_value');
 
+    $properties['timezone'] = DataDefinition::create('string')
+      ->setLabel(t('Timezone'));
+
     return $properties;
   }
 
@@ -83,6 +86,18 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
 
     $element['datetime_type']['#options'][static::DATETIME_TYPE_ALLDAY] = $this->t('All Day');
 
+    $element['timezone_storage']['#states'] = [
+      // Hide the option for per-date time zone storage if this is a date-only
+      // or all-day field.
+        'visible' => [
+          ':input[name="settings[datetime_type]"]' => ['value' => static::DATETIME_TYPE_DATETIME],
+        ],
+        'disabled' => [
+          [':input[name="settings[datetime_type]"]' => ['value' => static::DATETIME_TYPE_DATE]],
+          [':input[name="settings[datetime_type]"]' => ['value' => static::DATETIME_TYPE_ALLDAY]],
+        ],
+      ];
+
     return $element;
   }
 
diff --git a/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeDefaultWidget.php b/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeDefaultWidget.php
index 79f7994692..5039dd7ee0 100644
--- a/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeDefaultWidget.php
+++ b/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeDefaultWidget.php
@@ -9,6 +9,7 @@
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
 
 /**
  * Plugin implementation of the 'daterange_default' widget.
@@ -33,8 +34,8 @@ class DateRangeDefaultWidget extends DateRangeWidgetBase implements ContainerFac
   /**
    * {@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);
+  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ConfigFactoryInterface $config_factory, EntityStorageInterface $date_storage) {
+    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $config_factory);
 
     $this->dateStorage = $date_storage;
   }
@@ -49,6 +50,7 @@ public static function create(ContainerInterface $container, array $configuratio
       $configuration['field_definition'],
       $configuration['settings'],
       $configuration['third_party_settings'],
+      $container->get('config.factory'),
       $container->get('entity_type.manager')->getStorage('date_format')
     );
   }
diff --git a/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeWidgetBase.php b/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeWidgetBase.php
index d98804e011..c41a61be2c 100644
--- a/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeWidgetBase.php
+++ b/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeWidgetBase.php
@@ -30,6 +30,9 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
       '#title' => $this->t('End date'),
     ] + $element['value'];
 
+    // The time zone selector should be present only once.
+    $element['end_value']['#expose_timezone'] = FALSE;
+
     if ($items[$delta]->start_date) {
       /** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */
       $start_date = $items[$delta]->start_date;
@@ -76,9 +79,17 @@ public function massageFormValues(array $values, array $form, FormStateInterface
           // we need to explicitly set the timezone.
           $start_date->setTimeZone($user_timezone)->setTime(0, 0, 0);
         }
+        elseif ($datetime_type !== DateRangeItem::DATETIME_TYPE_DATE) {
+          // Store the time zone if appropriate.
+          $item['timezone'] = '';
+          if ($this->shouldStoreTimezone($start_date, $form, $form_state) && $this->getFieldSetting('timezone_storage') === TRUE) {
+            $item['timezone'] = $start_date->getTimezone()->getName();
+          }
+        }
 
         // Adjust the date for storage.
         $item['value'] = $start_date->setTimezone($storage_timezone)->format($storage_format);
+
       }
 
       if (!empty($item['end_value']) && $item['end_value'] instanceof DrupalDateTime) {
@@ -124,6 +135,7 @@ public function validateStartEnd(array &$element, FormStateInterface $form_state
         }
       }
     }
+
   }
 
 }
diff --git a/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php b/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php
index 9cecd605f0..fd9cb5e048 100644
--- a/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php
+++ b/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php
@@ -5,6 +5,7 @@
 use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\Core\Datetime\Entity\DateFormat;
+use Drupal\datetime\Plugin\Field\FieldFormatter\DateTimeFormatterBase;
 use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
 use Drupal\Tests\datetime\Functional\DateTestBase;
 use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
@@ -368,7 +369,7 @@ public function testDatetimeRangeField() {
 
     // Verify that the 'timezone_override' setting works.
     $this->displayOptions['type'] = 'daterange_custom';
-    $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A', 'timezone_override' => 'America/New_York'] + $this->defaultSettings;
+    $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A', 'timezone_override' => 'America/New_York', 'timezone_default' => DateTimeFormatterBase::TIMEZONE_FIXED] + $this->defaultSettings;
     entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
       ->setComponent($field_name, $this->displayOptions)
       ->save();
@@ -536,7 +537,7 @@ public function testAlldayRangeField() {
 
     // Verify that the 'timezone_override' setting works.
     $this->displayOptions['type'] = 'daterange_custom';
-    $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A', 'timezone_override' => 'America/New_York'] + $this->defaultSettings;
+    $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A', 'timezone_override' => 'America/New_York', 'timezone_default' => DateTimeFormatterBase::TIMEZONE_FIXED] + $this->defaultSettings;
     entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
       ->setComponent($field_name, $this->displayOptions)
       ->save();
diff --git a/core/modules/datetime_range/tests/src/Kernel/DateRangeItemTest.php b/core/modules/datetime_range/tests/src/Kernel/DateRangeItemTest.php
index 658159a8aa..e012e92fe4 100644
--- a/core/modules/datetime_range/tests/src/Kernel/DateRangeItemTest.php
+++ b/core/modules/datetime_range/tests/src/Kernel/DateRangeItemTest.php
@@ -49,7 +49,10 @@ protected function setUp() {
       'field_name' => mb_strtolower($this->randomMachineName()),
       'entity_type' => 'entity_test',
       'type' => 'daterange',
-      'settings' => ['datetime_type' => DateRangeItem::DATETIME_TYPE_DATE],
+      'settings' => [
+        'datetime_type' => DateRangeItem::DATETIME_TYPE_DATE,
+        'timezone_storage' => FALSE,
+      ],
     ]);
     $this->fieldStorage->save();
 
diff --git a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldFormatterSettingsTest.php b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldFormatterSettingsTest.php
index 7347c0470d..acb0e15c54 100644
--- a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldFormatterSettingsTest.php
+++ b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldFormatterSettingsTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\Tests\field\Kernel\Migrate\d6;
 
 use Drupal\Core\Entity\Entity\EntityViewDisplay;
+use Drupal\datetime\Plugin\Field\FieldFormatter\DateTimeFormatterBase;
 use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
 
 /**
@@ -93,10 +94,10 @@ public function testEntityDisplaySettings() {
     $expected['weight'] = 2;
     $expected['type'] = 'number_decimal';
     $expected['settings'] = [
-       'scale' => 2,
-       'decimal_separator' => '.',
-       'thousand_separator' => ',',
-       'prefix_suffix' => TRUE,
+      'scale' => 2,
+      'decimal_separator' => '.',
+      'thousand_separator' => ',',
+      'prefix_suffix' => TRUE,
     ];
     $component = $display->getComponent('field_test_three');
     $this->assertIdentical($expected, $component);
@@ -159,7 +160,7 @@ public function testEntityDisplaySettings() {
     $this->assertIdentical($expected, $component);
 
     // Test date field.
-    $defaults = ['format_type' => 'fallback', 'timezone_override' => ''];
+    $defaults = ['format_type' => 'fallback', 'timezone_override' => '', 'timezone_default' => DateTimeFormatterBase::TIMEZONE_USER, 'timezone_per_date' => FALSE];
     $expected['weight'] = 10;
     $expected['type'] = 'datetime_default';
     $expected['settings'] = ['format_type' => 'fallback'] + $defaults;
diff --git a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldInstanceTest.php b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldInstanceTest.php
index 6c4e29c4be..625780c130 100644
--- a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldInstanceTest.php
+++ b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldInstanceTest.php
@@ -107,7 +107,7 @@ public function testFieldInstanceMigration() {
     $this->assertInstanceOf(FieldConfig::class, $field);
     $this->assertSame('Date Field', $field->label());
     $this->assertSame('An example date field.', $field->getDescription());
-    $expected = ['datetime_type' => 'datetime'];
+    $expected = ['datetime_type' => 'datetime', 'timezone_storage' => FALSE];
     $this->assertSame($expected, $field->getSettings());
     $expected = [
       [
@@ -123,7 +123,7 @@ public function testFieldInstanceMigration() {
     $this->assertInstanceOf(FieldConfig::class, $field);
     $this->assertSame('Datetime Field', $field->label());
     $this->assertSame('An example datetime field.', $field->getDescription());
-    $expected = ['datetime_type' => 'datetime'];
+    $expected = ['datetime_type' => 'datetime', 'timezone_storage' => FALSE];
     $this->assertSame($expected, $field->getSettings());
     $expected = [];
     $this->assertSame($expected, $field->getDefaultValueLiteral());
diff --git a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldWidgetSettingsTest.php b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldWidgetSettingsTest.php
index c6f47fb1bd..aef606784b 100644
--- a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldWidgetSettingsTest.php
+++ b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldWidgetSettingsTest.php
@@ -91,7 +91,11 @@ public function testWidgetSettings() {
     $component = $form_display->getComponent('field_test_date');
     $expected['type'] = 'datetime_default';
     $expected['weight'] = 10;
-    $expected['settings'] = [];
+    $expected['settings'] = [
+      'timezone_default' => 'user',
+      'timezone_override' => '',
+      'timezone_per_date' => FALSE,
+    ];
     $this->assertIdentical($expected, $component);
 
     $component = $form_display->getComponent('field_test_datestamp');
diff --git a/core/modules/field_ui/src/Form/FieldStorageAddForm.php b/core/modules/field_ui/src/Form/FieldStorageAddForm.php
index 5afd358a67..8dea103b66 100644
--- a/core/modules/field_ui/src/Form/FieldStorageAddForm.php
+++ b/core/modules/field_ui/src/Form/FieldStorageAddForm.php
@@ -416,7 +416,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
   }
 
   /**
-   * Configures the field for the default form mode.
+   * Configures the newly created field for the default view and form modes.
    *
    * @param string $field_name
    *   The field name.
@@ -442,7 +442,7 @@ protected function configureEntityFormDisplay($field_name, $widget_id = NULL, ar
   }
 
   /**
-   * Configures the field for the default view mode.
+   * Configures the newly created field for the default view and form modes.
    *
    * @param string $field_name
    *   The field name.
