diff --git a/core/config/schema/core.entity.schema.yml b/core/config/schema/core.entity.schema.yml
index 73d84bc4df..00b3697d37 100644
--- a/core/config/schema/core.entity.schema.yml
+++ b/core/config/schema/core.entity.schema.yml
@@ -331,6 +331,35 @@ field.formatter.settings.timestamp:
     timezone:
       type: string
       label: 'Time zone'
+    tooltip:
+      type: mapping
+      label: Tooltip
+      mapping:
+        date_format:
+          type: string
+          label: 'Tooltip date format'
+        custom_date_format:
+          type: string
+          label: 'Tooltip custom date format'
+    timeago:
+      type: mapping
+      label: 'Options to show as timeago'
+      mapping:
+        enabled:
+          type: boolean
+          label: 'Show as time ago'
+        future_format:
+          type: string
+          label: 'Future format'
+        past_format:
+          type: string
+          label: 'Past format'
+        granularity:
+          type: integer
+          label: 'Granularity'
+        refresh:
+          type: integer
+          label: 'Refresh interval in seconds'
 
 field.formatter.settings.timestamp_ago:
   type: mapping
diff --git a/core/core.libraries.yml b/core/core.libraries.yml
index 3157e3aeb9..15768877dc 100644
--- a/core/core.libraries.yml
+++ b/core/core.libraries.yml
@@ -941,3 +941,13 @@ drupal.dialog.off_canvas:
     - core/drupal.announce
     - core/drupal.dialog
     - core/drupal.dialog.ajax
+
+drupal.timeago:
+  version: VERSION
+  js:
+    misc/timeago.js:
+      defer: true
+  dependencies:
+    - core/jquery
+    - core/drupal
+    - core/jquery.once
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampFormatter.php
index f73f08340a..c94cdaed78 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampFormatter.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampFormatter.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
 
+use Drupal\Component\Render\FormattableMarkup;
+use Drupal\Component\Serialization\Json;
 use Drupal\Core\Datetime\DateFormatterInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Field\FieldDefinitionInterface;
@@ -94,6 +96,17 @@ public static function defaultSettings() {
       'date_format' => 'medium',
       'custom_date_format' => '',
       'timezone' => '',
+      'tooltip' => [
+        'date_format' => 'long',
+        'custom_date_format' => '',
+      ],
+      'timeago' => [
+        'enabled' => FALSE,
+        'future_format' => '@interval hence',
+        'past_format' => '@interval ago',
+        'granularity' => 2,
+        'refresh' => 60,
+      ],
     ] + parent::defaultSettings();
   }
 
