Skip to content

Commit b43f917

Browse files
committed
[Translation] Add DateIntervalFormatter
1 parent c01b032 commit b43f917

File tree

11 files changed

+239
-1
lines changed

11 files changed

+239
-1
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Symfony\Bridge\Twig\Extension;
4+
5+
use Symfony\Component\Intl\DateIntervalFormatter\DateIntervalFormatter;
6+
use Symfony\Component\Intl\DateIntervalFormatter\DateIntervalFormatterInterface;
7+
use Twig\Extension\AbstractExtension;
8+
use Twig\TwigFilter;
9+
10+
class IntlExtension extends AbstractExtension
11+
{
12+
private $dateIntervalFormatter;
13+
14+
public function __construct(DateIntervalFormatterInterface $dateIntervalFormatter = null)
15+
{
16+
$this->dateIntervalFormatter = $dateIntervalFormatter ?? new DateIntervalFormatter();
17+
}
18+
19+
public function getFilters(): array
20+
{
21+
return [
22+
new TwigFilter('date_interval', [$this->dateIntervalFormatter, 'formatInterval']),
23+
new TwigFilter('date_relative', [$this->dateIntervalFormatter, 'formatRelative']),
24+
];
25+
}
26+
}

src/Symfony/Bridge/Twig/Extension/TranslationExtension.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
use Symfony\Bridge\Twig\NodeVisitor\TranslationNodeVisitor;
1616
use Symfony\Bridge\Twig\TokenParser\TransDefaultDomainTokenParser;
1717
use Symfony\Bridge\Twig\TokenParser\TransTokenParser;
18+
use Symfony\Component\Intl\DateIntervalFormatter\DateIntervalFormatter;
19+
use Symfony\Component\Intl\DateIntervalFormatter\DateIntervalFormatterInterface;
1820
use Symfony\Component\Translation\TranslatableMessage;
1921
use Symfony\Contracts\Translation\TranslatableInterface;
2022
use Symfony\Contracts\Translation\TranslatorInterface;
@@ -36,6 +38,7 @@ final class TranslationExtension extends AbstractExtension
3638
{
3739
private $translator;
3840
private $translationNodeVisitor;
41+
private $dateIntervalFormatter;
3942

4043
public function __construct(TranslatorInterface $translator = null, TranslationNodeVisitor $translationNodeVisitor = null)
4144
{

src/Symfony/Bridge/Twig/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"symfony/form": "^5.3",
3131
"symfony/http-foundation": "^4.4|^5.0",
3232
"symfony/http-kernel": "^4.4|^5.0",
33+
"symfony/intl": "^5.3",
3334
"symfony/mime": "^5.2",
3435
"symfony/polyfill-intl-icu": "~1.0",
3536
"symfony/property-info": "^4.4|^5.1",
@@ -48,7 +49,7 @@
4849
"symfony/workflow": "^5.2",
4950
"twig/cssinliner-extra": "^2.12",
5051
"twig/inky-extra": "^2.12",
51-
"twig/markdown-extra": "^2.12"
52+
"twig/markdown-extra": "^2.12",
5253
},
5354
"conflict": {
5455
"symfony/console": "<4.4",

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
7474
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
7575
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
76+
use Symfony\Component\Intl\DateIntervalFormatter\DateIntervalFormatterInterface;
7677
use Symfony\Component\Lock\Lock;
7778
use Symfony\Component\Lock\LockFactory;
7879
use Symfony\Component\Lock\LockInterface;
@@ -545,6 +546,10 @@ public function load(array $configs, ContainerBuilder $container)
545546
'kernel.locale_aware',
546547
'kernel.reset',
547548
]);
549+
550+
if (\class_exists(DateIntervalFormatterInterface::class)) {
551+
$loader->load('intl.php');
552+
}
548553
}
549554

