diff --git a/core/core.services.yml b/core/core.services.yml
index 8d3f9e728f847d9ed9a163be3470b50c1893a06b..589c5f6c135f437ba5f55597a2b16a61b3ce7910 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1143,7 +1143,7 @@ services:
     arguments: ['@module_handler']
   date.formatter:
     class: Drupal\Core\Datetime\DateFormatter
-    arguments: ['@entity.manager', '@language_manager', '@string_translation', '@config.factory']
+    arguments: ['@entity.manager', '@language_manager', '@string_translation', '@config.factory', '@request_stack']
   feed.bridge.reader:
     class: Drupal\Component\Bridge\ZfExtensionManagerSfContainer
     calls:
diff --git a/core/lib/Drupal/Core/Datetime/DateFormatter.php b/core/lib/Drupal/Core/Datetime/DateFormatter.php
index 82e996988dd990e354dc61f2c725c74d2a08474f..0aad043debfcfb75e30bdb7a794e66a1774b6405 100644
--- a/core/lib/Drupal/Core/Datetime/DateFormatter.php
+++ b/core/lib/Drupal/Core/Datetime/DateFormatter.php
@@ -15,6 +15,7 @@
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\StringTranslation\TranslationInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Symfony\Component\HttpFoundation\RequestStack;
 
 /**
  * Provides a service to handler various date related functionality.
@@ -52,6 +53,13 @@ class DateFormatter {
    */
   protected $configFactory;
 
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
   protected $country = NULL;
   protected $dateFormats = array();
 
