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..676f21cf2d 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,97 @@ 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 = [
+          'format' => [
+            'future' => $timeago['future_format'],
+            'past' => $timeago['past_format'],
+          ],
+          'granularity' => $timeago['granularity'],
+          'refresh' => $timeago['refresh'],
+        ];
+        $elements[$delta]['#attributes']['data-drupal-timeago'] = 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'),
+      600 => $this->t('10 minutes'),
+    ];
+  }
+
 }
diff --git a/core/misc/timeago.es6.js b/core/misc/timeago.es6.js
new file mode 100644
index 0000000000..651d7ae02e
--- /dev/null
+++ b/core/misc/timeago.es6.js
@@ -0,0 +1,198 @@
+/**
+ * @file
+ * Dynamic timeago formatting.
+ */
+
+(($, 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: () => {
+      $('time.js-timeago')
+        .once('timeago')
+        .each((delta, item) => {
+          const getTimeoutDuration = (value, refresh, granularity) => {
+            this.allIntervals = Object.keys(Drupal.dateFormatter.intervals);
+            const intervals = Object.keys(value);
+            const intervalsCount = intervals.length;
+            const lastInterval = intervals.pop();
+            // If the lowest interval of "time ago" is 'minute' or greater but
+            // the refresh interval is lower, do not refresh often than the
+            // duration if the lowest unit of "time ago".
+            if (lastInterval !== 'second') {
+              // If the "time ago" value parts count equals the granularity and
+              // lowest interval duration is bigger than the configured refresh,
+              // use the interval duration. For example, if the refresh duration
+              // is '10 seconds' and the "time ago" is '1 hour 32 minutes', do
+              // not refresh every 10 seconds but every minute.
+              if (intervalsCount === granularity) {
+                $.each(Drupal.dateFormatter.intervals, (interval, duration) => {
+                  if (interval === lastInterval) {
+                    refresh = refresh < duration ? duration : refresh;
+                    return false;
+                  }
+                });
+              }
+              // The "time ago" value parts count might be smaller than the
+              // granularity when lowest part is missed because is 0. In this
+              // case the missed part interval duration is used as refresh.
+              else {
+                const index = this.allIntervals.indexOf(lastInterval);
+                const nextInterval = this.allIntervals[index + 1];
+                refresh = Drupal.dateFormatter.intervals[nextInterval];
+              }
+            }
+            return refresh * 1000;
+          };
+
+          Drupal.showTimeAgo = (element) => {
+            const timestamp = (new Date($(element).attr('datetime'))).getTime();
+            const timeagoSettings = JSON.parse($(element).attr('data-drupal-timeago'));
+            const now = Date.now();
+            const options = { granularity: timeagoSettings.granularity };
+            let timeago;
+            let format;
+
+            if (timestamp > now) {
+              timeago = Drupal.dateFormatter.formatDiff(now, timestamp, options);
+              format = timeagoSettings.format.future;
+            }
+            else {
+              timeago = Drupal.dateFormatter.formatDiff(timestamp, now, options);
+              format = timeagoSettings.format.past;
+            }
+            $(element).text(Drupal.t(format, { '@interval': timeago.formatted }));
+
+            if (timeagoSettings.refresh > 0) {
+              const timeout = getTimeoutDuration(
+                timeago.value,
+                timeagoSettings.refresh,
+                timeagoSettings.granularity,
+              );
+              element.timer = setTimeout(Drupal.showTimeAgo, timeout, element);
+            }
+          };
+          Drupal.showTimeAgo(item);
+        });
+    },
+    detach: (context, settings, trigger) => {
+      if (trigger === 'unload') {
+        const elements = $(context).find('time.js-timeago');
+        elements.removeOnce('timeago');
+        elements.each(() => {
+          clearInterval(this.timer);
+        });
+      }
+    },
+  };
+
+  /**
+   * @typedef {object} timeAgo
+   * @prop {string} formatted
+   *   A translated string representation of the interval.
+   * @prop {object} value
+   *   The elements composing the "time ago" interval. Example: { day: 2,
+   *   hour: 2, minute: 32, second: 15 }.
+   */
+
+  /**
+   * 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]
+   *   An optional object with additional options.
+   * @param {number} [options.granularity=2]
+   *   An integer value that signals how many different units to display in the
+   *   string. Defaults to 2.
+   * @param {boolean} [options.granularity=true]
+   *   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 {timeAgo}
+   *   A "time ago" type object.
+   */
+  Drupal.dateFormatter.formatDiff = (from, to, options) => {
+    // Provide sane defaults.
+    options = options || {};
+    $.extend({ granularity: 2, strict: true }, options);
+
+    if (options.strict && from > to) {
+      return { formatted: Drupal.t('0 seconds'), value: { second: 0 } };
+    }
+
+    const output = [];
+    const value = {};
+    let units;
+    let granularity = options.granularity;
+
+    // Compute the difference in seconds.
+    let diff = Math.round((to - from) / 1000);
+
+    $.each(Drupal.dateFormatter.intervals, (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;
+          default:
+            output.push(Drupal.formatPlural(units, '1 second', '@count seconds'));
+        }
+        value[interval] = units;
+
+        granularity -= 1;
+        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 { formatted: Drupal.t('0 seconds'), value: { second: 0 } };
+    }
+    return { formatted: output.join(' '), value };
+  };
+
+  // Time intervals.
+  Drupal.dateFormatter.intervals = {
+    year: 365 * 24 * 60 * 60,
+    month: 30 * 24 * 60 * 60,
+    week: 7 * 24 * 60 * 60,
+    day: 24 * 60 * 60,
+    hour: 60 * 60,
+    minute: 60,
+    second: 1,
+  };
+})(jQuery, Drupal);
diff --git a/core/misc/timeago.js b/core/misc/timeago.js
new file mode 100644
index 0000000000..9f73d2907a
--- /dev/null
+++ b/core/misc/timeago.js
@@ -0,0 +1,141 @@
+/**
+* DO NOT EDIT THIS FILE.
+* See the following change record for more information,
+* https://www.drupal.org/node/2815083
+* @preserve
+**/
+var _this = this;
+
+(function ($, Drupal) {
+  Drupal.dateFormatter = Drupal.dateFormatter || {};
+
+  Drupal.behaviors.timestampAsTimeAgo = {
+    attach: function attach() {
+      $('time.js-timeago').once('timeago').each(function (delta, item) {
+        var getTimeoutDuration = function getTimeoutDuration(value, refresh, granularity) {
+          _this.allIntervals = Object.keys(Drupal.dateFormatter.intervals);
+          var intervals = Object.keys(value);
+          var intervalsCount = intervals.length;
+          var lastInterval = intervals.pop();
+
+          if (lastInterval !== 'second') {
+            if (intervalsCount === granularity) {
+              $.each(Drupal.dateFormatter.intervals, function (interval, duration) {
+                if (interval === lastInterval) {
+                  refresh = refresh < duration ? duration : refresh;
+                  return false;
+                }
+              });
+            } else {
+                var index = _this.allIntervals.indexOf(lastInterval);
+                var nextInterval = _this.allIntervals[index + 1];
+                refresh = Drupal.dateFormatter.intervals[nextInterval];
+              }
+          }
+          return refresh * 1000;
+        };
+
+        Drupal.showTimeAgo = function (element) {
+          var timestamp = new Date($(element).attr('datetime')).getTime();
+          var timeagoSettings = JSON.parse($(element).attr('data-drupal-timeago'));
+          var now = Date.now();
+          var options = { granularity: timeagoSettings.granularity };
+          var timeago = void 0;
+          var format = void 0;
+
+          if (timestamp > now) {
+            timeago = Drupal.dateFormatter.formatDiff(now, timestamp, options);
+            format = timeagoSettings.format.future;
+          } else {
+            timeago = Drupal.dateFormatter.formatDiff(timestamp, now, options);
+            format = timeagoSettings.format.past;
+          }
+          $(element).text(Drupal.t(format, { '@interval': timeago.formatted }));
+
+          if (timeagoSettings.refresh > 0) {
+            var timeout = getTimeoutDuration(timeago.value, timeagoSettings.refresh, timeagoSettings.granularity);
+            element.timer = setTimeout(Drupal.showTimeAgo, timeout, element);
+          }
+        };
+        Drupal.showTimeAgo(item);
+      });
+    },
+    detach: function detach(context, settings, trigger) {
+      if (trigger === 'unload') {
+        var elements = $(context).find('time.js-timeago');
+        elements.removeOnce('timeago');
+        elements.each(function () {
+          clearInterval(_this.timer);
+        });
+      }
+    }
+  };
+
+  Drupal.dateFormatter.formatDiff = function (from, to, options) {
+    options = options || {};
+    $.extend({ granularity: 2, strict: true }, options);
+
+    if (options.strict && from > to) {
+      return { formatted: Drupal.t('0 seconds'), value: { second: 0 } };
+    }
+
+    var output = [];
+    var value = {};
+    var units = void 0;
+    var granularity = options.granularity;
+
+    var diff = Math.round((to - from) / 1000);
+
+    $.each(Drupal.dateFormatter.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;
+          default:
+            output.push(Drupal.formatPlural(units, '1 second', '@count seconds'));
+        }
+        value[interval] = units;
+
+        granularity -= 1;
+        if (granularity <= 0) {
+          return false;
+        }
+      } else if (output.length > 0) {
+        return false;
+      }
+    });
+
+    if (output.length === 0) {
+      return { formatted: Drupal.t('0 seconds'), value: { second: 0 } };
+    }
+    return { formatted: output.join(' '), value: value };
+  };
+
+  Drupal.dateFormatter.intervals = {
+    year: 365 * 24 * 60 * 60,
+    month: 30 * 24 * 60 * 60,
+    week: 7 * 24 * 60 * 60,
+    day: 24 * 60 * 60,
+    hour: 60 * 60,
+    minute: 60,
+    second: 1
+  };
+})(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/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..3be433861e 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'), trim(strip_tags($executable->getStyle()->getField(0, 'created'))));
+    $this->assertEqual($date_formatter->format($this->testUsers[0]->getCreatedTime(), 'custom', 'H:i:s'), trim(strip_tags($executable->getStyle()->getField(0, 'created_1'))));
+    $this->assertEqual($date_formatter->format($this->testUsers[0]->getCreatedTime(), 'fallback'), trim(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'), trim(strip_tags($executable->getStyle()->getField(1, 'created'))));
+    $this->assertEqual($date_formatter->format($this->testUsers[1]->getCreatedTime(), 'custom', 'H:i:s'), trim(strip_tags($executable->getStyle()->getField(1, 'created_1'))));
+    $this->assertEqual($date_formatter->format($this->testUsers[1]->getCreatedTime(), 'fallback'), trim(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'), trim(strip_tags($executable->getStyle()->getField(2, 'created'))));
+    $this->assertEqual($date_formatter->format($this->testUsers[2]->getCreatedTime(), 'custom', 'H:i:s'), trim(strip_tags($executable->getStyle()->getField(2, 'created_1'))));
+    $this->assertEqual($date_formatter->format($this->testUsers[2]->getCreatedTime(), 'fallback'), trim(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'), trim(strip_tags($executable->getStyle()->getField(3, 'created'))));
+    $this->assertEqual($date_formatter->format($this->testUsers[3]->getCreatedTime(), 'custom', 'H:i:s'), trim(strip_tags($executable->getStyle()->getField(3, 'created_1'))));
+    $this->assertEqual($date_formatter->format($this->testUsers[3]->getCreatedTime(), 'fallback'), trim(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'), trim(strip_tags($executable->getStyle()->getField(4, 'created'))));
+    $this->assertEqual($date_formatter->format($this->testUsers[4]->getCreatedTime(), 'custom', 'H:i:s'), trim(strip_tags($executable->getStyle()->getField(4, 'created_1'))));
+    $this->assertEqual($date_formatter->format($this->testUsers[4]->getCreatedTime(), 'fallback'), trim(strip_tags($executable->getStyle()->getField(4, 'created_2'))));
   }
 
   /**
