diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml
index 3b5bbe4190..5d5cbe20d5 100644
--- a/core/config/schema/core.data_types.schema.yml
+++ b/core/config/schema/core.data_types.schema.yml
@@ -743,6 +743,16 @@ field.value.float:
       type: float
       label: 'Value'
 
+# Schema for the configuration of the Timestamp field type.
+
+field.value.timestamp:
+  type: mapping
+  label: 'Default value'
+  mapping:
+    value:
+      type: integer
+      label: 'Value'
+
 # Text with a text format.
 text_format:
   type: mapping
diff --git a/core/config/schema/core.entity.schema.yml b/core/config/schema/core.entity.schema.yml
index df2a2fd02d..3ffba610a6 100644
--- a/core/config/schema/core.entity.schema.yml
+++ b/core/config/schema/core.entity.schema.yml
@@ -324,7 +324,34 @@ 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'
 
+# @deprecated
 field.formatter.settings.timestamp_ago:
   type: mapping
   label: 'Timestamp ago display format settings'
diff --git a/core/core.libraries.yml b/core/core.libraries.yml
index 159182aeb6..2c2872e87d 100644
--- a/core/core.libraries.yml
+++ b/core/core.libraries.yml
@@ -937,3 +937,12 @@ drupal.dialog.off_canvas:
     - core/drupal.announce
     - core/drupal.dialog
     - core/drupal.dialog.ajax
+
+drupal.timeago:
+  version: VERSION
+  js:
+    misc/timeago.js: {}
+  dependencies:
+    - core/jquery
+    - core/drupal
+    - core/jquery.once
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampAgoFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampAgoFormatter.php
index 55a2fb1a04..c9ff0b792b 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampAgoFormatter.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampAgoFormatter.php
@@ -2,6 +2,9 @@
 
 namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
 
+// @todo Add the correct link to change notice.
+// @trigger_error('\Drupal\Core\Field\Plugin\Field\FieldFormatter\TimestampAgoFormatter is deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\Field\Plugin\Field\FieldFormatter\TimestampFormatter instead. See https://www.drupal.org/node/TOBEREPLACED.', E_USER_DEPRECATED);
+
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Datetime\DateFormatterInterface;
@@ -16,9 +19,13 @@
 /**
  * Plugin implementation of the 'timestamp' formatter as time ago.
  *
+ * @deprecated in Drupal 8.5.0, will be removed before Drupal 9.0.0. Use the
+ *   \Drupal\Core\Field\Plugin\Field\FieldFormatter\TimestampFormatter formatter
+ *   instead and configure it with "Display as 'time ago'" option.
+ *
  * @FieldFormatter(
  *   id = "timestamp_ago",
- *   label = @Translation("Time ago"),
+ *   label = @Translation("Time ago (deprecated)"),
  *   field_types = {
  *     "timestamp",
  *     "created",
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..2712786d8d 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,16 @@ 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,
+      ],
     ] + parent::defaultSettings();
   }
 
@@ -101,42 +113,92 @@ 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,
+    ];
+
+    return $form;
   }
 
   /**
@@ -147,13 +209,36 @@ 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]);
+    }
+
     return $summary;
   }
 
@@ -166,26 +251,74 @@ 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, $langcode),
+          // 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';
+      }
+    }
+
+    if ($timeago['enabled']) {
+      $elements['#attached']['library'][] = 'core/drupal.timeago';
+      // Pass the 'time ago' display settings. These are customized for each
+      // field, but not field item, so we pass them ony once for all items.
+      $settings = $timeago;
+      unset($settings['enabled']);
+      $elements['#attributes']['data-drupal-timeago'] = base64_encode(Json::encode($settings));
     }
 
     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,
+        ]
+      ],
+    ];
+  }
+
 }
diff --git a/core/misc/timeago.es6.js b/core/misc/timeago.es6.js
new file mode 100644
index 0000000000..d8de27ee57
--- /dev/null
+++ b/core/misc/timeago.es6.js
@@ -0,0 +1,135 @@
+/**
+ * @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) {
+      // @todo Refresh on an interval configurable via formatter settings.
+      $('time.js-timeago')
+        .once('timeago')
+        .each(function () {
+          var timestamp = (new Date($(this).attr('datetime'))).getTime();
+          // Get the widget settings.
+          var parentElement = $(this).closest('[data-drupal-timeago]')[0];
+          var timeagoSettings = JSON.parse(atob($(parentElement).attr('data-drupal-timeago')));
+          var interval, format;
+          var options = { granularity: timeagoSettings.granularity };
+          var 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;
+          }
+          $(this).text(Drupal.t(format, {'@interval': interval}));
+        })
+    }
+  };
+
+  /**
+   * 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 = {
+      y: 365 * oneDay,
+      m: 30 * oneDay,
+      w: 7 * oneDay,
+      d: oneDay,
+      h: 60 * 60,
+      i: 60,
+      s: 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 'y':
+            output.push(Drupal.formatPlural(units, '1 year', '@count years'));
+            break;
+          case 'm':
+            output.push(Drupal.formatPlural(units, '1 months', '@count months'));
+            break;
+          case 'w':
+            output.push(Drupal.formatPlural(units, '1 week', '@count weeks'));
+            break;
+          case 'd':
+            output.push(Drupal.formatPlural(units, '1 day', '@count days'));
+            break;
+          case 'h':
+            output.push(Drupal.formatPlural(units, '1 hour', '@count hours'));
+            break;
+          case 'i':
+            output.push(Drupal.formatPlural(units, '1 minute', '@count minutes'));
+            break;
+          case 's':
+            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..12df8684ae
--- /dev/null
+++ b/core/misc/timeago.js
@@ -0,0 +1,101 @@
+/**
+* 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 parentElement = $(this).closest('[data-drupal-timeago]')[0];
+        var timeagoSettings = JSON.parse(atob($(parentElement).attr('data-drupal-timeago')));
+        var interval, format;
+        var options = { granularity: timeagoSettings.granularity };
+        var 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;
+        }
+        $(this).text(Drupal.t(format, { '@interval': interval }));
+      });
+    }
+  };
+
+  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 = {
+      y: 365 * oneDay,
+      m: 30 * oneDay,
+      w: 7 * oneDay,
+      d: oneDay,
+      h: 60 * 60,
+      i: 60,
+      s: 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 'y':
+            output.push(Drupal.formatPlural(units, '1 year', '@count years'));
+            break;
+          case 'm':
+            output.push(Drupal.formatPlural(units, '1 months', '@count months'));
+            break;
+          case 'w':
+            output.push(Drupal.formatPlural(units, '1 week', '@count weeks'));
+            break;
+          case 'd':
+            output.push(Drupal.formatPlural(units, '1 day', '@count days'));
+            break;
+          case 'h':
+            output.push(Drupal.formatPlural(units, '1 hour', '@count hours'));
+            break;
+          case 'i':
+            output.push(Drupal.formatPlural(units, '1 minute', '@count minutes'));
+            break;
+          case 's':
+            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 36039dff0d..6d1e5cc0d9 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.
@@ -81,3 +82,27 @@ function system_post_update_classy_message_library() {
 function system_post_update_field_type_plugins() {
   // Empty post-update hook.
 }
+
+/**
+ * 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/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);
+  }
+
+}
