-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[Intl] Add DateIntervalFormatter
#39912
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Bridge\Twig\Extension; | ||
|
||
use Symfony\Component\Intl\DateIntervalFormatter\DateIntervalFormatter; | ||
use Symfony\Component\Intl\DateIntervalFormatter\DateIntervalFormatterInterface; | ||
use Twig\Extension\AbstractExtension; | ||
use Twig\TwigFilter; | ||
|
||
class IntlExtension extends AbstractExtension | ||
{ | ||
private $dateIntervalFormatter; | ||
|
||
public function __construct(DateIntervalFormatterInterface $dateIntervalFormatter = null) | ||
{ | ||
$this->dateIntervalFormatter = $dateIntervalFormatter ?? new DateIntervalFormatter(); | ||
} | ||
|
||
public function getFilters(): array | ||
{ | ||
return [ | ||
new TwigFilter('date_interval', [$this->dateIntervalFormatter, 'formatInterval']), | ||
new TwigFilter('date_relative', [$this->dateIntervalFormatter, 'formatRelative']), | ||
]; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -79,6 +79,7 @@ | |
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; | ||
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; | ||
use Symfony\Component\HttpKernel\DependencyInjection\Extension; | ||
use Symfony\Component\Intl\DateIntervalFormatter\DateIntervalFormatterInterface; | ||
use Symfony\Component\Lock\Lock; | ||
use Symfony\Component\Lock\LockFactory; | ||
use Symfony\Component\Lock\LockInterface; | ||
|
@@ -574,6 +575,10 @@ public function load(array $configs, ContainerBuilder $container) | |
'kernel.locale_aware', | ||
'kernel.reset', | ||
]); | ||
|
||
if (class_exists(DateIntervalFormatterInterface::class)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. interface_exists, since it's not a class but an interface |
||
$loader->load('intl.php'); | ||
} | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\DependencyInjection\Loader\Configurator; | ||
|
||
use Symfony\Component\Intl\DateIntervalFormatter\DateIntervalFormatter; | ||
use Symfony\Component\Intl\DateIntervalFormatter\DateIntervalFormatterInterface; | ||
|
||
return static function (ContainerConfigurator $container) { | ||
$container->services() | ||
->set('intl.date_interval_formatter', DateIntervalFormatter::class) | ||
->alias(DateIntervalFormatterInterface::class, 'intl.date_interval_formatter') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. alias should be unindented by one |
||
; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,8 @@ | |
use Psr\Container\ContainerInterface; | ||
use Symfony\Bundle\FrameworkBundle\CacheWarmer\TranslationsCacheWarmer; | ||
use Symfony\Bundle\FrameworkBundle\Translation\Translator; | ||
use Symfony\Component\Intl\DateIntervalFormatter\DateIntervalFormatter; | ||
use Symfony\Component\Intl\DateIntervalFormatter\DateIntervalFormatterInterface; | ||
use Symfony\Component\Translation\Dumper\CsvFileDumper; | ||
use Symfony\Component\Translation\Dumper\IcuResFileDumper; | ||
use Symfony\Component\Translation\Dumper\IniFileDumper; | ||
|
@@ -158,5 +160,8 @@ | |
->args([service(ContainerInterface::class)]) | ||
->tag('container.service_subscriber', ['id' => 'translator']) | ||
->tag('kernel.cache_warmer') | ||
|
||
->set('translation.formatter.date_interval', DateIntervalFormatter::class) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't get the need for this service |
||
->alias(DateIntervalFormatterInterface::class, 'translation.formatter.date_interval') | ||
; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,11 @@ | ||
CHANGELOG | ||
========= | ||
|
||
5.4 | ||
--- | ||
|
||
* Add `DateIntervalFormatter` to humanize a `DateInterval` or a difference between two `DateTimeInterface` objects | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing space at start of line |
||
|
||
5.3 | ||
--- | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Intl\DateIntervalFormatter; | ||
|
||
class DateIntervalFormatter implements DateIntervalFormatterInterface | ||
{ | ||
private const VALUE_NAMES = [ | ||
'y' => 'year', | ||
'm' => 'month', | ||
'd' => 'day', | ||
'h' => 'hour', | ||
'i' => 'minute', | ||
's' => 'second', | ||
]; | ||
|
||
public function formatInterval($interval, int $precision = 0): string | ||
{ | ||
if (!$interval instanceof \DateInterval) { | ||
$interval = new \DateInterval($interval); | ||
} | ||
|
||
$elements = []; | ||
$currentPrecision = 0; | ||
|
||
foreach (self::VALUE_NAMES as $value => $name) { | ||
if ($elements) { | ||
++$currentPrecision; | ||
} | ||
|
||
if ((!$precision || $currentPrecision < $precision) && $interval->{$value}) { | ||
$elements[] = $this->format($interval->{$value}, $name); | ||
} | ||
} | ||
|
||
$lastElement = array_pop($elements); | ||
|
||
return null !== $lastElement ? ($elements ? implode(', ', $elements).' and '.$lastElement : $lastElement) : 'now'; | ||
} | ||
|
||
/** | ||
* @param \DateTimeInterface|string $dateTime | ||
* @param \DateTimeInterface|string|null $currentDateTime | ||
*/ | ||
public function formatRelative($dateTime, $currentDateTime = null, int $precision = 0): string | ||
{ | ||
if (!$dateTime instanceof \DateTimeInterface) { | ||
$dateTime = new \DateTimeImmutable($dateTime); | ||
} | ||
|
||
if (!$currentDateTime instanceof \DateTimeInterface) { | ||
$currentDateTime = new \DateTimeImmutable($currentDateTime ?? 'now'); | ||
} | ||
|
||
if ($dateTime > $currentDateTime) { | ||
return 'in '.$this->formatInterval($currentDateTime->diff($dateTime)); | ||
} | ||
|
||
if ($dateTime->getTimestamp() === $currentDateTime->getTimestamp()) { | ||
return 'now'; | ||
} | ||
|
||
return $this->formatInterval($currentDateTime->diff($dateTime), $precision).' ago'; | ||
} | ||
|
||
private function format(int $number, string $singular): string | ||
{ | ||
return $number > 1 ? "{$number} {$singular}s" : "{$number} {$singular}"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Intl\DateIntervalFormatter; | ||
|
||
interface DateIntervalFormatterInterface | ||
{ | ||
/** | ||
* @param \DateInterval|string $interval | ||
*/ | ||
public function formatInterval($interval, int $precision = 0): string; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is $precision supposed to mean? reading the interface should provide a clue |
||
|
||
/** | ||
* @param \DateTimeInterface|string $dateTime | ||
* @param \DateTimeInterface|string|null $currentDateTime | ||
*/ | ||
public function formatRelative($dateTime, $currentDateTime = null, int $precision = 0): string; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Intl\Tests\DateIntervalFormatter; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Component\Intl\DateIntervalFormatter\DateIntervalFormatter; | ||
|
||
class DateIntervalFormatterTest extends TestCase | ||
{ | ||
/** | ||
* @dataProvider provideIntervals | ||
*/ | ||
public function testFormatInterval($interval, string $expected, int $precision = 0) | ||
{ | ||
$formatter = new DateIntervalFormatter(); | ||
$this->assertSame($expected, $formatter->formatInterval($interval, $precision)); | ||
} | ||
|
||
public function provideIntervals(): \Generator | ||
{ | ||
yield [new \DateInterval('PT0S'), 'now']; | ||
yield [new \DateInterval('PT1S'), '1 second']; | ||
yield [new \DateInterval('PT10S'), '10 seconds']; | ||
yield [new \DateInterval('PT1M10S'), '1 minute and 10 seconds']; | ||
yield [new \DateInterval('PT10M10S'), '10 minutes and 10 seconds']; | ||
yield [new \DateInterval('PT1H10M10S'), '1 hour, 10 minutes and 10 seconds']; | ||
yield [new \DateInterval('PT10H10M10S'), '10 hours, 10 minutes and 10 seconds']; | ||
yield [new \DateInterval('P1DT10H10M10S'), '1 day, 10 hours, 10 minutes and 10 seconds']; | ||
yield [new \DateInterval('P10DT10H10M10S'), '10 days, 10 hours, 10 minutes and 10 seconds']; | ||
yield [new \DateInterval('P1M10DT10H10M10S'), '1 month, 10 days, 10 hours, 10 minutes and 10 seconds']; | ||
yield [new \DateInterval('P10M10DT10H10M10S'), '10 months, 10 days, 10 hours, 10 minutes and 10 seconds']; | ||
yield [new \DateInterval('P1Y10M10DT10H10M10S'), '1 year, 10 months, 10 days, 10 hours, 10 minutes and 10 seconds']; | ||
yield [new \DateInterval('P10Y10M10DT10H10M10S'), '10 years, 10 months, 10 days, 10 hours, 10 minutes and 10 seconds']; | ||
yield [new \DateInterval('P10Y10M10DT10H10M10S'), '10 years, 10 months and 10 days', 3]; | ||
yield [new \DateInterval('P1Y1DT1S'), '1 year and 1 day', 3]; | ||
yield ['PT1M10S', '1 minute and 10 seconds']; | ||
} | ||
|
||
/** | ||
* @dataProvider provideDates | ||
*/ | ||
public function testFormatDates($dateTime, $currentDateTime, string $expected, int $precision = 0) | ||
{ | ||
$formatter = new DateIntervalFormatter(); | ||
$this->assertSame($expected, $formatter->formatRelative($dateTime, $currentDateTime, $precision)); | ||
} | ||
|
||
public function provideDates(): \Generator | ||
YaFou marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
yield [new \DateTime('2021-01-01'), new \DateTime('2020-01-01'), 'in 1 year']; | ||
yield [new \DateTime('2020-01-01'), new \DateTime('2021-01-01'), '1 year ago']; | ||
yield [new \DateTime('2021-01-01'), new \DateTime('2021-01-01'), 'now']; | ||
yield [new \DateTime('2020-01-01'), new \DateTime('2021-02-02'), '1 year and 1 month ago', 2]; | ||
yield ['2021-01-01', new \DateTime('2020-01-01'), 'in 1 year']; | ||
yield ['2021-01-01', '2020-01-01', 'in 1 year']; | ||
yield ['-1 hour', null, '1 hour ago']; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about doing this in
https://github.com/twigphp/intl-extra/blob/2.x/IntlExtension.php
instead?Currently it may conflict with https://github.com/twigphp/twig-extra-bundle/blob/2.x/Resources/config/intl.php.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think @HeahDude's idea is a good one. Can you please remove that code from this PR and move it to twig instead?