@@ -85,12 +93,15 @@ class DateFormatter {
    *   The string translation.
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The configuration factory.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack.
    */
-  public function __construct(EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager, TranslationInterface $translation, ConfigFactoryInterface $config_factory) {
+  public function __construct(EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager, TranslationInterface $translation, ConfigFactoryInterface $config_factory, RequestStack $request_stack) {
     $this->dateFormatStorage = $entity_manager->getStorage('date_format');
     $this->languageManager = $language_manager;
     $this->stringTranslation = $translation;
     $this->configFactory = $config_factory;
+    $this->requestStack = $request_stack;
   }
 
   /**
@@ -111,13 +122,13 @@ public function __construct(EntityManagerInterface $entity_manager, LanguageMana
    *   (optional) If $type is 'custom', a PHP date format string suitable for
    *   input to date(). Use a backslash to escape ordinary text, so it does not
    *   get interpreted as date format characters.
-   * @param string $timezone
+   * @param string|null $timezone
    *   (optional) Time zone identifier, as described at
    *   http://php.net/manual/timezones.php Defaults to the time zone used to
    *   display the page.
-   * @param string $langcode
-   *   (optional) Language code to translate to. Defaults to the language used to
-   *   display the page.
+   * @param string|null $langcode
+   *   (optional) Language code to translate to. NULL (default) means to use
+   *   the user interface language for the page.
    *
    * @return string
    *   A translated date string in the requested format.
@@ -163,17 +174,26 @@ public function format($timestamp, $type = 'medium', $format = '', $timezone = N
   /**
    * Formats a time interval with the requested granularity.
    *
+   * Note that for intervals over 30 days, the output is approximate: a "month"
+   * is always exactly 30 days, and a "year" is always 365 days. It is not
+   * possible to make a more exact representation, given that there is only one
+   * input in seconds. If you are formatting an interval between two specific
+   * timestamps, use \Drupal\Core\Datetime\DateFormatter::formatDiff() instead.
+   *
    * @param int $interval
    *   The length of the interval in seconds.
    * @param int $granularity
    *   (optional) How many different units to display in the string (2 by
    *   default).
-   * @param string $langcode
-   *   (optional) Language code to translate to a language other than what is
-   *   used to display the page. Defaults to NULL.
+   * @param string|null $langcode
+   *   (optional) langcode: The language code for the language used to format
+   *   the date. Defaults to NULL, which results in the user interface language
+   *   for the page being used.
    *
    * @return string
    *   A translated string representation of the interval.
+   *
+   * @see \Drupal\Core\Datetime\DateFormatter::formatDiff()
    */
   public function formatInterval($interval, $granularity = 2, $langcode = NULL) {
     $output = '';
@@ -214,11 +234,185 @@ public function getSampleDateFormats($langcode = NULL, $timestamp = NULL, $timez
     // All date format characters for the PHP date() function.
     $date_chars = str_split('dDjlNSwzWFmMntLoYyaABgGhHisueIOPTZcrU');
     $date_elements = array_combine($date_chars, $date_chars);
-    return array_map(function($character) use ($timestamp, $timezone, $langcode) {
+    return array_map(function ($character) use ($timestamp, $timezone, $langcode) {
       return $this->format($timestamp, 'custom', $character, $timezone, $langcode);
     }, $date_elements);
   }
 
+  /**
+   * Formats the time difference from the current request time to a timestamp.
+   *
+   * @param $timestamp
+   *   A UNIX timestamp to compare against the current request time.
+   * @param array $options
+   *   (optional) An associative array 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.
+   *   - langcode: The language code for the language used to format the date.
+   *     Defaults to NULL, which results in the user interface language for the
+   *     page being used.
+   *   - strict: A Boolean value indicating whether or not the timestamp can be
+   *     before the current request time. If TRUE (default) and $timestamp is
+   *     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 string
+   *   A translated string representation of the difference between the given
+   *   timestamp and the current request time. This interval is always positive.
+   *
+   * @see \Drupal\Core\Datetime\DateFormatter::formatDiff()
+   * @see \Drupal\Core\Datetime\DateFormatter::formatTimeDiffSince()
+   */
+  public function formatTimeDiffUntil($timestamp, $options = array()) {
+    $request_time = $this->requestStack->getCurrentRequest()->server->get('REQUEST_TIME');
+    return $this->formatDiff($request_time, $timestamp, $options);
+  }
+
+  /**
+   * Formats the time difference from a timestamp to the current request time.
+   *
+   * @param $timestamp
+   *   A UNIX timestamp to compare against the current request time.
+   * @param array $options
+   *   (optional) An associative array 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.
+   *   - langcode: The language code for the language used to format the date.
+   *     Defaults to NULL, which results in the user interface language for the
+   *     page being used.
+   *   - strict: A Boolean value indicating whether or not the timestamp can be
+   *     after the current request time. If TRUE (default) and $timestamp is
+   *     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 string
+   *   A translated string representation of the difference between the given
+   *   timestamp and the current request time. This interval is always positive.
+   *
+   * @see \Drupal\Core\Datetime\DateFormatter::formatDiff()
+   * @see \Drupal\Core\Datetime\DateFormatter::formatTimeDiffUntil()
+   */
+  public function formatTimeDiffSince($timestamp, $options = array()) {
+    $request_time = $this->requestStack->getCurrentRequest()->server->get('REQUEST_TIME');
+    return $this->formatDiff($timestamp, $request_time, $options);
+  }
+
+  /**
+   * Formats a time interval between two timestamps.
+   *
+   * @param int $from
+   *   A UNIX timestamp, defining the from date and time.
+   * @param int $to
+   *   A UNIX timestamp, defining the to date and time.
+   * @param array $options
+   *   (optional) An associative array 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.
+   *   - langcode: The language code for the language used to format the date.
+   *     Defaults to NULL, which results in the user interface language for the
+   *     page being used.
+   *   - 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. This interval is
+   *   always positive.
+   *
+   * @see \Drupal\Core\Datetime\DateFormatter::formatInterval()
+   * @see \Drupal\Core\Datetime\DateFormatter::formatTimeDiffSince()
+   * @see \Drupal\Core\Datetime\DateFormatter::formatTimeDiffUntil()
+   */
+  public function formatDiff($from, $to, $options = array()) {
+
+    $options += array(
+      'granularity' => 2,
+      'langcode' => NULL,
+      'strict' => TRUE,
+    );
+
+    if ($options['strict'] && $from > $to) {
+      return $this->t('0 seconds');
+    }
+
+    $date_time_from = new \DateTime();
+    $date_time_from->setTimestamp($from);
+
+    $date_time_to = new \DateTime();
+    $date_time_to->setTimestamp($to);
+
+    $interval = $date_time_to->diff($date_time_from);
+
+    $granularity = $options['granularity'];
+    $output = '';
+
+    // 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.
+    foreach (array('y', 'm', 'd', 'h', 'i', 's') as $value) {
+      if ($interval->$value > 0) {
+        // Switch over the keys to call formatPlural() explicitly with literal
+        // strings for all different possibilities.
+        switch ($value) {
+          case 'y':
+            $interval_output = $this->formatPlural($interval->y, '1 year', '@count years', array(), array('langcode' => $options['langcode']));
+            break;
+
+          case 'm':
+            $interval_output = $this->formatPlural($interval->m, '1 month', '@count months', array(), array('langcode' => $options['langcode']));
+            break;
+
+          case 'd':
+            // \DateInterval doesn't support weeks, so we need to calculate them
+            // ourselves.
+            $interval_output = '';
+            $days = $interval->d;
+            if ($days >= 7) {
+              $weeks = floor($days / 7);
+              $interval_output .= $this->formatPlural($weeks, '1 week', '@count weeks', array(), array('langcode' => $options['langcode']));
+              $days -= $weeks * 7;
+              $granularity--;
+            }
+            if ($granularity > 0 && $days > 0) {
+              $interval_output .= ($interval_output ? ' ' : '') . $this->formatPlural($days, '1 day', '@count days', array(), array('langcode' => $options['langcode']));
+            }
+            break;
+
+          case 'h':
+            $interval_output = $this->formatPlural($interval->h, '1 hour', '@count hours', array(), array('langcode' => $options['langcode']));
+            break;
+
+          case 'i':
+            $interval_output = $this->formatPlural($interval->i, '1 minute', '@count minutes', array(), array('langcode' => $options['langcode']));
+            break;
+
+          case 's':
+            $interval_output = $this->formatPlural($interval->s, '1 second', '@count seconds', array(), array('langcode' => $options['langcode']));
+            break;
+
+        }
+        $output .= ($output ? ' ' : '') . $interval_output;
+        $granularity--;
+      }
+
+      if ($granularity == 0) {
+        break;
+      }
+    }
+
+    if (empty($output)) {
+      $output = $this->t('0 seconds');
+    }
+
+    return $output;
+  }
+
   /**
    * Loads the given format pattern for the given langcode.
    *
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 04f96fae0e52ef9a497133666afff9d4de4d0318..30dae802e24f4c4de13b39b10fd2758f100b81fa 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampAgoFormatter.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampAgoFormatter.php
@@ -87,7 +87,7 @@ public function viewElements(FieldItemListInterface $items) {
 
     foreach ($items as $delta => $item) {
       if ($item->value) {
-        $updated = $this->t('@time ago', array('@time' => $this->dateFormatter->formatInterval(REQUEST_TIME - $item->value)));
+        $updated = $this->t('@time ago', array('@time' => $this->dateFormatter->formatTimeDiffSince($item->value)));
       }
       else {
         $updated = $this->t('never');
diff --git a/core/modules/aggregator/src/Controller/AggregatorController.php b/core/modules/aggregator/src/Controller/AggregatorController.php
index 303b43c8a080366fe66b5b1986d9722a61229410..eaa82e5999ba7691d008717f6a7664fbb61b0ae9 100644
--- a/core/modules/aggregator/src/Controller/AggregatorController.php
+++ b/core/modules/aggregator/src/Controller/AggregatorController.php
@@ -127,8 +127,8 @@ public function adminOverview() {
       $row[] = $this->formatPlural($entity_manager->getStorage('aggregator_item')->getItemCount($feed), '1 item', '@count items');
       $last_checked = $feed->getLastCheckedTime();
       $refresh_rate = $feed->getRefreshRate();
-      $row[] = ($last_checked ? $this->t('@time ago', array('@time' => $this->dateFormatter->formatInterval(REQUEST_TIME - $last_checked))) : $this->t('never'));
-      $row[] = ($last_checked && $refresh_rate ? $this->t('%time left', array('%time' => $this->dateFormatter->formatInterval($last_checked + $refresh_rate - REQUEST_TIME))) : $this->t('never'));
+      $row[] = ($last_checked ? $this->t('@time ago', array('@time' => $this->dateFormatter->formatTimeDiffSince($last_checked))) : $this->t('never'));
+      $row[] = ($last_checked && $refresh_rate ? $this->t('@time left', array('@time' => $this->dateFormatter->formatTimeDiffUntil($last_checked + $refresh_rate))) : $this->t('never'));
       $links['edit'] = [
         'title' => $this->t('Edit'),
         'url' => Url::fromRoute('entity.aggregator_feed.edit_form', ['aggregator_feed' => $feed->id()]),
diff --git a/core/modules/comment/src/Tests/CommentTokenReplaceTest.php b/core/modules/comment/src/Tests/CommentTokenReplaceTest.php
index c45c0f73b91cd8c2532da3f1adbf3cb52c2403fe..c3808e692b4bff4cb9f3394b3e537955881238fd 100644
--- a/core/modules/comment/src/Tests/CommentTokenReplaceTest.php
+++ b/core/modules/comment/src/Tests/CommentTokenReplaceTest.php
@@ -60,8 +60,8 @@ function testCommentTokenReplacement() {
     $tests['[comment:langcode]'] = SafeMarkup::checkPlain($comment->language()->getId());
     $tests['[comment:url]'] = $comment->url('canonical', $url_options + array('fragment' => 'comment-' . $comment->id()));
     $tests['[comment:edit-url]'] = $comment->url('edit-form', $url_options);
-    $tests['[comment:created:since]'] = \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $comment->getCreatedTime(), 2, $language_interface->getId());
-    $tests['[comment:changed:since]'] = \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $comment->getChangedTimeAcrossTranslations(), 2, $language_interface->getId());
+    $tests['[comment:created:since]'] = \Drupal::service('date.formatter')->formatTimeDiffSince($comment->getCreatedTime(), array('langcode' => $language_interface->getId()));
+    $tests['[comment:changed:since]'] = \Drupal::service('date.formatter')->formatTimeDiffSince($comment->getChangedTimeAcrossTranslations(), array('langcode' => $language_interface->getId()));
     $tests['[comment:parent:cid]'] = $comment->hasParentComment() ? $comment->getParentComment()->id() : NULL;
     $tests['[comment:parent:title]'] = SafeMarkup::checkPlain($parent_comment->getSubject());
     $tests['[comment:entity]'] = SafeMarkup::checkPlain($node->getTitle());
diff --git a/core/modules/contact/src/Tests/ContactSitewideTest.php b/core/modules/contact/src/Tests/ContactSitewideTest.php
index ff8d1158aa2158941c0701fbeddb82e15b8f8da3..c815d35ae13b3a535e7cee15cd0b4cc00cc64dc6 100644
--- a/core/modules/contact/src/Tests/ContactSitewideTest.php
+++ b/core/modules/contact/src/Tests/ContactSitewideTest.php
@@ -231,7 +231,7 @@ function testSiteWideContact() {
     }
     // Submit contact form one over limit.
     $this->submitContact($this->randomMachineName(16), $recipients[0], $this->randomMachineName(16), $id, $this->randomMachineName(64));
-    $this->assertRaw(t('You cannot send more than %number messages in @interval. Try again later.', array('%number' => $this->config('contact.settings')->get('flood.limit'), '@interval' => \Drupal::service('date.formatter')->formatInterval(600))));
+    $this->assertRaw(t('You cannot send more than %number messages in 10 min. Try again later.', array('%number' => $this->config('contact.settings')->get('flood.limit'))));
 
     // Test listing controller.
     $this->drupalLogin($admin_user);
diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index 7ec742299351497890d9ec26300a02323d8dd9a4..eb25bf82abb24494899759f9b668cb101e987d4e 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -633,5 +633,5 @@ function template_preprocess_forum_submitted(&$variables) {
     $username = array('#theme' => 'username', '#account' => User::load($variables['topic']->uid));
     $variables['author'] = drupal_render($username);
   }
-  $variables['time'] = isset($variables['topic']->created) ? \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $variables['topic']->created) : '';
+  $variables['time'] = isset($variables['topic']->created) ? \Drupal::service('date.formatter')->formatTimeDiffSince($variables['topic']->created) : '';
 }
diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc
index 41ce0b846f89c7452caff9f95eae8c8582603221..d58d9557ca4bec9486ac30c03035cd9fd554dbde 100644
--- a/core/modules/locale/locale.pages.inc
+++ b/core/modules/locale/locale.pages.inc
@@ -119,6 +119,6 @@ function template_preprocess_locale_translation_update_info(array &$variables) {
 function template_preprocess_locale_translation_last_check(array &$variables) {
   $last = $variables['last'];
   $variables['last_checked'] = ($last != NULL);
-  $variables['time'] = \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $last);
+  $variables['time'] = \Drupal::service('date.formatter')->formatTimeDiffSince($last);
   $variables['link'] = \Drupal::l(t('Check manually'), new Url('locale.check_translation', array(), array('query' => \Drupal::destination()->getAsArray())));
 }
diff --git a/core/modules/node/src/Tests/NodeTokenReplaceTest.php b/core/modules/node/src/Tests/NodeTokenReplaceTest.php
index ffef457b3fb7544a4abd4155fc1dd19ed0c79b56..1ba30033926e5872990f46cdad819eebddc14dce 100644
--- a/core/modules/node/src/Tests/NodeTokenReplaceTest.php
+++ b/core/modules/node/src/Tests/NodeTokenReplaceTest.php
@@ -73,8 +73,8 @@ function testNodeTokenReplacement() {
     $tests['[node:author]'] = SafeMarkup::checkPlain($account->getUsername());
     $tests['[node:author:uid]'] = $node->getOwnerId();
     $tests['[node:author:name]'] = SafeMarkup::checkPlain($account->getUsername());
-    $tests['[node:created:since]'] = \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $node->getCreatedTime(), 2, $this->interfaceLanguage->getId());
-    $tests['[node:changed:since]'] = \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $node->getChangedTime(), 2, $this->interfaceLanguage->getId());
+    $tests['[node:created:since]'] = \Drupal::service('date.formatter')->formatTimeDiffSince($node->getCreatedTime(), array('langcode' => $this->interfaceLanguage->getId()));
+    $tests['[node:changed:since]'] = \Drupal::service('date.formatter')->formatTimeDiffSince($node->getChangedTime(), array('langcode' => $this->interfaceLanguage->getId()));
 
     // Test to make sure that we generated something for each token.
     $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
diff --git a/core/modules/system/module.api.php b/core/modules/system/module.api.php
index 24fb7411088c924183e0bcc46c7324482213b4c6..63f81b280dddc0b6b2185f81960e38febc13c4f9 100644
--- a/core/modules/system/module.api.php
+++ b/core/modules/system/module.api.php
@@ -761,7 +761,7 @@ function hook_requirements($phase) {
     $cron_last = \Drupal::state()->get('system.cron_last');
 
     if (is_numeric($cron_last)) {
-      $requirements['cron']['value'] = t('Last run !time ago', array('!time' => \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $cron_last)));
+      $requirements['cron']['value'] = t('Last run !time ago', array('!time' => \Drupal::service('date.formatter')->formatTimeDiffSince($cron_last)));
     }
     else {
       $requirements['cron'] = array(
diff --git a/core/modules/system/src/Form/CronForm.php b/core/modules/system/src/Form/CronForm.php
index 9df94141ae1b8fb6fc550e9cafab71bf91f351be..c57ceae5475e176b0ac549da0ce7311ad70ee342 100644
--- a/core/modules/system/src/Form/CronForm.php
+++ b/core/modules/system/src/Form/CronForm.php
@@ -101,8 +101,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       '#value' => t('Run cron'),
       '#submit' => array('::submitCron'),
     );
-
-    $status = '<p>' . t('Last run: %cron-last ago.', array('%cron-last' => $this->dateFormatter->formatInterval(REQUEST_TIME - $this->state->get('system.cron_last')))) . '</p>';
+    $status = '<p>' . $this->t('Last run: %time ago.', array('%time' => $this->dateFormatter->formatTimeDiffSince($this->state->get('system.cron_last')))) . '</p>';
     $form['status'] = array(
       '#markup' => $status,
     );
diff --git a/core/modules/system/src/Tests/System/TokenReplaceUnitTest.php b/core/modules/system/src/Tests/System/TokenReplaceUnitTest.php
index 6cf92efc131359351b57f9b33eea8f9f9a889eef..f98c3735faa4ce845594a8b6e04b8a1326f0018e 100644
--- a/core/modules/system/src/Tests/System/TokenReplaceUnitTest.php
+++ b/core/modules/system/src/Tests/System/TokenReplaceUnitTest.php
@@ -154,7 +154,7 @@ public function testSystemDateTokenReplacement() {
     $tests['[date:medium]'] = $date_formatter->format($date, 'medium', '', NULL, $this->interfaceLanguage->getId());
     $tests['[date:long]'] = $date_formatter->format($date, 'long', '', NULL, $this->interfaceLanguage->getId());
     $tests['[date:custom:m/j/Y]'] = $date_formatter->format($date, 'custom', 'm/j/Y', NULL, $this->interfaceLanguage->getId());
-    $tests['[date:since]'] = $date_formatter->formatInterval(REQUEST_TIME - $date, 2, $this->interfaceLanguage->getId());
+    $tests['[date:since]'] = $date_formatter->formatTimeDiffSince($date, array('langcode' => $this->interfaceLanguage->getId()));
     $tests['[date:raw]'] = Xss::filter($date);
 
     // Test to make sure that we generated something for each token.
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 488db27a8ae3f40685fb0f537ac7bb4f35f84691..a0849b11de23d9a91e581bbb54ced3894e6d1a1d 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -359,7 +359,7 @@ function system_requirements($phase) {
     }
 
     // Set summary and description based on values determined above.
-    $summary = t('Last run !time ago', array('!time' => \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $cron_last)));
+    $summary = t('Last run !time ago', array('!time' => \Drupal::service('date.formatter')->formatTimeDiffSince($cron_last)));
     $description = '';
     if ($severity != REQUIREMENT_INFO) {
       $description = t('Cron has not run recently.') . ' ' . $help;
diff --git a/core/modules/system/system.tokens.inc b/core/modules/system/system.tokens.inc
index 8ce5825fe6d248f820b37d3f0b6ea6248c9f98c0..20d5ec17955b515b21670435c137af30177e3668 100644
--- a/core/modules/system/system.tokens.inc
+++ b/core/modules/system/system.tokens.inc
@@ -68,7 +68,7 @@ function system_token_info() {
   );
   $date['since'] = array(
     'name' => t("Time-since"),
-    'description' => t("A date in 'time-since' format. (%date)", array('%date' => \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - 360, 2))),
+    'description' => t("A date in 'time-since' format. (%date)", array('%date' => \Drupal::service('date.formatter')->formatTimeDiffSince(REQUEST_TIME - 360))),
   );
   $date['raw'] = array(
     'name' => t("Raw timestamp"),
@@ -157,7 +157,7 @@ function system_tokens($type, $tokens, array $data = array(), array $options = a
           break;
 
         case 'since':
-          $replacements[$original] = \Drupal::service('date.formatter')->formatInterval((REQUEST_TIME - $date), 2, $langcode);
+          $replacements[$original] = \Drupal::service('date.formatter')->formatTimeDiffSince($date, array('langcode' => $langcode));
           break;
 
         case 'raw':
diff --git a/core/modules/tracker/src/Tests/TrackerTest.php b/core/modules/tracker/src/Tests/TrackerTest.php
index c5c8500b4de22479aa8e51c450a248e92f8c71a6..bc2f56487230771dcb8fe844b7e81023eac2eaaa 100644
--- a/core/modules/tracker/src/Tests/TrackerTest.php
+++ b/core/modules/tracker/src/Tests/TrackerTest.php
@@ -91,7 +91,7 @@ function testTrackerAll() {
 
     $this->drupalGet('activity');
     $this->assertText($node->label(), 'Published node shows up in the tracker listing.');
-    $this->assertText(\Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $node->getChangedTime()), 'The changed time was displayed on the tracker listing.');
+    $this->assertText(\Drupal::service('date.formatter')->formatTimeDiffSince($node->getChangedTime()), 'The changed time was displayed on the tracker listing.');
   }
 
   /**
diff --git a/core/modules/tracker/tracker.pages.inc b/core/modules/tracker/tracker.pages.inc
index 5eac748199e4c0243eef6e7fd37b06a134f18f14..4ce372d2a732e015732e3c037c821421dc87beca 100644
--- a/core/modules/tracker/tracker.pages.inc
+++ b/core/modules/tracker/tracker.pages.inc
@@ -114,7 +114,7 @@ function tracker_page($account = NULL) {
         ),
         'last updated' => array(
           'data' => t('!time ago', array(
-            '!time' => \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $node->last_activity),
+            '!time' => \Drupal::service('date.formatter')->formatTimeDiffSince($node->last_activity),
           )),
         ),
       );
diff --git a/core/modules/update/update.module b/core/modules/update/update.module
index 63ed8d0a74f024197fe10bc0251519dc7b729b62..4aed1efd6ad6353fa8fbc353c76b20cdcb55fd58 100644
--- a/core/modules/update/update.module
+++ b/core/modules/update/update.module
@@ -574,7 +574,7 @@ function _update_project_status_sort($a, $b) {
  * @see theme_update_report()
  */
 function template_preprocess_update_last_check(&$variables) {
-  $variables['time'] = \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $variables['last']);
+  $variables['time'] = \Drupal::service('date.formatter')->formatTimeDiffSince($variables['last']);
   $variables['link'] = \Drupal::l(t('Check manually'), new Url('update.manual_status', array(), array('query' => \Drupal::destination()->getAsArray())));
 }
 
diff --git a/core/modules/user/src/Tests/UserAdminListingTest.php b/core/modules/user/src/Tests/UserAdminListingTest.php
index fbf31976288e87035d50458e05235c0d488759b2..76fc674e4d047ec6d7fce421b2a743e7eb72c182 100644
--- a/core/modules/user/src/Tests/UserAdminListingTest.php
+++ b/core/modules/user/src/Tests/UserAdminListingTest.php
@@ -91,7 +91,7 @@ public function testUserListing() {
     $expected_roles = array('custom_role_1', 'custom_role_2');
     $this->assertEqual($result_accounts[$role_account_name]['roles'], $expected_roles, 'Ensure roles are listed properly.');
 
-    $this->assertEqual($result_accounts[$timestamp_user]['member_for'], \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $accounts[$timestamp_user]->created->value), 'Ensure the right member time is displayed.');
+    $this->assertEqual($result_accounts[$timestamp_user]['member_for'], \Drupal::service('date.formatter')->formatTimeDiffSince($accounts[$timestamp_user]->created->value), 'Ensure the right member time is displayed.');
   }
 
 }
diff --git a/core/modules/user/src/UserListBuilder.php b/core/modules/user/src/UserListBuilder.php
index fc7daee24159ce9a550b95f29fdaf03d3544f417..7575b867270d6caf0f5665925fa1ab647c281d59 100644
--- a/core/modules/user/src/UserListBuilder.php
+++ b/core/modules/user/src/UserListBuilder.php
@@ -151,10 +151,8 @@ public function buildRow(EntityInterface $entity) {
       '#theme' => 'item_list',
       '#items' => $users_roles,
     );
-    $row['member_for'] = $this->dateFormatter->formatInterval(REQUEST_TIME - $entity->getCreatedTime());
-    $row['access'] = $entity->access ? $this->t('@time ago', array(
-      '@time' => $this->dateFormatter->formatInterval(REQUEST_TIME - $entity->getLastAccessedTime()),
-    )) : t('never');
+    $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');
     return $row + parent::buildRow($entity);
   }
 
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index f2c241497a4c3690a08934b72b23fa7a8c90e585..1d59201075f33a08de41e617cd976d7b0b3f35b0 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -370,7 +370,7 @@ function user_user_view(array &$build, UserInterface $account, EntityViewDisplay
   if ($display->getComponent('member_for')) {
     $build['member_for'] = array(
       '#type' => 'item',
-      '#markup' => '<h4 class="label">' . t('Member for') . '</h4> ' . \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $account->getCreatedTime()),
+      '#markup' => '<h4 class="label">' . t('Member for') . '</h4> ' . \Drupal::service('date.formatter')->formatTimeDiffSince($account->getCreatedTime()),
     );
   }
 }
