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(); + } + }