550555
/**
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
13+
14+
use Symfony\Component\Intl\DateIntervalFormatter\DateIntervalFormatter;
15+
use Symfony\Component\Intl\DateIntervalFormatter\DateIntervalFormatterInterface;
16+
17+
return static function (ContainerConfigurator $container) {
18+
$container->services()
19+
->set('intl.date_interval_formatter', DateIntervalFormatter::class)
20+
->alias(DateIntervalFormatterInterface::class, 'intl.date_interval_formatter')
21+
;
22+
};

src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use Psr\Container\ContainerInterface;
1515
use Symfony\Bundle\FrameworkBundle\CacheWarmer\TranslationsCacheWarmer;
1616
use Symfony\Bundle\FrameworkBundle\Translation\Translator;
17+
use Symfony\Component\Intl\DateIntervalFormatter\DateIntervalFormatter;
18+
use Symfony\Component\Intl\DateIntervalFormatter\DateIntervalFormatterInterface;
1719
use Symfony\Component\Translation\Dumper\CsvFileDumper;
1820
use Symfony\Component\Translation\Dumper\IcuResFileDumper;
1921
use Symfony\Component\Translation\Dumper\IniFileDumper;
@@ -158,5 +160,8 @@
158160
->args([service(ContainerInterface::class)])
159161
->tag('container.service_subscriber', ['id' => 'translator'])
160162
->tag('kernel.cache_warmer')
163+
164+
->set('translation.formatter.date_interval', DateIntervalFormatter::class)
165+
->alias(DateIntervalFormatterInterface::class, 'translation.formatter.date_interval')
161166
;
162167
};

src/Symfony/Bundle/TwigBundle/Resources/config/twig.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Symfony\Bridge\Twig\Extension\HttpFoundationExtension;
2222
use Symfony\Bridge\Twig\Extension\HttpKernelExtension;
2323
use Symfony\Bridge\Twig\Extension\HttpKernelRuntime;
24+
use Symfony\Bridge\Twig\Extension\IntlExtension;
2425
use Symfony\Bridge\Twig\Extension\ProfilerExtension;
2526
use Symfony\Bridge\Twig\Extension\RoutingExtension;
2627
use Symfony\Bridge\Twig\Extension\StopwatchExtension;
@@ -160,5 +161,9 @@
160161
->factory([TwigErrorRenderer::class, 'isDebug'])
161162
->args([service('request_stack'), param('kernel.debug')]),
162163
])
164+
165+
->set('twig.extension.intl', IntlExtension::class)
166+
->args([service('intl.date_interval_formatter')->nullOnInvalid()])
167+
->tag('twig.extension')
163168
;
164169
};

src/Symfony/Component/Intl/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
5.3
5+
---
6+
7+
* Add `DateIntervalFormatter` to humanize a `DateInterval` or a difference between two `DateTimeInterface` objects
8+
49
5.0.0
510
-----
611

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Intl\DateIntervalFormatter;
13+
14+
class DateIntervalFormatter implements DateIntervalFormatterInterface
15+
{
16+
private const VALUE_NAMES = [
17+
'y' => 'year',
18+
'm' => 'month',
19+
'd' => 'day',
20+
'h' => 'hour',
21+
'i' => 'minute',
22+
's' => 'second',
23+
];
24+
25+
public function formatInterval($interval, int $precision = 0): string
26+
{
27+
if (!$interval instanceof \DateInterval) {
28+
$interval = new \DateInterval($interval);
29+
}
30+
31+
$elements = [];
32+
$currentPrecision = 0;
33+
34+
foreach (self::VALUE_NAMES as $value => $name) {
35+
if ($elements) {
36+
++$currentPrecision;
37+
}
38+
39+
if ((!$precision || $currentPrecision < $precision) && $interval->{$value}) {
40+
$elements[] = $this->format($interval->{$value}, $name);
41+
}
42+
}
43+
44+
$lastElement = array_pop($elements);
45+
46+
return null !== $lastElement ? ($elements ? implode(', ', $elements).' and '.$lastElement : $lastElement) : 'now';
47+
}
48+
49+
public function formatRelative($dateTime, $currentDateTime = 'now', int $precision = 0): string
50+
{
51+
if (!$dateTime instanceof \DateTimeInterface) {
52+
$dateTime = new \DateTimeImmutable($dateTime);
53+
}
54+
55+
if (!$currentDateTime instanceof \DateTimeInterface) {
56+
$currentDateTime = new \DateTimeImmutable($currentDateTime);
57+
}
58+
59+
if ($dateTime > $currentDateTime) {
60+
return 'in '.$this->formatInterval($currentDateTime->diff($dateTime));
61+
}
62+
63+
if ($dateTime->getTimestamp() === $currentDateTime->getTimestamp()) {
64+
return 'now';
65+
}
66+
67+
return $this->formatInterval($currentDateTime->diff($dateTime), $precision).' ago';
68+
}
69+
70+
private function format(int $number, string $singular): string
71+
{
72+
return $number > 1 ? "{$number} {$singular}s" : "{$number} {$singular}";
73+
}
74+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Intl\DateIntervalFormatter;
13+
14+
interface DateIntervalFormatterInterface
15+
{
16+
/**
17+
* @param \DateInterval|string $interval
18+
*/
19+
public function formatInterval($interval, int $precision = 0): string;
20+
21+
/**
22+
* @param \DateTimeInterface|string $dateTime
23+
* @param \DateTimeInterface|string $currentDateTime
24+
*/
25+
public function formatRelative($dateTime, $currentDateTime = 'now', int $precision = 0): string;
26+
}

0 commit comments

Comments
 (0)