diff --git a/core/lib/Drupal/Core/Datetime/Element/Datetime.php b/core/lib/Drupal/Core/Datetime/Element/Datetime.php
index e238ff5..5ee1b03 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,
         ];
       }
@@ -191,6 +200,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
@@ -223,7 +234,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;
@@ -328,6 +341,17 @@ public static function processAjaxForm(&$element, FormStateInterface $form_state
       }
     }
 
+    // Expose a timezone selector.
+    if (!empty($element['#expose_timezone']) && $element['#expose_timezone']) {
+      $element['timezone'] = array(
+        '#type' => 'select',
+        '#options' => array_combine(\DateTimeZone::listIdentifiers(), \DateTimeZone::listIdentifiers()),
+        // 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 a009011..28d143c 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 0000000..5f20a22
--- /dev/null
+++ b/core/modules/datetime/datetime.install
@@ -0,0 +1,100 @@
+<?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_mapping->getFieldTableName($field_name);
+          }
+        }
+
+        if (!empty($field_changes)) {
+
+          $change_list[$entity_type_id] = [
+            'field_storage_definitions' => $field_changes,
+            'revision_table' => $entity_type->getRevisionTable(),
+          ];
+        }
+      }
+    }
+
+    $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['field_storage_definitions'] as $field_name => $field_table) {
+      $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_table, $timezone_field_name, $field_spec);
+      $schema->addIndex($field_table, 'value_timezone', $field_schema['indexes']['value_timezone'], $field_schema);
+
+      if ($field_changes['revision_table']) {
+        $revision_table = $field_changes['revision_table'] . '__' . $field_name;
+        $schema->addField($revision_table, $timezone_field_name, $field_spec);
+        $schema->addIndex($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 0000000..47450c7
--- /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 0000000..03930cf
--- /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 e3b9daf..c4dbda2 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeCustomFormatter.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeCustomFormatter.php
@@ -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 fa65496..cb6839c 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 a6680f6..0579264 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.
@@ -34,6 +37,13 @@
   protected $dateFormatStorage;
 
   /**
+   * A config factory for retrieving required config settings.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $config;
+
+  /**
    * Constructs a new DateTimeDefaultFormatter.
    *
    * @param string $plugin_id
@@ -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 8ddfdda..5b673c8 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php
@@ -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 3264069..12559ce 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 2a4ffcd..044a3e2 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 ce86e04..e021950 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.
     foreach ($values as &$item) {
       if (!empty($item['value']) && $item['value'] instanceof DrupalDateTime) {
         $date = $item['value'];
@@ -62,15 +147,38 @@ public function massageFormValues(array $values, array $form, FormStateInterface
             $format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
             break;
         }
-        // Adjust the date for storage.
-        $date->setTimezone(new \DateTimezone(DateTimeItemInterface::STORAGE_TIMEZONE));
-        $item['value'] = $date->format($format);
+
+        // 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();
+        }
+
+        $item['value'] = $date->format($format, ['timezone' => DateTimeItemInterface::STORAGE_TIMEZONE]);
       }
     }
     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->getSetting('timezone_per_date');
+  }
+
+  /**
    * Creates a date object for use as a default value.
    *
    * This will take a default value, apply the proper timezone for display in
@@ -90,6 +198,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/Tests/DateTestBase.php b/core/modules/datetime/src/Tests/DateTestBase.php
index ca43b8d..73affaf 100644
--- a/core/modules/datetime/src/Tests/DateTestBase.php
+++ b/core/modules/datetime/src/Tests/DateTestBase.php
@@ -36,6 +36,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.
    *
    * @var \Drupal\field\Entity\FieldStorageConfig
@@ -101,6 +108,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/DateTestBase.php b/core/modules/datetime/tests/src/Functional/DateTestBase.php
index 21bad50..535f09c 100644
--- a/core/modules/datetime/tests/src/Functional/DateTestBase.php
+++ b/core/modules/datetime/tests/src/Functional/DateTestBase.php
@@ -31,6 +31,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.
    *
    * @var \Drupal\field\Entity\FieldStorageConfig
@@ -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 5acd6fc..686fe6f 100644
--- a/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php
+++ b/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php
@@ -12,6 +12,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.
@@ -25,7 +27,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,16 +317,6 @@ public function testDatetimeField() {
     $output = $this->renderTestEntity($id);
     $this->assertContains($expected, $output, SafeMarkup::format('Formatted date field using datetime_custom format displayed as %expected.', ['%expected' => $expected]));
 
-    // 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;
-    entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
-      ->setComponent($field_name, $this->displayOptions)
-      ->save();
-    $expected = $date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']);
-    $output = $this->renderTestEntity($id);
-    $this->assertContains($expected, $output, SafeMarkup::format('Formatted date field using datetime_custom format displayed as %expected.', ['%expected' => $expected]));
-
     // Verify that the 'datetime_time_ago' formatter works for intervals in the
     // past.  First update the test entity so that the date difference always
     // has the same interval.  Since the database always stores UTC, and the
@@ -368,6 +364,330 @@ public function testDatetimeField() {
     ]);
     $output = $this->renderTestEntity($id);
     $this->assertContains((string) $expected, $output, SafeMarkup::format('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 allows per-date timezones, but the widget does not.
+        'not allowed' => FALSE,
+        // 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 widget doesn't allow the editor
+        // to select a per-date time zone.
+        $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);
   }
 
   /**
@@ -776,6 +1096,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/Kernel/DateTimeItemTest.php b/core/modules/datetime/tests/src/Kernel/DateTimeItemTest.php
index 75f892a..23645b0 100644
--- a/core/modules/datetime/tests/src/Kernel/DateTimeItemTest.php
+++ b/core/modules/datetime/tests/src/Kernel/DateTimeItemTest.php
@@ -47,7 +47,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 +349,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/src/Plugin/Field/FieldType/DateRangeItem.php b/core/modules/datetime_range/src/Plugin/Field/FieldType/DateRangeItem.php
index 7c34ed1..c17e194 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 79f7994..5039dd7 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 85fda9b..f5c12fe 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;
@@ -65,19 +68,25 @@ public function massageFormValues(array $values, array $form, FormStateInterface
             // All day fields start at midnight on the starting date, but are
             // stored like datetime fields, so we need to adjust the time.
             // This function is called twice, so to prevent a double conversion
-            // we need to explicitly set the timezone.
-            $start_date->setTimeZone(timezone_open(drupal_get_user_timezone()));
+            // we need to explicitly set the time zone. The correct time zone is
+            // the default time zone, because all day fields don't allow
+            // per-date timezones.
+            $start_date->setTimezone(timezone_open($this->getDefaultTimezone()));
             $start_date->setTime(0, 0, 0);
             $format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
             break;
 
           default:
             $format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
+            // 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();
+            }
             break;
         }
         // Adjust the date for storage.
-        $start_date->setTimezone(new \DateTimezone(DateTimeItemInterface::STORAGE_TIMEZONE));
-        $item['value'] = $start_date->format($format);
+        $item['value'] = $start_date->format($format, ['timezone' => DateTimeItemInterface::STORAGE_TIMEZONE]);
       }
 
       if (!empty($item['end_value']) && $item['end_value'] instanceof DrupalDateTime) {
@@ -92,19 +101,37 @@ public function massageFormValues(array $values, array $form, FormStateInterface
             // All day fields end at midnight on the end date, but are
             // stored like datetime fields, so we need to adjust the time.
             // This function is called twice, so to prevent a double conversion
-            // we need to explicitly set the timezone.
-            $end_date->setTimeZone(timezone_open(drupal_get_user_timezone()));
+            // we need to explicitly set the time zone. The correct time zone is
+            // the default time zone, because all day fields don't allow
+            // per-date timezones.
+            $end_date->setTimezone(timezone_open($this->getDefaultTimezone()));
             $end_date->setTime(23, 59, 59);
             $format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
             break;
 
           default:
             $format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
+            if ($this->getSetting('timezone_per_date') && $this->getFieldSetting('timezone_storage') === TRUE) {
+              // The datetime element will have interpreted the end date using
+              // the time zone specified in #date_timezone, because the time
+              // zone selector is not present on the end date. Therefore we
+              // have to restore the value back to its raw state, and
+              // reinterpret it using the time zone from the start value, which
+              // used the time zone selector. This is safe only because the
+              // datetime element's valueCallback sets the time zone on its
+              // returned date object to be the time zone it used to interpret
+              // the inputted time, and the start time must always be present.
+              $raw_end_date = $end_date->format($format);
+              if (!empty($start_date) && $start_date instanceof DrupalDateTime) {
+                $timezone = $start_date->getTimezone()->getName();
+                drupal_set_message(rand() . "starttz:" . $timezone);
+                $end_date = DrupalDateTime::createFromFormat($format, $raw_end_date, $timezone);
+              }
+            }
             break;
         }
         // Adjust the date for storage.
-        $end_date->setTimezone(new \DateTimezone(DateTimeItemInterface::STORAGE_TIMEZONE));
-        $item['end_value'] = $end_date->format($format);
+        $item['end_value'] = $end_date->format($format, ['timezone' => DateTimeItemInterface::STORAGE_TIMEZONE]);
       }
     }
 
@@ -134,6 +161,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 bd83b98..a7e71f5 100644
--- a/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php
+++ b/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php
@@ -369,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();
@@ -537,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 54f805f..30cefb0 100644
--- a/core/modules/datetime_range/tests/src/Kernel/DateRangeItemTest.php
+++ b/core/modules/datetime_range/tests/src/Kernel/DateRangeItemTest.php
@@ -50,7 +50,10 @@ protected function setUp() {
       'field_name' => Unicode::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 7bf3ad5..15a31ff 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 6c4e29c..625780c 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 6e8e040..8a88795 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 97e3ce7..4666fa6 100644
--- a/core/modules/field_ui/src/Form/FieldStorageAddForm.php
+++ b/core/modules/field_ui/src/Form/FieldStorageAddForm.php
@@ -412,7 +412,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.
@@ -430,7 +430,7 @@ protected function configureEntityFormDisplay($field_name, $widget_id = NULL) {
   }
 
   /**
-   * 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.