diff --git a/core/modules/views/src/Plugin/views/field/Date.php b/core/modules/views/src/Plugin/views/field/Date.php
index 8857c1a673b531eff8830bb826ac8d291f4fe0b7..2a7886a5396be3a4de37e5a7008c41bffeac5aa6 100644
--- a/core/modules/views/src/Plugin/views/field/Date.php
+++ b/core/modules/views/src/Plugin/views/field/Date.php
@@ -151,24 +151,33 @@ public function render(ResultRow $values) {
       $time_diff = REQUEST_TIME - $value; // will be positive for a datetime in the past (ago), and negative for a datetime in the future (hence)
       switch ($format) {
         case 'raw time ago':
-          return $this->dateFormatter->formatInterval($time_diff, is_numeric($custom_format) ? $custom_format : 2);
+          return $this->dateFormatter->formatTimeDiffSince($value, array('granularity' => is_numeric($custom_format) ? $custom_format : 2));
+
         case 'time ago':
-          return $this->t('%time ago', array('%time' => $this->dateFormatter->formatInterval($time_diff, is_numeric($custom_format) ? $custom_format : 2)));
+          return $this->t('%time ago', array('%time' => $this->dateFormatter->formatTimeDiffSince($value, array('granularity' => is_numeric($custom_format) ? $custom_format : 2))));
+
         case 'raw time hence':
-          return $this->dateFormatter->formatInterval(-$time_diff, is_numeric($custom_format) ? $custom_format : 2);
+          return $this->dateFormatter->formatTimeDiffUntil($value, array('granularity' => is_numeric($custom_format) ? $custom_format : 2));
+
         case 'time hence':
-          return $this->t('%time hence', array('%time' => $this->dateFormatter->formatInterval(-$time_diff, is_numeric($custom_format) ? $custom_format : 2)));
+          return $this->t('%time hence', array('%time' => $this->dateFormatter->formatTimeDiffUntil($value, array('granularity' => is_numeric($custom_format) ? $custom_format : 2))));
+
         case 'raw time span':
-          return ($time_diff < 0 ? '-' : '') . $this->dateFormatter->formatInterval(abs($time_diff), is_numeric($custom_format) ? $custom_format : 2);
+          return ($time_diff < 0 ? '-' : '') . $this->dateFormatter->formatTimeDiffSince($value, array('strict' => FALSE, 'granularity' => is_numeric($custom_format) ? $custom_format : 2));
+
         case 'inverse time span':
-          return ($time_diff > 0 ? '-' : '') . $this->dateFormatter->formatInterval(abs($time_diff), is_numeric($custom_format) ? $custom_format : 2);
+          return ($time_diff > 0 ? '-' : '') . $this->dateFormatter->formatTimeDiffSince($value, array('strict' => FALSE, 'granularity' => is_numeric($custom_format) ? $custom_format : 2));
+
         case 'time span':
-          return $this->t(($time_diff < 0 ? '%time hence' : '%time ago'), array('%time' => $this->dateFormatter->formatInterval(abs($time_diff), is_numeric($custom_format) ? $custom_format : 2)));
+          $time = $this->dateFormatter->formatTimeDiffSince($value, array('strict' => FALSE, 'granularity' => is_numeric($custom_format) ? $custom_format : 2));
+          return ($time_diff < 0) ? $this->t('%time hence', array('%time' => $time)) : $this->t('%time ago', array('%time' => $time));
+
         case 'custom':
           if ($custom_format == 'r') {
             return format_date($value, $format, $custom_format, $timezone, 'en');
           }
           return format_date($value, $format, $custom_format, $timezone);
+
         default:
           return format_date($value, $format, '', $timezone);
       }
diff --git a/core/modules/views/src/Tests/Handler/FieldDateTest.php b/core/modules/views/src/Tests/Handler/FieldDateTest.php
index 3ea113d1cf0b3cfcea2110b69e65bd2f26bf440c..98eae40a01735f5ff275f533103a864e9cb13db3 100644
--- a/core/modules/views/src/Tests/Handler/FieldDateTest.php
+++ b/core/modules/views/src/Tests/Handler/FieldDateTest.php
@@ -121,19 +121,19 @@ public function testFieldDate() {
     }
 
     // Check times in the past.
-    $test = $this->container->get('date.formatter')->formatInterval(REQUEST_TIME - $time, 2);
+    $time_since = $this->container->get('date.formatter')->formatTimeDiffSince($time);
     $intervals = array(
-      'raw time ago' => $test,
-      'time ago' => t('%time ago', array('%time' => $test)),
-      'raw time span' => $test,
-      'inverse time span' => -$test,
-      'time span' => t('%time ago', array('%time' => $test)),
+      'raw time ago' => $time_since,
+      'time ago' => t('%time ago', array('%time' => $time_since)),
+      'raw time span' => $time_since,
+      'inverse time span' => -$time_since,
+      'time span' => t('%time ago', array('%time' => $time_since)),
     );
     $this->assertRenderedDatesEqual($view, $intervals);
 
     // Check times in the future.
     $time = gmmktime(0, 0, 0, 1, 1, 2050);
-    $formatted = $this->container->get('date.formatter')->formatInterval($time - REQUEST_TIME, 2);
+    $formatted = $this->container->get('date.formatter')->formatTimeDiffUntil($time);
     $intervals = array(
       'raw time span' => -$formatted,
       'time span' => t('%time hence', array(
diff --git a/core/modules/views_ui/src/ViewEditForm.php b/core/modules/views_ui/src/ViewEditForm.php
index 1e3261b6a154e6da8df9ecc0bdbc8b72024f8ae0..bf7fdcfb9ee02bdf5480e6e1c803298db3b907f2 100644
--- a/core/modules/views_ui/src/ViewEditForm.php
+++ b/core/modules/views_ui/src/ViewEditForm.php
@@ -147,7 +147,7 @@ public function form(array $form, FormStateInterface $form_state) {
       );
       $lock_message_substitutions = array(
         '!user' => drupal_render($username),
-        '!age' => $this->dateFormatter->formatInterval(REQUEST_TIME - $view->lock->updated),
+        '!age' => $this->dateFormatter->formatTimeDiffSince($view->lock->updated),
         '@url' => $view->url('break-lock-form'),
       );
       $form['locked'] = array(
diff --git a/core/tests/Drupal/Tests/Core/Datetime/DateTest.php b/core/tests/Drupal/Tests/Core/Datetime/DateTest.php
index 1cbed5ee1d019a578d6eb022fcf734c236209ba2..09c890a16bb07f9708077f62e079faaf36654b9c 100644
--- a/core/tests/Drupal/Tests/Core/Datetime/DateTest.php
+++ b/core/tests/Drupal/Tests/Core/Datetime/DateTest.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Datetime\DateFormatter;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Tests\UnitTestCase;
+use Symfony\Component\HttpFoundation\Request;
 
 /**
  * @coversDefaultClass \Drupal\Core\Datetime\DateFormatter
@@ -39,22 +40,37 @@ class DateTest extends UnitTestCase {
   protected $stringTranslation;
 
   /**
-   * The tested date service class.
+   * The mocked string translation.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $requestStack;
+
+  /**
+   * The mocked date formatter class.
    *
    * @var \Drupal\Core\Datetime\DateFormatter
    */
   protected $dateFormatter;
 
+  /**
+   * The date formatter class where methods can be stubbed.
+   *
+   * @var \Drupal\Core\Datetime\DateFormatter|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $dateFormatterStub;
+
   protected function setUp() {
     parent::setUp();
 
     $entity_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface');
 
     $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
-    $this->entityManager->expects($this->once())->method('getStorage')->with('date_format')->willReturn($entity_storage);
+    $this->entityManager->expects($this->any())->method('getStorage')->with('date_format')->willReturn($entity_storage);
 
     $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
     $this->stringTranslation = $this->getMock('Drupal\Core\StringTranslation\TranslationInterface');
+    $this->requestStack = $this->getMock('Symfony\Component\HttpFoundation\RequestStack');
 
     $config_factory = $this->getConfigFactoryStub(['system.date' => ['country' => ['default' => 'GB']]]);
     $container = new ContainerBuilder();
@@ -62,15 +78,20 @@ protected function setUp() {
     $container->set('string_translation', $this->getStringTranslationStub());
     \Drupal::setContainer($container);
 
-    $this->dateFormatter = new DateFormatter($this->entityManager, $this->languageManager, $this->stringTranslation, $this->getConfigFactoryStub());
+    $this->dateFormatter = new DateFormatter($this->entityManager, $this->languageManager, $this->stringTranslation, $this->getConfigFactoryStub(), $this->requestStack);
+
+    $this->dateFormatterStub = $this->getMockBuilder('\Drupal\Core\Datetime\DateFormatter')
+      ->setConstructorArgs([$this->entityManager, $this->languageManager, $this->stringTranslation, $this->getConfigFactoryStub(), $this->requestStack])
+      ->setMethods(['formatDiff'])
+      ->getMock();
   }
 
   /**
-   * Tests the formatPlugin method.
+   * Tests the formatInterval method.
    *
    * @dataProvider providerTestFormatInterval
    *
-   * @covers \Drupal\Core\Datetime\DateFormatter::formatInterval
+   * @covers ::formatInterval
    */
   public function testFormatInterval($interval, $granularity, $expected, $langcode = NULL) {
     // Mocks a simple formatPlural implementation.
@@ -160,4 +181,218 @@ public function testGetSampleDateFormats() {
     }
   }
 
+  /**
+   * Tests the formatTimeDiffUntil method.
+   *
+   * @covers ::formatTimeDiffUntil
+   */
+  public function testFormatTimeDiffUntil() {
+    $expected = '1 second';
+    $request_time = $this->createTimestamp('2013-12-11 10:09:08');
+    $timestamp = $this->createTimestamp('2013-12-11 10:09:09');
+    $options = array();
+
+    // Mocks the formatDiff function of the dateformatter object.
+    $this->dateFormatterStub
+      ->expects($this->any())
+      ->method('formatDiff')
+      ->with($timestamp, $request_time, $options)
+      ->will($this->returnValue($expected));
+
+    $request = Request::createFromGlobals();
+    $request->server->set('REQUEST_TIME', $request_time);
+    // Mocks a the request stack getting the current request.
+    $this->requestStack->expects($this->any())
+      ->method('getCurrentRequest')
+      ->willReturn($request);
+
+    $this->assertEquals($expected, $this->dateFormatterStub->formatTimeDiffSince($timestamp, $options));
+  }
+
+  /**
+   * Tests the formatTimeDiffSince method.
+   *
+   * @covers ::formatTimeDiffSince
+   */
+  public function testFormatTimeDiffSince() {
+    $expected = '1 second';
+    $timestamp = $this->createTimestamp('2013-12-11 10:09:07');
+    $request_time = $this->createTimestamp('2013-12-11 10:09:08');
+    $options = array();
+
+    // Mocks the formatDiff function of the dateformatter object.
+    $this->dateFormatterStub
+      ->expects($this->any())
+      ->method('formatDiff')
+      ->with($request_time, $timestamp, $options)
+      ->will($this->returnValue($expected));
+
+    $request = Request::createFromGlobals();
+    $request->server->set('REQUEST_TIME', $request_time);
+    // Mocks a the request stack getting the current request.
+    $this->requestStack->expects($this->any())
+      ->method('getCurrentRequest')
+      ->willReturn($request);
+
+    $this->assertEquals($expected, $this->dateFormatterStub->formatTimeDiffUntil($timestamp, $options));
+  }
+
+  /**
+   * Tests the formatDiff method.
+   *
+   * @dataProvider providerTestFormatDiff
+   *
+   * @covers ::formatDiff
+   */
+  public function testformatDiff($expected, $timestamp1, $timestamp2, $options = array()) {
+
+    // Mocks a simple formatPlural implementation.
+    $this->stringTranslation->expects($this->any())
+      ->method('formatPlural')
+      ->with($this->anything(), $this->anything(), $this->anything(), array(), array('langcode' => isset($options['langcode']) ? $options['langcode'] : NULL))
+      ->will($this->returnCallback(function($count, $one, $multiple) {
+        return $count == 1 ? $one : str_replace('@count', $count, $multiple);
+      }));
+
+    // Mocks a simple translate implementation.
+    $this->stringTranslation->expects($this->any())
+      ->method('translate')
+      ->with($this->anything())
+      ->will($this->returnCallback(function($string, $args, $options) {
+        return $string;
+      }));
+
+    $this->assertEquals($expected, $this->dateFormatter->formatDiff($timestamp1, $timestamp2, $options));
+  }
+
+  /**
+   * Data provider for testformatDiff().
+   */
+  public function providerTestFormatDiff() {
+    // This is the fixed request time in the test.
+    $request_time = $this->createTimestamp('2013-12-11 10:09:08');
+
+    $granularity_3 = array('granularity' => 3);
+    $granularity_4 = array('granularity' => 4);
+
+    $langcode_en = array('langcode' => 'en');
+    $langcode_lolspeak = array('langcode' => 'xxx-lolspeak');
+
+    $non_strict = array('strict' => FALSE);
+
+    $data = array(
+      // Checks for equal timestamps.
+      array('0 seconds', $request_time, $request_time),
+
+      // Checks for seconds only.
+      array('1 second', $this->createTimestamp('2013-12-11 10:09:07'), $request_time),
+      array('1 second', $this->createTimestamp('2013-12-11 10:09:07'), $request_time),
+      array('1 second', $this->createTimestamp('2013-12-11 10:09:07'), $request_time, $granularity_3 + $langcode_en),
+      array('1 second', $this->createTimestamp('2013-12-11 10:09:07'), $request_time, $granularity_4 + $langcode_lolspeak),
+      array('2 seconds', $this->createTimestamp('2013-12-11 10:09:06'), $request_time),
+      array('59 seconds', $this->createTimestamp('2013-12-11 10:08:09'), $request_time),
+      array('59 seconds', $this->createTimestamp('2013-12-11 10:08:09'), $request_time),
+
+      // Checks for minutes and possibly seconds.
+      array('1 minute', $this->createTimestamp('2013-12-11 10:08:08'), $request_time),
+      array('1 minute', $this->createTimestamp('2013-12-11 10:08:08'), $request_time),
+      array('1 minute 1 second', $this->createTimestamp('2013-12-11 10:08:07'), $request_time),
+      array('1 minute 59 seconds', $this->createTimestamp('2013-12-11 10:07:09'), $request_time),
+      array('2 minutes', $this->createTimestamp('2013-12-11 10:07:08'), $request_time),
+      array('2 minutes 1 second', $this->createTimestamp('2013-12-11 10:07:07'), $request_time),
+      array('2 minutes 2 seconds', $this->createTimestamp('2013-12-11 10:07:06'), $request_time),
+      array('2 minutes 2 seconds', $this->createTimestamp('2013-12-11 10:07:06'), $request_time, $granularity_3),
+      array('2 minutes 2 seconds', $this->createTimestamp('2013-12-11 10:07:06'), $request_time, $granularity_4),
+      array('30 minutes', $this->createTimestamp('2013-12-11 09:39:08'), $request_time),
+      array('59 minutes 59 seconds', $this->createTimestamp('2013-12-11 09:09:09'), $request_time),
+      array('59 minutes 59 seconds', $this->createTimestamp('2013-12-11 09:09:09'), $request_time),
+
+      // Checks for hours and possibly minutes or seconds.
+      array('1 hour', $this->createTimestamp('2013-12-11 09:09:08'), $request_time),
+      array('1 hour', $this->createTimestamp('2013-12-11 09:09:08'), $request_time),
+      array('1 hour 1 second', $this->createTimestamp('2013-12-11 09:09:07'), $request_time),
+      array('1 hour 2 seconds', $this->createTimestamp('2013-12-11 09:09:06'), $request_time),
+      array('1 hour 1 minute', $this->createTimestamp('2013-12-11 09:08:08'), $request_time),
+      array('1 hour 1 minute 1 second', $this->createTimestamp('2013-12-11 09:08:07'), $request_time, $granularity_3),
+      array('1 hour 1 minute 2 seconds', $this->createTimestamp('2013-12-11 09:08:06'), $request_time, $granularity_4),
+      array('1 hour 30 minutes', $this->createTimestamp('2013-12-11 08:39:08'), $request_time),
+      array('2 hours', $this->createTimestamp('2013-12-11 08:09:08'), $request_time),
+      array('23 hours 59 minutes', $this->createTimestamp('2013-12-10 10:10:08'), $request_time),
+
+      // Checks for days and possibly hours, minutes or seconds.
+      array('1 day', $this->createTimestamp('2013-12-10 10:09:08'), $request_time),
+      array('1 day 1 second', $this->createTimestamp('2013-12-10 10:09:07'), $request_time),
+      array('1 day 1 hour', $this->createTimestamp('2013-12-10 09:09:08'), $request_time),
+      array('1 day 1 hour 1 minute', $this->createTimestamp('2013-12-10 09:08:07'), $request_time, $granularity_3 + $langcode_en),
+      array('1 day 1 hour 1 minute 1 second', $this->createTimestamp('2013-12-10 09:08:07'), $request_time, $granularity_4 + $langcode_lolspeak),
+      array('1 day 2 hours 2 minutes 2 seconds', $this->createTimestamp('2013-12-10 08:07:06'), $request_time, $granularity_4),
+      array('2 days', $this->createTimestamp('2013-12-09 10:09:08'), $request_time),
+      array('2 days 2 minutes', $this->createTimestamp('2013-12-09 10:07:08'), $request_time),
+      array('2 days 2 hours', $this->createTimestamp('2013-12-09 08:09:08'), $request_time),
+      array('2 days 2 hours 2 minutes', $this->createTimestamp('2013-12-09 08:07:06'), $request_time, $granularity_3 + $langcode_en),
+      array('2 days 2 hours 2 minutes 2 seconds', $this->createTimestamp('2013-12-09 08:07:06'), $request_time, $granularity_4 + $langcode_lolspeak),
+
+      // Checks for weeks and possibly days, hours, minutes or seconds.
+      array('1 week', $this->createTimestamp('2013-12-04 10:09:08'), $request_time),
+      array('1 week 1 day', $this->createTimestamp('2013-12-03 10:09:08'), $request_time),
+      array('2 weeks', $this->createTimestamp('2013-11-27 10:09:08'), $request_time),
+      array('2 weeks 2 days', $this->createTimestamp('2013-11-25 08:07:08'), $request_time),
+      array('2 weeks 2 days 2 hours 2 minutes', $this->createTimestamp('2013-11-25 08:07:08'), $request_time, $granularity_4),
+      array('4 weeks', $this->createTimestamp('2013-11-13 10:09:08'), $request_time),
+      array('4 weeks 1 day', $this->createTimestamp('2013-11-12 10:09:08'), $request_time),
+
+      // Checks for months and possibly days, hours, minutes or seconds.
+      array('1 month', $this->createTimestamp('2013-11-11 10:09:08'), $request_time),
+      array('1 month 1 second', $this->createTimestamp('2013-11-11 10:09:07'), $request_time),
+      array('1 month 1 hour', $this->createTimestamp('2013-11-11 09:09:08'), $request_time),
+      array('1 month 1 hour 1 minute', $this->createTimestamp('2013-11-11 09:08:07'), $request_time, $granularity_3),
+      array('1 month 1 hour 1 minute 1 second', $this->createTimestamp('2013-11-11 09:08:07'), $request_time, $granularity_4),
+      array('1 month 4 weeks', $this->createTimestamp('2013-10-13 10:09:08'), $request_time),
+      array('1 month 4 weeks 1 day', $this->createTimestamp('2013-10-13 10:09:08'), $request_time, $granularity_3),
+      array('1 month 4 weeks', $this->createTimestamp('2013-10-12 10:09:08'), $request_time),
+      array('1 month 4 weeks 2 days', $this->createTimestamp('2013-10-12 10:09:08'), $request_time, $granularity_3),
+      array('2 months', $this->createTimestamp('2013-10-11 10:09:08'), $request_time),
+      array('2 months 1 day', $this->createTimestamp('2013-10-10 10:09:08'), $request_time),
+      array('2 months 2 days', $this->createTimestamp('2013-10-09 08:07:06'), $request_time),
+      array('2 months 2 days 2 hours', $this->createTimestamp('2013-10-09 08:07:06'), $request_time, $granularity_3),
+      array('2 months 2 days 2 hours 2 minutes', $this->createTimestamp('2013-10-09 08:07:06'), $request_time, $granularity_4),
+      array('6 months 2 days', $this->createTimestamp('2013-06-09 10:09:08'), $request_time),
+      array('11 months 3 hours', $this->createTimestamp('2013-01-11 07:09:08'), $request_time),
+      array('11 months 4 weeks', $this->createTimestamp('2012-12-12 10:09:08'), $request_time),
+      array('11 months 4 weeks 2 days', $this->createTimestamp('2012-12-12 10:09:08'), $request_time, $granularity_3),
+
+      // Checks for years and possibly months, days, hours, minutes or seconds.
+      array('1 year', $this->createTimestamp('2012-12-11 10:09:08'), $request_time),
+      array('1 year 1 minute', $this->createTimestamp('2012-12-11 10:08:08'), $request_time),
+      array('1 year 1 day', $this->createTimestamp('2012-12-10 10:09:08'), $request_time),
+      array('2 years', $this->createTimestamp('2011-12-11 10:09:08'), $request_time),
+      array('2 years 2 minutes', $this->createTimestamp('2011-12-11 10:07:08'), $request_time),
+      array('2 years 2 days', $this->createTimestamp('2011-12-09 10:09:08'), $request_time),
+      array('2 years 2 months 2 days', $this->createTimestamp('2011-10-09 08:07:06'), $request_time, $granularity_3),
+      array('2 years 2 months 2 days 2 hours', $this->createTimestamp('2011-10-09 08:07:06'), $request_time, $granularity_4),
+      array('10 years', $this->createTimestamp('2003-12-11 10:09:08'), $request_time),
+      array('100 years', $this->createTimestamp('1913-12-11 10:09:08'), $request_time),
+
+      // Checks the non-strict option vs. strict (default).
+      array('1 second', $this->createTimestamp('2013-12-11 10:09:08'), $this->createTimestamp('2013-12-11 10:09:07'), $non_strict),
+      array('0 seconds', $this->createTimestamp('2013-12-11 10:09:08'), $this->createTimestamp('2013-12-11 10:09:07')),
+    );
+
+    return $data;
+  }
+
+  /**
+   * Creates a UNIX timestamp given a date and time string in the format
+   * year-month-day hour:minute:seconds (e.g. 2013-12-11 10:09:08).
+   *
+   * @param string $dateTimeString
+   *   The formatted date and time string.
+   *
+   * @return int
+   *   The UNIX timestamp.
+   */
+  private function createTimestamp($dateTimeString) {
+    return \DateTime::createFromFormat('Y-m-d G:i:s', $dateTimeString)->getTimestamp();
+  }
+
 }