diff --git a/core/lib/Drupal/Core/Datetime/DateFormatter.php b/core/lib/Drupal/Core/Datetime/DateFormatter.php
index f6227ee8..dc129e0 100644
--- a/core/lib/Drupal/Core/Datetime/DateFormatter.php
+++ b/core/lib/Drupal/Core/Datetime/DateFormatter.php
@@ -206,6 +206,7 @@ public function formatDiff($from, $to, $options = array()) {
       'granularity' => 2,
       'langcode' => NULL,
       'strict' => TRUE,
+      'return_as_object' => FALSE,
     );
 
     if ($options['strict'] && $from > $to) {
@@ -226,6 +227,7 @@ public function formatDiff($from, $to, $options = array()) {
     // We loop over the keys provided by \DateInterval explicitly. Since we
     // don't take the "invert" property into account, the resulting output value
     // will always be positive.
+    $max_age = 1e99;
     foreach (array('y', 'm', 'd', 'h', 'i', 's') as $value) {
       if ($interval->$value > 0) {
         // Switch over the keys to call formatPlural() explicitly with literal
@@ -233,10 +235,12 @@ public function formatDiff($from, $to, $options = array()) {
         switch ($value) {
           case 'y':
             $interval_output = $this->formatPlural($interval->y, '1 year', '@count years', array(), array('langcode' => $options['langcode']));
+            $max_age = min($max_age, 365*86400);
             break;
 
           case 'm':
             $interval_output = $this->formatPlural($interval->m, '1 month', '@count months', array(), array('langcode' => $options['langcode']));
+            $max_age = min($max_age, 30*86400);
             break;
 
           case 'd':
@@ -249,10 +253,12 @@ public function formatDiff($from, $to, $options = array()) {
               $interval_output .= $this->formatPlural($weeks, '1 week', '@count weeks', array(), array('langcode' => $options['langcode']));
               $days -= $weeks * 7;
               $granularity--;
+              $max_age = min($max_age, 7*86400);
             }
 
             if ((!$output || $weeks > 0) && $granularity > 0 && $days > 0) {
               $interval_output .= ($interval_output ? ' ' : '') . $this->formatPlural($days, '1 day', '@count days', array(), array('langcode' => $options['langcode']));
+              $max_age = min($max_age, 86400);
             }
             else {
               // If we did not output days, set the granularity to 0 so that we
@@ -263,14 +269,17 @@ public function formatDiff($from, $to, $options = array()) {
 
           case 'h':
             $interval_output = $this->formatPlural($interval->h, '1 hour', '@count hours', array(), array('langcode' => $options['langcode']));
+            $max_age = min($max_age, 3600);
             break;
 
           case 'i':
             $interval_output = $this->formatPlural($interval->i, '1 minute', '@count minutes', array(), array('langcode' => $options['langcode']));
+            $max_age = min($max_age, 60);
             break;
 
           case 's':
             $interval_output = $this->formatPlural($interval->s, '1 second', '@count seconds', array(), array('langcode' => $options['langcode']));
+            $max_age = min($max_age, 1);
             break;
 
         }
@@ -292,6 +301,10 @@ public function formatDiff($from, $to, $options = array()) {
       $output = $this->t('0 seconds');
     }
 
+    if ($options['return_as_object']) {
+      return new FormattedDateDiff($output, $max_age);
+    }
+
     return $output;
   }
 
diff --git a/core/lib/Drupal/Core/Datetime/DateFormatterInterface.php b/core/lib/Drupal/Core/Datetime/DateFormatterInterface.php
index 1863216..e936855 100644
--- a/core/lib/Drupal/Core/Datetime/DateFormatterInterface.php
+++ b/core/lib/Drupal/Core/Datetime/DateFormatterInterface.php
@@ -107,8 +107,10 @@ public function getSampleDateFormats($langcode = NULL, $timestamp = NULL, $timez
    *     before the current request time, the result string will be "0 seconds".
    *     If FALSE and $timestamp is before the current request time, the result
    *     string will be the formatted time difference.
+   *   - return_as_object: A Boolean value whether to return a FormattedDateDiff
+   *     object.
    *
-   * @return string
+   * @return string|\Drupal\Core\Datetime\FormattedDateDiff
    *   A translated string representation of the difference between the given
    *   timestamp and the current request time. This interval is always positive.
    *
@@ -135,8 +137,10 @@ public function formatTimeDiffUntil($timestamp, $options = array());
    *     after the current request time, the result string will be "0 seconds".
    *     If FALSE and $timestamp is after the current request time, the result
    *     string will be the formatted time difference.
+   *   - return_as_object: A Boolean value whether to return a FormattedDateDiff
+   *     object.
    *
-   * @return string
+   * @return string|\Drupal\Core\Datetime\FormattedDateDiff
    *   A translated string representation of the difference between the given
    *   timestamp and the current request time. This interval is always positive.
    *
@@ -164,8 +168,10 @@ public function formatTimeDiffSince($timestamp, $options = array());
    *     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_as_object: A Boolean value whether to return a FormattedDateDiff
+   *     object.
    *
-   * @return string
+   * @return string|\Drupal\Core\Datetime\FormattedDateDiff
    *   A translated string representation of the interval. This interval is
    *   always positive.
    *
diff --git a/core/lib/Drupal/Core/Datetime/FormattedDateDiff.php b/core/lib/Drupal/Core/Datetime/FormattedDateDiff.php
new file mode 100644
index 0000000..fa67481
--- /dev/null
+++ b/core/lib/Drupal/Core/Datetime/FormattedDateDiff.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Datetime\FormattedDateDiff.
+ */
+
+namespace Drupal\Core\Datetime;
+
+use Drupal\Core\Cache\CacheableDependencyInterface;
+use Drupal\Core\Cache\UnchangingCacheableDependencyTrait;
+use Drupal\Core\Render\RenderableInterface;
+
+/**
+ * Contains a formatted time difference.
+ */
+class FormattedDateDiff implements RenderableInterface, CacheableDependencyInterface {
+
+  use UnchangingCacheableDependencyTrait;
+
+  /**
+   * The actual formatted time difference.
+   *
+   * @var string
+   */
+  protected $string;
+
+  /**
+   * A date range until the string cannot longer be cached.
+   *
+   * For example when the time difference is 1 day 1 hour, it can be cached
+   * until now + 1 hour, so its 3600 seconds.
+   *
+   * @var int
+   */
+  protected $maxAge;
+
+  /**
+   * Creates a new FormattedDateDiff instance.
+   *
+   * @param string $string
+   *   The formatted time difference.
+   * @param int $max_age
+   *   A date range until the string cannot be longer cached.
+   */
+  public function __construct($string, $max_age) {
+    $this->string = $string;
+    $this->maxAge = $max_age;
+  }
+
+  /**
+   * @return string
+   */
+  public function getString() {
+    return $this->string;
+  }
+
+  /**
+   * @return int
+   */
+  public function getMaxAge() {
+    return $this->maxAge;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function toRenderable() {
+    return [
+      '#markup' => $this->string,
+      '#cache' => [
+        'max-age' => $this->maxAge,
+      ],
+    ];
+  }
+
+}
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 a0f61b5..77c9f2a 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampAgoFormatter.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampAgoFormatter.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
 
 use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Datetime\DateFormatterInterface;
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldItemListInterface;
@@ -161,10 +162,12 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
         $updated = $this->formatTimestamp($item->value);
       }
       else {
-        $updated = $this->t('never');
+        $updated = [
+          '#markup' => $this->t('never'),
+        ];
       }
 
-      $elements[$delta] = array('#markup' => $updated);
+      $elements[$delta] = $updated;
     }
 
     return $elements;
@@ -176,19 +179,30 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
    * @param int $timestamp
    *   A UNIX timestamp to format.
    *
-   * @return string
+   * @return array
    *   The formatted timestamp string using the past or future format setting.
    */
   protected function formatTimestamp($timestamp) {
     $granularity = $this->getSetting('granularity');
-    $options = ['granularity' => $granularity];
+    $options = [
+      'granularity' => $granularity,
+      'render_as_object' => TRUE,
+    ];
 
     if ($this->request->server->get('REQUEST_TIME') > $timestamp) {
-      return SafeMarkup::format($this->getSetting('past_format'), ['@interval' => $this->dateFormatter->formatTimeDiffSince($timestamp, $options)]);
+      $result = $this->dateFormatter->formatTimeDiffSince($timestamp, $options);
+      $build = [
+          '#markup' => SafeMarkup::format($this->getSetting('past_format'), ['@interval' => $result->getString()]),
+      ];
     }
     else {
-      return SafeMarkup::format($this->getSetting('future_format'), ['@interval' => $this->dateFormatter->formatTimeDiffUntil($timestamp, $options)]);
+      $result = $this->dateFormatter->formatTimeDiffUntil($timestamp, $options);
+      $build = [
+        '#markup' => SafeMarkup::format($this->getSetting('future_format'), ['@interval' => $result->getString()]),
+      ];
     }
+    CacheableMetadata::createFromObject($result)->applyTo($build);
+    return $build;
   }
 
 }
diff --git a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeTimeAgoFormatter.php b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeTimeAgoFormatter.php
index b902c31..399afbd 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeTimeAgoFormatter.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeTimeAgoFormatter.php
@@ -8,6 +8,7 @@
 namespace Drupal\datetime\Plugin\Field\FieldFormatter;
 
 use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Datetime\DateFormatterInterface;
 use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\Core\Field\FieldDefinitionInterface;
@@ -112,7 +113,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
 
     foreach ($items as $delta => $item) {
       $date = $item->date;
-      $output = '';
+      $output = [];
       if (!empty($item->date)) {
         if ($this->getFieldSetting('datetime_type') == 'date') {
           // A date without time will pick up the current time, use the default.
@@ -120,7 +121,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
         }
         $output = $this->formatDate($date);
       }
-      $elements[$delta] = array('#markup' => $output);
+      $elements[$delta] = $output;
     }
 
     return $elements;
@@ -176,20 +177,32 @@ public function settingsSummary() {
    * @param \Drupal\Core\Datetime\DrupalDateTime|object $date
    *   A date/time object.
    *
-   * @return string
+   * @return array
    *   The formatted date/time string using the past or future format setting.
    */
   protected function formatDate(DrupalDateTime $date) {
     $granularity = $this->getSetting('granularity');
     $timestamp = $date->getTimestamp();
-    $options = ['granularity' => $granularity];
+    $options = [
+      'granularity' => $granularity,
+      'return_as_object' => TRUE,
+    ];
 
     if ($this->request->server->get('REQUEST_TIME') > $timestamp) {
-      return SafeMarkup::format($this->getSetting('past_format'), ['@interval' => $this->dateFormatter->formatTimeDiffSince($timestamp, $options)]);
+      $result = $this->dateFormatter->formatTimeDiffSince($timestamp, $options);
+      $build = [
+        '#markup' => SafeMarkup::format($this->getSetting('past_format'), ['@interval' => $result->getString()]),
+      ];
     }
     else {
-      return SafeMarkup::format($this->getSetting('future_format'), ['@interval' => $this->dateFormatter->formatTimeDiffUntil($timestamp, $options)]);
+      $result = $this->dateFormatter->formatTimeDiffUntil($timestamp, $options);
+      $build = [
+        '#markup' => SafeMarkup::format($this->getSetting('future_format'), ['@interval' => $result->getString()]),
+      ];
     }
+    CacheableMetadata::createFromObject($result)
+      ->applyTo($build);
+    return $build;
   }
 
 }
diff --git a/core/modules/user/src/UserListBuilder.php b/core/modules/user/src/UserListBuilder.php
index a61ce89..8f9b56c 100644
--- a/core/modules/user/src/UserListBuilder.php
+++ b/core/modules/user/src/UserListBuilder.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\user;
 
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Datetime\DateFormatterInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityListBuilder;
@@ -151,8 +152,20 @@ public function buildRow(EntityInterface $entity) {
       '#theme' => 'item_list',
       '#items' => $users_roles,
     );
-    $row['member_for'] = $this->dateFormatter->formatTimeDiffSince($entity->getCreatedTime());
-    $row['access'] = $entity->access ? $this->t('@time ago', array('@time' => $this->dateFormatter->formatTimeDiffSince($entity->getLastAccessedTime()))) : t('never');
+    $options = [
+      'return_as_object' => TRUE,
+    ];
+    $row['member_for']['data'] = $this->dateFormatter->formatTimeDiffSince($entity->getCreatedTime(), $options)->toRenderable();
+    $last_access = $this->dateFormatter->formatTimeDiffSince($entity->getLastAccessedTime(), $options);
+
+    if ($entity->access) {
+      $row['access']['data']['#markup'] = $last_access->getString();
+      CacheableMetadata::createFromObject($last_access)
+        ->applyTo($row['access']['data']);
+    }
+    else {
+      $row['access']['data']['#markup'] = t('never');
+    }
     return $row + parent::buildRow($entity);
   }
 
diff --git a/core/modules/views/src/Plugin/views/field/TimeInterval.php b/core/modules/views/src/Plugin/views/field/TimeInterval.php
index 4c91421..c8627e4 100644
--- a/core/modules/views/src/Plugin/views/field/TimeInterval.php
+++ b/core/modules/views/src/Plugin/views/field/TimeInterval.php
@@ -87,6 +87,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    */
   public function render(ResultRow $values) {
     $value = $values->{$this->field_alias};
+    // @fixme
     return $this->dateFormatter->formatInterval($value, isset($this->options['granularity']) ? $this->options['granularity'] : 2);
   }
 
