3 namespace Drupal\Core\Datetime;
5 use Drupal\Core\Config\ConfigFactoryInterface;
6 use Drupal\Core\Entity\EntityManagerInterface;
7 use Drupal\Core\Language\Language;
8 use Drupal\Core\Language\LanguageManagerInterface;
9 use Drupal\Core\StringTranslation\TranslationInterface;
10 use Drupal\Core\StringTranslation\StringTranslationTrait;
11 use Symfony\Component\HttpFoundation\RequestStack;
14 * Provides a service to handle various date related functionality.
18 class DateFormatter implements DateFormatterInterface {
19 use StringTranslationTrait;
22 * The list of loaded timezones.
29 * The date format storage.
31 * @var \Drupal\Core\Entity\EntityStorageInterface
33 protected $dateFormatStorage;
36 * Language manager for retrieving the default langcode when none is specified.
38 * @var \Drupal\Core\Language\LanguageManagerInterface
40 protected $languageManager;
43 * The configuration factory.
45 * @var \Drupal\Core\Config\ConfigFactoryInterface
47 protected $configFactory;
52 * @var \Symfony\Component\HttpFoundation\RequestStack
54 protected $requestStack;
56 protected $country = NULL;
57 protected $dateFormats = [];
60 * Contains the different date interval units.
62 * This array is keyed by strings representing the unit (e.g.
63 * '1 year|@count years') and with the amount of values of the unit in
69 '1 year|@count years' => 31536000,
70 '1 month|@count months' => 2592000,
71 '1 week|@count weeks' => 604800,
72 '1 day|@count days' => 86400,
73 '1 hour|@count hours' => 3600,
74 '1 min|@count min' => 60,
75 '1 sec|@count sec' => 1,
79 * Constructs a Date object.
81 * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
83 * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
84 * The language manager.
85 * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
86 * The string translation.
87 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
88 * The configuration factory.
89 * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
92 public function __construct(EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager, TranslationInterface $translation, ConfigFactoryInterface $config_factory, RequestStack $request_stack) {
93 $this->dateFormatStorage = $entity_manager->getStorage('date_format');
94 $this->languageManager = $language_manager;
95 $this->stringTranslation = $translation;
96 $this->configFactory = $config_factory;
97 $this->requestStack = $request_stack;
103 public function format($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) {
104 if (!isset($timezone)) {
105 $timezone = date_default_timezone_get();
107 // Store DateTimeZone objects in an array rather than repeatedly
108 // constructing identical objects over the life of a request.
109 if (!isset($this->timezones[$timezone])) {
110 $this->timezones[$timezone] = timezone_open($timezone);
113 if (empty($langcode)) {
114 $langcode = $this->languageManager->getCurrentLanguage()->getId();
117 // Create a DrupalDateTime object from the timestamp and timezone.
119 'langcode' => $langcode,
120 'country' => $this->country(),
122 $date = DrupalDateTime::createFromTimestamp($timestamp, $this->timezones[$timezone], $create_settings);
124 // If we have a non-custom date format use the provided date format pattern.
125 if ($type !== 'custom') {
126 if ($date_format = $this->dateFormat($type, $langcode)) {
127 $format = $date_format->getPattern();
131 // Fall back to the 'medium' date format type if the format string is
132 // empty, either from not finding a requested date format or being given an
133 // empty custom format string.
134 if (empty($format)) {
135 $format = $this->dateFormat('fallback', $langcode)->getPattern();
138 // Call $date->format().
140 'langcode' => $langcode,
142 return $date->format($format, $settings);
148 public function formatInterval($interval, $granularity = 2, $langcode = NULL) {
150 foreach ($this->units as $key => $value) {
151 $key = explode('|', $key);
152 if ($interval >= $value) {
153 $output .= ($output ? ' ' : '') . $this->formatPlural(floor($interval / $value), $key[0], $key[1], [], ['langcode' => $langcode]);
158 // Break if there was previous output but not any output at this level,
159 // to avoid skipping levels and getting output like "1 year 1 second".
163 if ($granularity == 0) {
167 return $output ? $output : $this->t('0 sec', [], ['langcode' => $langcode]);
173 public function getSampleDateFormats($langcode = NULL, $timestamp = NULL, $timezone = NULL) {
174 $timestamp = $timestamp ?: time();
175 // All date format characters for the PHP date() function.
176 $date_chars = str_split('dDjlNSwzWFmMntLoYyaABgGhHisueIOPTZcrU');
177 $date_elements = array_combine($date_chars, $date_chars);
178 return array_map(function ($character) use ($timestamp, $timezone, $langcode) {
179 return $this->format($timestamp, 'custom', $character, $timezone, $langcode);
186 public function formatTimeDiffUntil($timestamp, $options = []) {
187 $request_time = $this->requestStack->getCurrentRequest()->server->get('REQUEST_TIME');
188 return $this->formatDiff($request_time, $timestamp, $options);
194 public function formatTimeDiffSince($timestamp, $options = []) {
195 $request_time = $this->requestStack->getCurrentRequest()->server->get('REQUEST_TIME');
196 return $this->formatDiff($timestamp, $request_time, $options);
202 public function formatDiff($from, $to, $options = []) {
208 'return_as_object' => FALSE,
211 if ($options['strict'] && $from > $to) {
212 $string = $this->t('0 seconds');
213 if ($options['return_as_object']) {
214 return new FormattedDateDiff($string, 0);
219 $date_time_from = new \DateTime();
220 $date_time_from->setTimestamp($from);
222 $date_time_to = new \DateTime();
223 $date_time_to->setTimestamp($to);
225 $interval = $date_time_to->diff($date_time_from);
227 $granularity = $options['granularity'];
230 // We loop over the keys provided by \DateInterval explicitly. Since we
231 // don't take the "invert" property into account, the resulting output value
232 // will always be positive.
234 foreach (['y', 'm', 'd', 'h', 'i', 's'] as $value) {
235 if ($interval->$value > 0) {
236 // Switch over the keys to call formatPlural() explicitly with literal
237 // strings for all different possibilities.
240 $interval_output = $this->formatPlural($interval->y, '1 year', '@count years', [], ['langcode' => $options['langcode']]);
241 $max_age = min($max_age, 365 * 86400);
245 $interval_output = $this->formatPlural($interval->m, '1 month', '@count months', [], ['langcode' => $options['langcode']]);
246 $max_age = min($max_age, 30 * 86400);
250 // \DateInterval doesn't support weeks, so we need to calculate them
252 $interval_output = '';
253 $days = $interval->d;
254 $weeks = floor($days / 7);
256 $interval_output .= $this->formatPlural($weeks, '1 week', '@count weeks', [], ['langcode' => $options['langcode']]);
259 $max_age = min($max_age, 7 * 86400);
262 if ((!$output || $weeks > 0) && $granularity > 0 && $days > 0) {
263 $interval_output .= ($interval_output ? ' ' : '') . $this->formatPlural($days, '1 day', '@count days', [], ['langcode' => $options['langcode']]);
264 $max_age = min($max_age, 86400);
267 // If we did not output days, set the granularity to 0 so that we
268 // will not output hours and get things like "1 week 1 hour".
274 $interval_output = $this->formatPlural($interval->h, '1 hour', '@count hours', [], ['langcode' => $options['langcode']]);
275 $max_age = min($max_age, 3600);
279 $interval_output = $this->formatPlural($interval->i, '1 minute', '@count minutes', [], ['langcode' => $options['langcode']]);
280 $max_age = min($max_age, 60);
284 $interval_output = $this->formatPlural($interval->s, '1 second', '@count seconds', [], ['langcode' => $options['langcode']]);
285 $max_age = min($max_age, 1);
289 $output .= ($output && $interval_output ? ' ' : '') . $interval_output;
293 // Break if there was previous output but not any output at this level,
294 // to avoid skipping levels and getting output like "1 year 1 second".
298 if ($granularity <= 0) {
303 if (empty($output)) {
304 $output = $this->t('0 seconds');
308 if ($options['return_as_object']) {
309 return new FormattedDateDiff($output, $max_age);
316 * Loads the given format pattern for the given langcode.
318 * @param string $format
319 * The machine name of the date format.
320 * @param string $langcode
321 * The langcode of the language to use.
323 * @return \Drupal\Core\Datetime\DateFormatInterface|null
324 * The configuration entity for the date format in the given language for
325 * non-custom formats, NULL otherwise.
327 protected function dateFormat($format, $langcode) {
328 if (!isset($this->dateFormats[$format][$langcode])) {
329 $original_language = $this->languageManager->getConfigOverrideLanguage();
330 $this->languageManager->setConfigOverrideLanguage(new Language(['id' => $langcode]));
331 $this->dateFormats[$format][$langcode] = $this->dateFormatStorage->load($format);
332 $this->languageManager->setConfigOverrideLanguage($original_language);
334 return $this->dateFormats[$format][$langcode];
338 * Returns the default country from config.
341 * The config setting for country.default.
343 protected function country() {
344 if ($this->country === NULL) {
345 $this->country = \Drupal::config('system.date')->get('country.default');
347 return $this->country;