@@ -101,42 +114,101 @@ public static function defaultSettings() {
    * {@inheritdoc}
    */
   public function settingsForm(array $form, FormStateInterface $form_state) {
-    $elements = parent::settingsForm($form, $form_state);
+    $form = parent::settingsForm($form, $form_state);
 
     $date_formats = [];
-
     foreach ($this->dateFormatStorage->loadMultiple() as $machine_name => $value) {
       $date_formats[$machine_name] = $this->t('@name format: @date', ['@name' => $value->label(), '@date' => $this->dateFormatter->format(REQUEST_TIME, $machine_name)]);
     }
-
     $date_formats['custom'] = $this->t('Custom');
 
-    $elements['date_format'] = [
+    $form['date_format'] = [
       '#type' => 'select',
       '#title' => $this->t('Date format'),
       '#options' => $date_formats,
       '#default_value' => $this->getSetting('date_format') ?: 'medium',
     ];
 
-    $elements['custom_date_format'] = [
+    $form['custom_date_format'] = [
       '#type' => 'textfield',
       '#title' => $this->t('Custom date format'),
       '#description' => $this->t('See <a href="http://php.net/manual/function.date.php" target="_blank">the documentation for PHP date formats</a>.'),
       '#default_value' => $this->getSetting('custom_date_format') ?: '',
+      '#states' => $this->buildStates(['date_format'], ['value' => 'custom']),
     ];
 
-    $elements['custom_date_format']['#states']['visible'][] = [
-      ':input[name="fields[' . $this->fieldDefinition->getName() . '][settings_edit_form][settings][date_format]"]' => ['value' => 'custom'],
-    ];
-
-    $elements['timezone'] = [
+    $form['timezone'] = [
       '#type' => 'select',
       '#title' => $this->t('Time zone'),
       '#options' => ['' => $this->t('- Default site/user time zone -')] + system_time_zones(FALSE, TRUE),
       '#default_value' => $this->getSetting('timezone'),
     ];
 
-    return $elements;
+    $tooltip = $this->getSetting('tooltip');
+    $form['tooltip']['#tree'] = TRUE;
+    $form['tooltip']['date_format'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Tooltip date format'),
+      '#description' => $this->t('Select the date format to be used for the title and displayed on mouse hover.'),
+      '#options' => $date_formats,
+      '#default_value' => $tooltip['date_format'],
+    ];
+
+    $form['tooltip']['custom_date_format'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Tooltip custom date format'),
+      '#description' => $this->t('See <a href="http://php.net/manual/function.date.php" target="_blank">the documentation for PHP date formats</a>.'),
+      '#default_value' => $tooltip['custom_date_format'],
+      '#states' => $this->buildStates(['tooltip', 'date_format'], ['value' => 'custom']),
+    ];
+
+    $timeago = $this->getSetting('timeago');
+    $form['timeago']['#tree'] = TRUE;
+    $form['timeago']['enabled'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t("Display as 'time ago'"),
+      '#description' => $this->t("If checked, the timestamp will be displayed as a 'time ago' string, in javascript enabled browsers. With javascript disabled, the main behavior is in place."),
+      '#default_value' => $timeago['enabled'],
+    ];
+
+    $states = $this->buildStates(['timeago', 'enabled'], ['checked' => TRUE]);
+
+    $form['timeago']['future_format'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Future format'),
+      '#description' => $this->t('Use <em>@interval</em> where you want the formatted interval text to appear.'),
+      '#default_value' => $timeago['future_format'],
+      '#states' => $states,
+    ];
+
+    $form['timeago']['past_format'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Past format'),
+      '#description' => $this->t('Use <em>@interval</em> where you want the formatted interval text to appear.'),
+      '#default_value' => $timeago['past_format'],
+      '#states' => $states,
+    ];
+
+    $form['timeago']['granularity'] = [
+      '#type' => 'number',
+      '#title' => $this->t('Granularity'),
+      '#description' => $this->t('How many time interval units should be shown in the formatted output.'),
+      '#default_value' => $timeago['granularity'],
+      '#min' => 1,
+      '#max' => 7,
+      '#states' => $states,
+    ];
+
+    $form['timeago']['refresh'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Refresh interval (seconds)'),
+      '#description' => $this->t("The interval to refresh the displayed 'time ago'."),
+      '#default_value' => $timeago['refresh'],
+      '#options' => $this->getRefreshIntervals(),
+      '#states' => $states,
+    ];
+
+    return $form;
   }
 
   /**
@@ -147,13 +219,41 @@ public function settingsSummary() {
 
     $date_format = $this->getSetting('date_format');
     $summary[] = $this->t('Date format: @date_format', ['@date_format' => $date_format]);
-    if ($this->getSetting('date_format') === 'custom' && ($custom_date_format = $this->getSetting('custom_date_format'))) {
+    if ($date_format === 'custom' && ($custom_date_format = $this->getSetting('custom_date_format'))) {
       $summary[] = $this->t('Custom date format: @custom_date_format', ['@custom_date_format' => $custom_date_format]);
     }
     if ($timezone = $this->getSetting('timezone')) {
       $summary[] = $this->t('Time zone: @timezone', ['@timezone' => $timezone]);
     }
 
+    $tooltip = $this->getSetting('tooltip');
+    $summary[] = $this->t('Tooltip date format: @date_format', ['@date_format' => $tooltip['date_format']]);
+    if ($tooltip['date_format'] === 'custom' && $tooltip['custom_date_format']) {
+      $summary[] = $this->t('Tooltip custom date format: @custom_date_format', ['@custom_date_format' => $tooltip['custom_date_format']]);
+    }
+
+    $timeago = $this->getSetting('timeago');
+    if ($timeago['enabled']) {
+      $summary[] = $this->t("Displayed as 'time ago'");
+
+      $options = ['granularity' => $timeago['granularity']];
+
+      $timestamp = strtotime('1 year 1 month 1 week 1 day 1 hour 1 minute');
+      $interval = $this->dateFormatter->formatTimeDiffUntil($timestamp, $options);
+      $display = new FormattableMarkup($timeago['future_format'], ['@interval' => $interval]);
+      $summary[] = $this->t('Future date: %display', ['%display' => $display]);
+
+      $timestamp = strtotime('-1 year -1 month -1 week -1 day -1 hour -1 minute');
+      $interval = $this->dateFormatter->formatTimeDiffSince($timestamp, $options);
+      $display = new FormattableMarkup($timeago['past_format'], ['@interval' => $interval]);
+      $summary[] = $this->t('Past date: %display', ['%display' => $display]);
+
+      if ($timeago['refresh']) {
+        $refresh_intervals = $this->getRefreshIntervals();
+        $summary[] = $this->t('Refresh in @interval', ['@interval' => $refresh_intervals[$timeago['refresh']]]);
+      }
+    }
+
     return $summary;
   }
 
@@ -166,26 +266,90 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
     $date_format = $this->getSetting('date_format');
     $custom_date_format = '';
     $timezone = $this->getSetting('timezone') ?: NULL;
-    $langcode = NULL;
+    $tooltip = $this->getSetting('tooltip');
+    $langcode = $tooltip_langcode = NULL;
+    $timeago = $this->getSetting('timeago');
 
     // If an RFC2822 date format is requested, then the month and day have to
     // be in English. @see http://www.faqs.org/rfcs/rfc2822.html
     if ($date_format === 'custom' && ($custom_date_format = $this->getSetting('custom_date_format')) === 'r') {
       $langcode = 'en';
     }
+    if ($tooltip['date_format'] === 'custom' && $tooltip['custom_date_format'] === 'r') {
+      $tooltip_langcode = 'en';
+    }
 
     foreach ($items as $delta => $item) {
       $elements[$delta] = [
+        '#theme' => 'time',
+        '#attributes' => [
+          // The representation of the date/time as RFC 3339 "date-time".
+          // @see https://www.w3.org/TR/2011/WD-html-markup-20110405/time.html
+          'datetime' => $this->dateFormatter->format($item->value, 'html_datetime', '', $timezone),
+          // Show a tooltip on mouse hover as title. When the time is displayed
+          // as 'time ago', it helps the user to read the exact date.
+          'title' => $this->dateFormatter->format($item->value, $tooltip['date_format'], $tooltip['custom_date_format'], $timezone, $tooltip_langcode),
+        ],
+        '#text' => $this->dateFormatter->format($item->value, $date_format, $custom_date_format, $timezone, $langcode),
         '#cache' => [
           'contexts' => [
             'timezone',
           ],
         ],
-        '#markup' => $this->dateFormatter->format($item->value, $date_format, $custom_date_format, $timezone, $langcode),
       ];
+      if ($timeago['enabled']) {
+        $elements[$delta]['#attributes']['class'][] = 'js-timeago';
+        $settings = $timeago;
+        unset($settings['enabled']);
+        $elements[$delta]['#attributes']['data-drupal-timeago'] = base64_encode(Json::encode($settings));
+      }
+    }
+
+    if ($timeago['enabled']) {
+      $elements['#attached']['library'][] = 'core/drupal.timeago';
     }
 
     return $elements;
   }
 
+  /**
+   * Builds the #states key for form elements.
+   *
+   * @param string[] $path
+   *   The remote element path.
+   * @param array $conditions
+   *   The conditions to be checked.
+   *
+   * @return array[]
+   *   The #states array.
+   */
+  protected function buildStates(array $path, array $conditions) {
+    $path = '[' . implode('][', $path) . ']';
+    return [
+      'visible' => [
+        [
+          ":input[name='fields[{$this->fieldDefinition->getName()}][settings_edit_form][settings]$path']" => $conditions,
+        ]
+      ],
+    ];
+  }
+
+  /**
+   * Returns the refresh interval options for the jQuery 'time ago' display.
+   *
+   * @return array
+   *   A list of refresh time intervals.
+   */
+  protected function getRefreshIntervals() {
+    return [
+      0 => $this->t('No refresh'),
+      1 => $this->t('1 second'),
+      10 => $this->t('10 seconds'),
+      15 => $this->t('15 seconds'),
+      30 => $this->t('30 seconds'),
+      60 => $this->t('1 minute'),
+      300 => $this->t('5 minutes'),
+    ];
+  }
+
 }
diff --git a/core/misc/timeago.es6.js b/core/misc/timeago.es6.js
new file mode 100644
index 0000000000..4e50746c31
--- /dev/null
+++ b/core/misc/timeago.es6.js
@@ -0,0 +1,141 @@
+/**
+ * @file
+ * Dynamic timeago formatting.
+ */
+
+(function ($, Drupal) {
+
+  Drupal.dateFormatter = Drupal.dateFormatter || { };
+
+  /**
+   * Replaces a timestamp, formatted as a date/time, with a 'time ago' string.
+   *
+   * @type {Drupal~behavior}
+   */
+  Drupal.behaviors.timestampAsTimeAgo = {
+    attach: function (context, settings) {
+      $('time.js-timeago')
+        .once('timeago')
+        .each(function () {
+          var timestamp = (new Date($(this).attr('datetime'))).getTime();
+          var timeagoSettings = JSON.parse(atob($(this).attr('data-drupal-timeago')));
+          var options = { granularity: timeagoSettings.granularity };
+          var element = this;
+
+          var showTimeAgo = function () {
+            var interval, format;
+            now = Date.now();
+
+            if (timestamp > now) {
+              interval = Drupal.dateFormatter.formatDiff(now, timestamp, options);
+              format = timeagoSettings.future_format;
+            }
+            else {
+              interval = Drupal.dateFormatter.formatDiff(timestamp, now, options);
+              format = timeagoSettings.past_format;
+            }
+            $(element).text(Drupal.t(format, {'@interval': interval}));
+          };
+
+          showTimeAgo();
+          if (timeagoSettings.refresh > 0) {
+            setInterval(showTimeAgo, timeagoSettings.refresh * 1000);
+          }
+        })
+    }
+  };
+
+  /**
+   * Formats a time interval between two timestamps.
+   *
+   * @param {number} from
+   *   A UNIX timestamp, defining the from date and time.
+   * @param {number} to
+   *   A UNIX timestamp, defining the to date and time.
+   * @param {object} [options={granularity:2,strict:true}]
+   *   An optional object with additional options. The following keys can be
+   *   used:
+   *   - granularity: An integer value that signals how many different units to
+   *     display in the string. Defaults to 2.
+   *   - strict: A boolean value indicating whether or not the 'from' timestamp
+   *     can be after the 'to' timestamp. If true (default) and 'from' is after
+   *     'to', the result string will be "0 seconds". If false and 'from' is
+   *     after 'to', the result string will be the formatted time difference.
+   *
+   * @return {string}
+   *   A translated string representation of the interval.
+   */
+  Drupal.dateFormatter.formatDiff = function (from, to, options) {
+    // Provide sane defaults.
+    options = options || {};
+    $.extend({granularity: 2, strict: true}, options);
+
+    if (options.strict && from > to) {
+      return Drupal.t('0 seconds');
+    }
+
+    // Compute the difference in seconds.
+    var diff = Math.round((to - from) / 1000);
+
+    var oneDay = 60 * 60 * 24;
+    var intervals = {
+      year: 365 * oneDay,
+      month: 30 * oneDay,
+      week: 7 * oneDay,
+      day: oneDay,
+      hour: 60 * 60,
+      minute: 60,
+      second: 1
+    };
+
+    var output = [];
+    var units;
+    var granularity = options.granularity;
+
+    $.each(intervals, function (interval, duration) {
+      units = Math.floor(diff / duration);
+      if (units > 0) {
+        diff %= (units * duration);
+        switch (interval) {
+          case 'year':
+            output.push(Drupal.formatPlural(units, '1 year', '@count years'));
+            break;
+          case 'month':
+            output.push(Drupal.formatPlural(units, '1 month', '@count months'));
+            break;
+          case 'week':
+            output.push(Drupal.formatPlural(units, '1 week', '@count weeks'));
+            break;
+          case 'day':
+            output.push(Drupal.formatPlural(units, '1 day', '@count days'));
+            break;
+          case 'hour':
+            output.push(Drupal.formatPlural(units, '1 hour', '@count hours'));
+            break;
+          case 'minute':
+            output.push(Drupal.formatPlural(units, '1 minute', '@count minutes'));
+            break;
+          case 'second':
+            output.push(Drupal.formatPlural(units, '1 second', '@count seconds'));
+            break;
+        }
+        granularity--;
+        if (granularity <= 0) {
+          // Limit the granularity of the output.
+          return false;
+        }
+      }
+      else if (output.length > 0) {
+        // Exit if there was previous output but not any output at this level,
+        // to avoid skipping levels and getting output like "1 year 1 second".
+        return false;
+      }
+    });
+
+    if (output.length === 0) {
+      return Drupal.t('0 seconds');
+    }
+    return output.join(' ');
+  };
+
+}(jQuery, Drupal));
diff --git a/core/misc/timeago.js b/core/misc/timeago.js
new file mode 100644
index 0000000000..8e99a41360
--- /dev/null
+++ b/core/misc/timeago.js
@@ -0,0 +1,108 @@
+/**
+* DO NOT EDIT THIS FILE.
+* See the following change record for more information,
+* https://www.drupal.org/node/2815083
+* @preserve
+**/
+
+(function ($, Drupal) {
+
+  Drupal.dateFormatter = Drupal.dateFormatter || {};
+
+  Drupal.behaviors.timestampAsTimeAgo = {
+    attach: function attach(context, settings) {
+      $('time.js-timeago').once('timeago').each(function () {
+        var timestamp = new Date($(this).attr('datetime')).getTime();
+        var timeagoSettings = JSON.parse(atob($(this).attr('data-drupal-timeago')));
+        var options = { granularity: timeagoSettings.granularity };
+        var element = this;
+
+        var showTimeAgo = function showTimeAgo() {
+          var interval, format;
+          now = Date.now();
+
+          if (timestamp > now) {
+            interval = Drupal.dateFormatter.formatDiff(now, timestamp, options);
+            format = timeagoSettings.future_format;
+          } else {
+            interval = Drupal.dateFormatter.formatDiff(timestamp, now, options);
+            format = timeagoSettings.past_format;
+          }
+          $(element).text(Drupal.t(format, { '@interval': interval }));
+        };
+
+        showTimeAgo();
+        if (timeagoSettings.refresh > 0) {
+          setInterval(showTimeAgo, timeagoSettings.refresh * 1000);
+        }
+      });
+    }
+  };
+
+  Drupal.dateFormatter.formatDiff = function (from, to, options) {
+    options = options || {};
+    $.extend({ granularity: 2, strict: true }, options);
+
+    if (options.strict && from > to) {
+      return Drupal.t('0 seconds');
+    }
+
+    var diff = Math.round((to - from) / 1000);
+
+    var oneDay = 60 * 60 * 24;
+    var intervals = {
+      year: 365 * oneDay,
+      month: 30 * oneDay,
+      week: 7 * oneDay,
+      day: oneDay,
+      hour: 60 * 60,
+      minute: 60,
+      second: 1
+    };
+
+    var output = [];
+    var units;
+    var granularity = options.granularity;
+
+    $.each(intervals, function (interval, duration) {
+      units = Math.floor(diff / duration);
+      if (units > 0) {
+        diff %= units * duration;
+        switch (interval) {
+          case 'year':
+            output.push(Drupal.formatPlural(units, '1 year', '@count years'));
+            break;
+          case 'month':
+            output.push(Drupal.formatPlural(units, '1 month', '@count months'));
+            break;
+          case 'week':
+            output.push(Drupal.formatPlural(units, '1 week', '@count weeks'));
+            break;
+          case 'day':
+            output.push(Drupal.formatPlural(units, '1 day', '@count days'));
+            break;
+          case 'hour':
+            output.push(Drupal.formatPlural(units, '1 hour', '@count hours'));
+            break;
+          case 'minute':
+            output.push(Drupal.formatPlural(units, '1 minute', '@count minutes'));
+            break;
+          case 'second':
+            output.push(Drupal.formatPlural(units, '1 second', '@count seconds'));
+            break;
+        }
+        granularity--;
+        if (granularity <= 0) {
+          return false;
+        }
+      } else if (output.length > 0) {
+        return false;
+      }
+    });
+
+    if (output.length === 0) {
+      return Drupal.t('0 seconds');
+    }
+    return output.join(' ');
+  };
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/system/system.post_update.php b/core/modules/system/system.post_update.php
index 66fa24aa16..3a7099d79d 100644
--- a/core/modules/system/system.post_update.php
+++ b/core/modules/system/system.post_update.php
@@ -8,6 +8,7 @@
 use Drupal\Core\Entity\Display\EntityDisplayInterface;
 use Drupal\Core\Entity\Entity\EntityFormDisplay;
 use Drupal\Core\Entity\Entity\EntityViewDisplay;
+use Drupal\Core\Field\Plugin\Field\FieldFormatter\TimestampFormatter;
 
 /**
  * Re-save all configuration entities to recalculate dependencies.
@@ -130,3 +131,26 @@ function system_post_update_change_delete_action_plugins() {
     }
   }
 }
+
+/**
+ * Update timestamp formatter settings.
+ */
+function system_post_update_timestamp_formatter() {
+  /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $entity_view_display */
+  foreach (EntityViewDisplay::loadMultiple() as $entity_view_display) {
+    $changed = FALSE;
+    foreach ($entity_view_display->getComponents() as $name => $component) {
+      if ($formatter_plugin = $entity_view_display->getRenderer($name)) {
+        // Check also potential plugins extending TimestampFormatter.
+        if (get_class($formatter_plugin) === TimestampFormatter::class || is_subclass_of($formatter_plugin, TimestampFormatter::class)) {
+          $component['settings'] = $formatter_plugin->getSettings();
+          $entity_view_display->setComponent($name, $component);
+          $changed = TRUE;
+        }
+      }
+    }
+    if ($changed) {
+      $entity_view_display->save();
+    }
+  }
+}
diff --git a/core/modules/system/templates/time.html.twig b/core/modules/system/templates/time.html.twig
index 9fc2dfa41a..c61ff4b277 100644
--- a/core/modules/system/templates/time.html.twig
+++ b/core/modules/system/templates/time.html.twig
@@ -19,4 +19,6 @@
  * @see http://www.w3.org/TR/html5-author/the-time-element.html#attr-time-datetime
  */
 #}
+{% spaceless %}
 <time{{ attributes }}>{{ text }}</time>
+{% endspaceless %}
diff --git a/core/modules/system/tests/fixtures/update/drupal-8.timestamp-formatter-settings-2921810.php b/core/modules/system/tests/fixtures/update/drupal-8.timestamp-formatter-settings-2921810.php
new file mode 100644
index 0000000000..a322ae3737
--- /dev/null
+++ b/core/modules/system/tests/fixtures/update/drupal-8.timestamp-formatter-settings-2921810.php
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * @file
+ * Contains database additions to drupal-8-rc1.bare.standard.php.gz for testing
+ * the upgrade path of https://www.drupal.org/project/drupal/issues/2921810.
+ */
+
+use Drupal\Core\Database\Database;
+use Drupal\field\Entity\FieldStorageConfig;
+
+$connection = Database::getConnection();
+
+// Add a new timestamp field 'field_foo'.
+$connection->insert('config')
+  ->fields(['collection', 'name', 'data'])->values([
+    'collection' => '',
+    'name' => 'field.storage.node.field_foo',
+    'data' => $field_storage = 'a:16:{s:4:"uuid";s:36:"815278cf-a977-4700-aad9-d58034de0115";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:1:{s:6:"module";a:1:{i:0;s:4:"node";}}s:2:"id";s:14:"node.field_foo";s:10:"field_name";s:9:"field_foo";s:11:"entity_type";s:4:"node";s:4:"type";s:9:"timestamp";s:8:"settings";a:0:{}s:6:"module";s:4:"core";s:6:"locked";b:0;s:11:"cardinality";i:1;s:12:"translatable";b:1;s:7:"indexes";a:0:{}s:22:"persist_with_no_fields";b:0;s:14:"custom_storage";b:0;}',
+  ])->values([
+    'collection' => '',
+    'name' => 'field.field.node.page.field_foo',
+    'data' => 'a:16:{s:4:"uuid";s:36:"ea669e7e-532e-41ad-9322-13ba6a9901b0";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:1:{s:6:"config";a:2:{i:0;s:28:"field.storage.node.field_foo";i:1;s:14:"node.type.page";}}s:2:"id";s:19:"node.page.field_foo";s:10:"field_name";s:9:"field_foo";s:11:"entity_type";s:4:"node";s:6:"bundle";s:4:"page";s:5:"label";s:3:"Foo";s:11:"description";s:0:"";s:8:"required";b:0;s:12:"translatable";b:0;s:13:"default_value";a:1:{i:0;a:1:{s:5:"value";i:1511630653;}}s:22:"default_value_callback";s:0:"";s:8:"settings";a:0:{}s:10:"field_type";s:9:"timestamp";}',
+  ])->execute();
+
+$connection->insert('key_value')
+  ->fields(['collection', 'name', 'value'])
+  ->values([
+    'collection' => 'config.entity.key_store.field_config',
+    'name' => 'uuid:ea669e7e-532e-41ad-9322-13ba6a9901b0',
+    'value' => 'a:1:{i:0;s:31:"field.field.node.page.field_foo";}',
+  ])
+  ->values([
+    'collection' => 'config.entity.key_store.field_storage_config',
+    'name' => 'uuid:815278cf-a977-4700-aad9-d58034de0115',
+    'value' => 'a:1:{i:0;s:28:"field.storage.node.field_foo";}',
+  ])
+  ->values([
+    'collection' => 'entity.storage_schema.sql',
+    'name' => 'node.field_schema_data.field_foo',
+    'value' => 'a:2:{s:15:"node__field_foo";a:4:{s:11:"description";s:38:"Data storage for node field field_foo.";s:6:"fields";a:7:{s:6:"bundle";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:128;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:88:"The field instance bundle to which this row belongs, used when deleting a field instance";}s:7:"deleted";a:5:{s:4:"type";s:3:"int";s:4:"size";s:4:"tiny";s:8:"not null";b:1;s:7:"default";i:0;s:11:"description";s:60:"A boolean indicating whether this data item has been deleted";}s:9:"entity_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:38:"The entity id this data is attached to";}s:11:"revision_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:47:"The entity revision id this data is attached to";}s:8:"langcode";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:32;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:37:"The language code for this data item.";}s:5:"delta";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:67:"The sequence number for this data item, used for multi-value fields";}s:15:"field_foo_value";a:2:{s:4:"type";s:3:"int";s:8:"not null";b:1;}}s:11:"primary key";a:4:{i:0;s:9:"entity_id";i:1;s:7:"deleted";i:2;s:5:"delta";i:3;s:8:"langcode";}s:7:"indexes";a:2:{s:6:"bundle";a:1:{i:0;s:6:"bundle";}s:11:"revision_id";a:1:{i:0;s:11:"revision_id";}}}s:24:"node_revision__field_foo";a:4:{s:11:"description";s:50:"Revision archive storage for node field field_foo.";s:6:"fields";a:7:{s:6:"bundle";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:128;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:88:"The field instance bundle to which this row belongs, used when deleting a field instance";}s:7:"deleted";a:5:{s:4:"type";s:3:"int";s:4:"size";s:4:"tiny";s:8:"not null";b:1;s:7:"default";i:0;s:11:"description";s:60:"A boolean indicating whether this data item has been deleted";}s:9:"entity_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:38:"The entity id this data is attached to";}s:11:"revision_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:47:"The entity revision id this data is attached to";}s:8:"langcode";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:32;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:37:"The language code for this data item.";}s:5:"delta";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:67:"The sequence number for this data item, used for multi-value fields";}s:15:"field_foo_value";a:2:{s:4:"type";s:3:"int";s:8:"not null";b:1;}}s:11:"primary key";a:5:{i:0;s:9:"entity_id";i:1;s:11:"revision_id";i:2;s:7:"deleted";i:3;s:5:"delta";i:4;s:8:"langcode";}s:7:"indexes";a:2:{s:6:"bundle";a:1:{i:0;s:6:"bundle";}s:11:"revision_id";a:1:{i:0;s:11:"revision_id";}}}}',
+  ])
+  ->execute();
+
+$data = $connection->select('key_value')
+  ->fields('key_value', ['value'])
+  ->condition('collection', 'entity.definitions.bundle_field_map')
+  ->condition('name', 'node')
+  ->execute()
+  ->fetchField();
+$data = unserialize($data);
+$data['field_foo']['type'] = 'timestamp';
+$data['field_foo']['bundles']['page'] = 'page';
+$connection->update('key_value')
+  ->fields(['value' => serialize($data)])
+  ->condition('collection', 'entity.definitions.bundle_field_map')
+  ->condition('name', 'node')
+  ->execute();
+
+$data = $connection->select('key_value')
+  ->fields('key_value', ['value'])
+  ->condition('collection', 'entity.definitions.installed')
+  ->condition('name', 'node.field_storage_definitions')
+  ->execute()
+  ->fetchField();
+$data = unserialize($data);
+$data['field_foo'] = new FieldStorageConfig(unserialize($field_storage));
+$connection->update('key_value')
+  ->fields(['value' => serialize($data)])
+  ->condition('collection', 'entity.definitions.installed')
+  ->condition('name', 'node.field_storage_definitions')
+  ->execute();
+
+// Add the new field to default entity view display.
+$config = $connection->select('config')
+  ->fields('config', ['data'])
+  ->condition('collection', '')
+  ->condition('name', 'core.entity_view_display.node.page.default')
+  ->execute()
+  ->fetchField();
+$config = unserialize($config);
+$config['content']['field_foo'] = [
+  'type' => 'timestamp',
+  'label' => 'hidden',
+  'weight' => 0,
+  'region' => 'content',
+  'settings' => [
+    'date_format' => 'custom',
+    'custom_date_format' => 'Y-m-d',
+    'timezone' => '',
+  ],
+  'third_party_settings' => [],
+];
+
+$connection->update('config')
+  ->fields(['data' => serialize($config)])
+  ->condition('collection', '')
+  ->condition('name', 'core.entity_view_display.node.page.default')
+  ->execute();
diff --git a/core/modules/system/tests/src/Functional/Update/TimestampFormatterSettingsUpdateTest.php b/core/modules/system/tests/src/Functional/Update/TimestampFormatterSettingsUpdateTest.php
new file mode 100644
index 0000000000..fb18bbd323
--- /dev/null
+++ b/core/modules/system/tests/src/Functional/Update/TimestampFormatterSettingsUpdateTest.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Drupal\Tests\system\Functional\Update;
+
+use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+
+/**
+ * Tests the update of timestamp formatter settings in entity view displays.
+ *
+ * @group system
+ */
+class TimestampFormatterSettingsUpdateTest extends UpdatePathTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8-rc1.bare.standard.php.gz',
+      __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.timestamp-formatter-settings-2921810.php',
+    ];
+  }
+
+  /**
+   * Tests system_post_update_timestamp_formatter().
+   *
+   * @see system_post_update_timestamp_formatter()
+   */
+  public function testPostUpdateTimestampFormatter() {
+    $config_factory = \Drupal::configFactory();
+    $name = 'core.entity_view_display.node.page.default';
+    $trail = 'content.field_foo.settings';
+
+    // Check that 'tooltip' and 'timeago' are missing before update.
+    $settings = $config_factory->get($name)->get($trail);
+    $this->assertArrayNotHasKey('tooltip', $settings);
+    $this->assertArrayNotHasKey('timeago', $settings);
+
+    $this->runUpdates();
+
+    // Check that 'tooltip' and 'timeago' were created after update.
+    $settings = $config_factory->get($name)->get($trail);
+    $this->assertArrayHasKey('tooltip', $settings);
+    $this->assertArrayHasKey('timeago', $settings);
+  }
+
+}
diff --git a/core/modules/views/tests/src/Kernel/Handler/FieldFieldTest.php b/core/modules/views/tests/src/Kernel/Handler/FieldFieldTest.php
index 8bdab81b63..34b0690ca6 100644
--- a/core/modules/views/tests/src/Kernel/Handler/FieldFieldTest.php
+++ b/core/modules/views/tests/src/Kernel/Handler/FieldFieldTest.php
@@ -377,41 +377,41 @@ public function testComplexRender() {
     $this->assertEqual("1, 3", $executable->getStyle()->getField(0, 'field_test_multiple'));
     $this->assertEqual("1", $executable->getStyle()->getField(0, 'field_test_multiple_1'));
     $this->assertEqual("3", $executable->getStyle()->getField(0, 'field_test_multiple_2'));
-    $this->assertEqual($date_formatter->format($this->testUsers[0]->getCreatedTime(), 'custom', 'Y'), $executable->getStyle()->getField(0, 'created'));
-    $this->assertEqual($date_formatter->format($this->testUsers[0]->getCreatedTime(), 'custom', 'H:i:s'), $executable->getStyle()->getField(0, 'created_1'));
-    $this->assertEqual($date_formatter->format($this->testUsers[0]->getCreatedTime(), 'fallback'), $executable->getStyle()->getField(0, 'created_2'));
+    $this->assertEqual($date_formatter->format($this->testUsers[0]->getCreatedTime(), 'custom', 'Y'), strip_tags($executable->getStyle()->getField(0, 'created')));
+    $this->assertEqual($date_formatter->format($this->testUsers[0]->getCreatedTime(), 'custom', 'H:i:s'), strip_tags($executable->getStyle()->getField(0, 'created_1')));
+    $this->assertEqual($date_formatter->format($this->testUsers[0]->getCreatedTime(), 'fallback'), strip_tags($executable->getStyle()->getField(0, 'created_2')));
 
     $this->assertEqual($this->testUsers[1]->getTimeZone(), $executable->getStyle()->getField(1, 'timezone'));
     $this->assertEqual("7, 0", $executable->getStyle()->getField(1, 'field_test_multiple'));
     $this->assertEqual("7", $executable->getStyle()->getField(1, 'field_test_multiple_1'));
     $this->assertEqual("0", $executable->getStyle()->getField(1, 'field_test_multiple_2'));
-    $this->assertEqual($date_formatter->format($this->testUsers[1]->getCreatedTime(), 'custom', 'Y'), $executable->getStyle()->getField(1, 'created'));
-    $this->assertEqual($date_formatter->format($this->testUsers[1]->getCreatedTime(), 'custom', 'H:i:s'), $executable->getStyle()->getField(1, 'created_1'));
-    $this->assertEqual($date_formatter->format($this->testUsers[1]->getCreatedTime(), 'fallback'), $executable->getStyle()->getField(1, 'created_2'));
+    $this->assertEqual($date_formatter->format($this->testUsers[1]->getCreatedTime(), 'custom', 'Y'), strip_tags($executable->getStyle()->getField(1, 'created')));
+    $this->assertEqual($date_formatter->format($this->testUsers[1]->getCreatedTime(), 'custom', 'H:i:s'), strip_tags($executable->getStyle()->getField(1, 'created_1')));
+    $this->assertEqual($date_formatter->format($this->testUsers[1]->getCreatedTime(), 'fallback'), strip_tags($executable->getStyle()->getField(1, 'created_2')));
 
     $this->assertEqual($this->testUsers[2]->getTimeZone(), $executable->getStyle()->getField(2, 'timezone'));
     $this->assertEqual("3, 5", $executable->getStyle()->getField(2, 'field_test_multiple'));
     $this->assertEqual("3", $executable->getStyle()->getField(2, 'field_test_multiple_1'));
     $this->assertEqual("5", $executable->getStyle()->getField(2, 'field_test_multiple_2'));
-    $this->assertEqual($date_formatter->format($this->testUsers[2]->getCreatedTime(), 'custom', 'Y'), $executable->getStyle()->getField(2, 'created'));
-    $this->assertEqual($date_formatter->format($this->testUsers[2]->getCreatedTime(), 'custom', 'H:i:s'), $executable->getStyle()->getField(2, 'created_1'));
-    $this->assertEqual($date_formatter->format($this->testUsers[2]->getCreatedTime(), 'fallback'), $executable->getStyle()->getField(2, 'created_2'));
+    $this->assertEqual($date_formatter->format($this->testUsers[2]->getCreatedTime(), 'custom', 'Y'), strip_tags($executable->getStyle()->getField(2, 'created')));
+    $this->assertEqual($date_formatter->format($this->testUsers[2]->getCreatedTime(), 'custom', 'H:i:s'), strip_tags($executable->getStyle()->getField(2, 'created_1')));
+    $this->assertEqual($date_formatter->format($this->testUsers[2]->getCreatedTime(), 'fallback'), strip_tags($executable->getStyle()->getField(2, 'created_2')));
 
     $this->assertEqual($this->testUsers[3]->getTimeZone(), $executable->getStyle()->getField(3, 'timezone'));
     $this->assertEqual("9, 9", $executable->getStyle()->getField(3, 'field_test_multiple'));
     $this->assertEqual("9", $executable->getStyle()->getField(3, 'field_test_multiple_1'));
     $this->assertEqual("9", $executable->getStyle()->getField(3, 'field_test_multiple_2'));
-    $this->assertEqual($date_formatter->format($this->testUsers[3]->getCreatedTime(), 'custom', 'Y'), $executable->getStyle()->getField(3, 'created'));
-    $this->assertEqual($date_formatter->format($this->testUsers[3]->getCreatedTime(), 'custom', 'H:i:s'), $executable->getStyle()->getField(3, 'created_1'));
-    $this->assertEqual($date_formatter->format($this->testUsers[3]->getCreatedTime(), 'fallback'), $executable->getStyle()->getField(3, 'created_2'));
+    $this->assertEqual($date_formatter->format($this->testUsers[3]->getCreatedTime(), 'custom', 'Y'), strip_tags($executable->getStyle()->getField(3, 'created')));
+    $this->assertEqual($date_formatter->format($this->testUsers[3]->getCreatedTime(), 'custom', 'H:i:s'), strip_tags($executable->getStyle()->getField(3, 'created_1')));
+    $this->assertEqual($date_formatter->format($this->testUsers[3]->getCreatedTime(), 'fallback'), strip_tags($executable->getStyle()->getField(3, 'created_2')));
 
     $this->assertEqual($this->testUsers[4]->getTimeZone(), $executable->getStyle()->getField(4, 'timezone'));
     $this->assertEqual("9, 0", $executable->getStyle()->getField(4, 'field_test_multiple'));
     $this->assertEqual("9", $executable->getStyle()->getField(4, 'field_test_multiple_1'));
     $this->assertEqual("0", $executable->getStyle()->getField(4, 'field_test_multiple_2'));
-    $this->assertEqual($date_formatter->format($this->testUsers[4]->getCreatedTime(), 'custom', 'Y'), $executable->getStyle()->getField(4, 'created'));
-    $this->assertEqual($date_formatter->format($this->testUsers[4]->getCreatedTime(), 'custom', 'H:i:s'), $executable->getStyle()->getField(4, 'created_1'));
-    $this->assertEqual($date_formatter->format($this->testUsers[4]->getCreatedTime(), 'fallback'), $executable->getStyle()->getField(4, 'created_2'));
+    $this->assertEqual($date_formatter->format($this->testUsers[4]->getCreatedTime(), 'custom', 'Y'), strip_tags($executable->getStyle()->getField(4, 'created')));
+    $this->assertEqual($date_formatter->format($this->testUsers[4]->getCreatedTime(), 'custom', 'H:i:s'), strip_tags($executable->getStyle()->getField(4, 'created_1')));
+    $this->assertEqual($date_formatter->format($this->testUsers[4]->getCreatedTime(), 'fallback'), strip_tags($executable->getStyle()->getField(4, 'created_2')));
   }
 
   /**
diff --git a/core/themes/classy/templates/field/time.html.twig b/core/themes/classy/templates/field/time.html.twig
index a9e363e432..4ac3453dfa 100644
--- a/core/themes/classy/templates/field/time.html.twig
+++ b/core/themes/classy/templates/field/time.html.twig
@@ -19,4 +19,6 @@
  * @see http://www.w3.org/TR/html5-author/the-time-element.html#attr-time-datetime
  */
 #}
+{% spaceless %}
 <time{{ attributes.addClass('datetime') }}>{{ text }}</time>
+{% endspaceless %}
diff --git a/core/themes/stable/templates/field/time.html.twig b/core/themes/stable/templates/field/time.html.twig
index cb93f9d576..6b057e49b5 100644
--- a/core/themes/stable/templates/field/time.html.twig
+++ b/core/themes/stable/templates/field/time.html.twig
@@ -19,4 +19,6 @@
  * @see http://www.w3.org/TR/html5-author/the-time-element.html#attr-time-datetime
  */
 #}
+{% spaceless %}
 <time{{ attributes }}>{{ text }}</time>
+{% endspaceless %}
