Skip to content

Commit

Permalink
bug #3664 Translated date formats into current locale (javiereguiluz)
Browse files Browse the repository at this point in the history
This PR was squashed before being merged into the 3.0.x-dev branch.

Discussion
----------

Translated date formats into current locale

Fixes #3634 and replaces #3636.

Commits
-------

ec746c3 Translated date formats into current locale
  • Loading branch information
javiereguiluz committed Sep 5, 2020
2 parents bb567b1 + ec746c3 commit 7816d3e
Show file tree
Hide file tree
Showing 12 changed files with 581 additions and 33 deletions.
3 changes: 2 additions & 1 deletion doc/crud.rst
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ Date, Time and Number Formatting Options
public function configureCrud(Crud $crud): Crud
{
return $crud
// the argument must be either one of these strings: 'short', 'medium', 'long', 'full'
// the argument must be either one of these strings: 'short', 'medium', 'long', 'full', 'none'
// (the strings are also available as \EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField::FORMAT_* constants)
// or a valid ICU Datetime Pattern (see http://userguide.icu-project.org/formatparse/datetime)
->setDateFormat('...')
->setTimeFormat('...')
Expand Down
3 changes: 1 addition & 2 deletions src/Config/Crud.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,7 @@ public function setDateTimeFormat(string $dateFormatOrPattern, string $timeForma
throw new \InvalidArgumentException(sprintf('The value of the time format can only be one of the following: %s (but "%s" was given).', implode(', ', DateTimeField::VALID_DATE_FORMATS), $timeFormat));
}

$dateTimePattern = $isDatePattern ? $dateFormatOrPattern : trim(sprintf('%s %s', DateTimeField::INTL_DATE_PATTERNS[$dateFormatOrPattern], DateTimeField::INTL_TIME_PATTERNS[$timeFormat]));
$this->dto->setDateTimePattern($dateTimePattern);
$this->dto->setDateTimePattern($dateFormatOrPattern, $timeFormat);

return $this;
}
Expand Down
13 changes: 7 additions & 6 deletions src/Dto/CrudDto.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Config\KeyValueStore;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;

/**
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
Expand Down Expand Up @@ -48,9 +49,9 @@ public function __construct()
{
$this->customPageTitles = [Crud::PAGE_DETAIL => null, Crud::PAGE_EDIT => null, Crud::PAGE_INDEX => null, Crud::PAGE_NEW => null];
$this->helpMessages = [Crud::PAGE_DETAIL => null, Crud::PAGE_EDIT => null, Crud::PAGE_INDEX => null, Crud::PAGE_NEW => null];
$this->datePattern = 'MMM d, y';
$this->timePattern = 'h:mm:ss a';
$this->dateTimePattern = 'MMM d, y h:mm:ss a';
$this->datePattern = 'medium';
$this->timePattern = 'medium';
$this->dateTimePattern = ['medium', 'medium'];
$this->dateIntervalFormat = '%%y Year(s) %%m Month(s) %%d Day(s)';
$this->defaultSort = [];
$this->searchFields = [];
Expand Down Expand Up @@ -161,14 +162,14 @@ public function setTimePattern(?string $format): void
$this->timePattern = $format;
}

public function getDateTimePattern(): string
public function getDateTimePattern(): array
{
return $this->dateTimePattern;
}

public function setDateTimePattern(string $pattern): void
public function setDateTimePattern(string $dateFormatOrPattern, string $timeFormat = DateTimeField::FORMAT_NONE): void
{
$this->dateTimePattern = $pattern;
$this->dateTimePattern = [$dateFormatOrPattern, $timeFormat];
}

public function getDateIntervalFormat(): string
Expand Down
40 changes: 31 additions & 9 deletions src/Field/Configurator/DateTimeConfigurator.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,41 @@ public function configure(FieldDto $field, EntityDto $entityDto, AdminContext $c
$defaultTimezone = $crud->getTimezone();
$timezone = $field->getCustomOption(DateTimeField::OPTION_TIMEZONE) ?? $defaultTimezone;

$dateFormat = null;
$timeFormat = null;
$icuDateTimePattern = '';
$formattedValue = $field->getValue();

if (DateTimeField::class === $field->getFieldFqcn()) {
$defaultDateTimePattern = $crud->getDateTimePattern();
$dateTimePattern = $field->getCustomOption(DateTimeField::OPTION_DATETIME_PATTERN) ?? $defaultDateTimePattern;
$formattedValue = $this->intlFormatter->formatDateTime($field->getValue(), null, null, $dateTimePattern, $timezone);
[$defaultDatePattern, $defaultTimePattern] = $crud->getDateTimePattern();
$datePattern = $field->getCustomOption(DateTimeField::OPTION_DATE_PATTERN) ?? $defaultDatePattern;
$timePattern = $field->getCustomOption(DateTimeField::OPTION_TIME_PATTERN) ?? $defaultTimePattern;
if (\in_array($datePattern, DateTimeField::VALID_DATE_FORMATS, true)) {
$dateFormat = $datePattern;
$timeFormat = $timePattern;
} else {
$icuDateTimePattern = $datePattern;
}

$formattedValue = $this->intlFormatter->formatDateTime($field->getValue(), $dateFormat, $timeFormat, $icuDateTimePattern, $timezone);
} elseif (DateField::class === $field->getFieldFqcn()) {
$defaultDatePattern = $crud->getDatePattern();
$datePattern = $field->getCustomOption(DateField::OPTION_DATE_PATTERN) ?? $defaultDatePattern;
$formattedValue = $this->intlFormatter->formatDate($field->getValue(), null, $datePattern, $timezone);
$dateFormatOrPattern = $field->getCustomOption(DateField::OPTION_DATE_PATTERN) ?? $crud->getDatePattern();
if (\in_array($dateFormatOrPattern, DateTimeField::VALID_DATE_FORMATS, true)) {
$dateFormat = $dateFormatOrPattern;
} else {
$icuDateTimePattern = $dateFormatOrPattern;
}

$formattedValue = $this->intlFormatter->formatDate($field->getValue(), $dateFormat, $icuDateTimePattern, $timezone);
} elseif (TimeField::class === $field->getFieldFqcn()) {
$defaultTimePattern = $crud->getTimePattern();
$timePattern = $field->getCustomOption(TimeField::OPTION_TIME_PATTERN) ?? $defaultTimePattern;
$formattedValue = $this->intlFormatter->formatTime($field->getValue(), null, $timePattern, $timezone);
$timeFormatOrPattern = $field->getCustomOption(TimeField::OPTION_TIME_PATTERN) ?? $crud->getTimePattern();
if (\in_array($timeFormatOrPattern, DateTimeField::VALID_DATE_FORMATS, true)) {
$timeFormat = $timeFormatOrPattern;
} else {
$icuDateTimePattern = $timeFormatOrPattern;
}

$formattedValue = $this->intlFormatter->formatTime($field->getValue(), $timeFormat, $icuDateTimePattern, $timezone);
}

$widgetOption = $field->getCustomOption(DateTimeField::OPTION_WIDGET);
Expand Down
5 changes: 2 additions & 3 deletions src/Field/DateField.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,10 @@ public function setFormat(string $dateFormatOrPattern): self
return DateTimeField::FORMAT_NONE !== $format;
});

throw new \InvalidArgumentException(sprintf('The first argument of the "%s()" method cannot be "%s" or an empty string. Use either the special date formats (%s) or a datetime Intl pattern.', __METHOD__, DateTimeField::FORMAT_NONE, implode(', ', $validDateFormatsWithoutNone)));
throw new \InvalidArgumentException(sprintf('The argument of the "%s()" method cannot be "%s" or an empty string. Use either the special date formats (%s) or a datetime Intl pattern.', __METHOD__, DateTimeField::FORMAT_NONE, implode(', ', $validDateFormatsWithoutNone)));
}

$datePattern = DateTimeField::INTL_DATE_PATTERNS[$dateFormatOrPattern] ?? $dateFormatOrPattern;
$this->setCustomOption(self::OPTION_DATE_PATTERN, $datePattern);
$this->setCustomOption(self::OPTION_DATE_PATTERN, $dateFormatOrPattern);

return $this;
}
Expand Down
10 changes: 6 additions & 4 deletions src/Field/DateTimeField.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ final class DateTimeField implements FieldInterface
public const WIDGET_CHOICE = 'choice';
public const WIDGET_TEXT = 'text';

public const OPTION_DATETIME_PATTERN = 'dateTimePattern';
public const OPTION_DATE_PATTERN = 'datePattern';
public const OPTION_TIME_PATTERN = 'timePattern';
public const OPTION_TIMEZONE = 'timezone';
public const OPTION_WIDGET = 'widget';

Expand All @@ -53,7 +54,8 @@ public static function new(string $propertyName, ?string $label = null): self
->setFormType(DateTimeType::class)
->addCssClass('field-datetime')
// the proper default values of these options are set on the Crud class
->setCustomOption(self::OPTION_DATETIME_PATTERN, null)
->setCustomOption(self::OPTION_DATE_PATTERN, null)
->setCustomOption(self::OPTION_TIME_PATTERN, null)
->setCustomOption(self::OPTION_TIMEZONE, null)
->setCustomOption(self::OPTION_WIDGET, self::WIDGET_NATIVE);
}
Expand Down Expand Up @@ -105,8 +107,8 @@ public function setFormat(string $dateFormatOrPattern, string $timeFormat = self
throw new \InvalidArgumentException(sprintf('The value of the time format can only be one of the following: %s (but "%s" was given).', implode(', ', self::VALID_DATE_FORMATS), $timeFormat));
}

$dateTimePattern = $isDatePattern ? $dateFormatOrPattern : trim(sprintf('%s %s', self::INTL_DATE_PATTERNS[$dateFormatOrPattern], self::INTL_TIME_PATTERNS[$timeFormat]));
$this->setCustomOption(self::OPTION_DATETIME_PATTERN, $dateTimePattern);
$this->setCustomOption(self::OPTION_DATE_PATTERN, $dateFormatOrPattern);
$this->setCustomOption(self::OPTION_TIME_PATTERN, $timeFormat);

return $this;
}
Expand Down
3 changes: 1 addition & 2 deletions src/Field/TimeField.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@ public function setFormat(string $timeFormatOrPattern): self
throw new \InvalidArgumentException(sprintf('The first argument of the "%s()" method cannot be "%s" or an empty string. Use either the special time formats (%s) or a datetime Intl pattern.', __METHOD__, DateTimeField::FORMAT_NONE, implode(', ', $validTimeFormatsWithoutNone)));
}

$timePattern = DateTimeField::INTL_TIME_PATTERNS[$timeFormatOrPattern] ?? $timeFormatOrPattern;
$this->setCustomOption(self::OPTION_TIME_PATTERN, $timePattern);
$this->setCustomOption(self::OPTION_TIME_PATTERN, $timeFormatOrPattern);

return $this;
}
Expand Down
7 changes: 2 additions & 5 deletions src/Intl/IntlFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ public function formatNumber($number, array $attrs = [], string $style = 'decima
}

/**
* @param \DateTimeInterface|string|null $date A date or null to use the current time
* @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged
*/
public function formatDateTime(?\DateTimeInterface $date, ?string $dateFormat = 'medium', ?string $timeFormat = 'medium', string $pattern = '', $timezone = null, string $calendar = 'gregorian', string $locale = null): ?string
Expand All @@ -149,19 +148,17 @@ public function formatDateTime(?\DateTimeInterface $date, ?string $dateFormat =
}

/**
* @param \DateTimeInterface|string|null $date A date or null to use the current time
* @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged
*/
public function formatDate($date, ?string $dateFormat = 'medium', string $pattern = '', $timezone = null, string $calendar = 'gregorian', string $locale = null): ?string
public function formatDate(?\DateTimeInterface $date, ?string $dateFormat = 'medium', string $pattern = '', $timezone = null, string $calendar = 'gregorian', string $locale = null): ?string
{
return $this->formatDateTime($date, $dateFormat, 'none', $pattern, $timezone, $calendar, $locale);
}

/**
* @param \DateTimeInterface|string|null $date A date or null to use the current time
* @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged
*/
public function formatTime($date, ?string $timeFormat = 'medium', string $pattern = '', $timezone = null, string $calendar = 'gregorian', string $locale = null): ?string
public function formatTime(?\DateTimeInterface $date, ?string $timeFormat = 'medium', string $pattern = '', $timezone = null, string $calendar = 'gregorian', string $locale = null): ?string
{
return $this->formatDateTime($date, 'none', $timeFormat, $pattern, $timezone, $calendar, $locale);
}
Expand Down
6 changes: 5 additions & 1 deletion tests/Field/AbstractFieldTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use EasyCorp\Bundle\EasyAdminBundle\Dto\I18nDto;
use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField;
use EasyCorp\Bundle\EasyAdminBundle\Field\Configurator\ChoiceConfigurator;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class AbstractFieldTest extends KernelTestCase
Expand All @@ -27,9 +28,12 @@ protected function setUp(): void

$crudMock = $this->getMockBuilder(CrudDto::class)
->disableOriginalConstructor()
->setMethods(['getCurrentPage'])
->setMethods(['getCurrentPage', 'getDatePattern', 'getDateTimePattern', 'getTimePattern'])
->getMock();
$crudMock->method('getCurrentPage')->willReturn(Crud::PAGE_INDEX);
$crudMock->method('getDatePattern')->willReturn(DateTimeField::FORMAT_MEDIUM);
$crudMock->method('getTimePattern')->willReturn(DateTimeField::FORMAT_MEDIUM);
$crudMock->method('getDateTimePattern')->willReturn([DateTimeField::FORMAT_MEDIUM, DateTimeField::FORMAT_MEDIUM]);

$i18nMock = $this->getMockBuilder(I18nDto::class)
->disableOriginalConstructor()
Expand Down
168 changes: 168 additions & 0 deletions tests/Field/DateFieldTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
<?php

namespace EasyCorp\Bundle\EasyAdminBundle\Tests\Field;

use EasyCorp\Bundle\EasyAdminBundle\Field\Configurator\DateTimeConfigurator;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Intl\IntlFormatter;

class DateFieldTest extends AbstractFieldTest
{
protected function setUp(): void
{
parent::setUp();

$intlFormatterMock = $this->getMockBuilder(IntlFormatter::class)
->disableOriginalConstructor()
->setMethods(['formatDate'])
->getMock();
$intlFormatterMock->method('formatDate')->willReturnCallback(
static function ($value, ?string $dateFormat = 'medium', string $pattern = '', $timezone = null, string $calendar = 'gregorian', string $locale = null) { return sprintf('value: %s | dateFormat: %s | pattern: %s | timezone: %s | calendar: %s | locale: %s', $value instanceof \DateTimeInterface ? $value->format('Y-m-d H:i:s') : $value, $dateFormat, $pattern, $timezone, $calendar, $locale); }
);

$this->configurator = new DateTimeConfigurator($intlFormatterMock);
}

public function testFieldWithWrongTimezone()
{
$this->expectException(\InvalidArgumentException::class);

$field = DateField::new('foo');
$field->setTimezone('this-timezone-does-not-exist');
}

public function testFieldWithoutTimezone()
{
$field = DateField::new('foo');
$field->setFieldFqcn(DateField::class);
$fieldDto = $this->configure($field);

$this->assertNull($fieldDto->getCustomOption(DateTimeField::OPTION_TIMEZONE));
}

public function testFieldWithTimezone()
{
$field = DateField::new('foo');
$field->setFieldFqcn(DateField::class);
$field->setTimezone('Europe/Madrid');
$fieldDto = $this->configure($field);

$this->assertSame('Europe/Madrid', $fieldDto->getCustomOption(DateTimeField::OPTION_TIMEZONE));
}

public function testFieldWithWrongFormat()
{
$this->expectException(\InvalidArgumentException::class);

$field = DateField::new('foo');
$field->setFieldFqcn(DateField::class);
$field->setFormat(DateTimeField::FORMAT_NONE);
}

public function testFieldWithEmptyFormat()
{
$this->expectException(\InvalidArgumentException::class);

$field = DateField::new('foo');
$field->setFieldFqcn(DateField::class);
$field->setFormat('');
}

public function testFieldWithPredefinedFormat()
{
$field = DateField::new('foo')->setValue(new \DateTime('2006-01-02 15:04:05'));
$field->setFieldFqcn(DateField::class);
$field->setFormat(DateTimeField::FORMAT_LONG);
$fieldDto = $this->configure($field);

$this->assertSame(DateTimeField::FORMAT_LONG, $fieldDto->getCustomOption(DateField::OPTION_DATE_PATTERN));
$this->assertSame('value: 2006-01-02 15:04:05 | dateFormat: long | pattern: | timezone: | calendar: gregorian | locale: ', $fieldDto->getFormattedValue());
}

public function testFieldWithCustomPattern()
{
$field = DateField::new('foo')->setValue(new \DateTime('2006-01-02 15:04:05'));
$field->setFieldFqcn(DateField::class);
$field->setFormat('HH:mm:ss ZZZZ a');
$fieldDto = $this->configure($field);

$this->assertSame('HH:mm:ss ZZZZ a', $fieldDto->getCustomOption(DateField::OPTION_DATE_PATTERN));
$this->assertSame('value: 2006-01-02 15:04:05 | dateFormat: | pattern: HH:mm:ss ZZZZ a | timezone: | calendar: gregorian | locale: ', $fieldDto->getFormattedValue());
}

public function testFieldDefaultWidget()
{
$field = DateField::new('foo');
$field->setFieldFqcn(DateField::class);
$fieldDto = $this->configure($field);

$this->assertSame(DateTimeField::WIDGET_NATIVE, $fieldDto->getCustomOption(DateField::OPTION_WIDGET));
}

public function testFieldRenderAsNativeWidget()
{
$field = DateField::new('foo');
$field->setFieldFqcn(DateField::class);
$field->renderAsNativeWidget();
$fieldDto = $this->configure($field);

$this->assertSame(DateTimeField::WIDGET_NATIVE, $fieldDto->getCustomOption(DateField::OPTION_WIDGET));
$this->assertSame('single_text', $fieldDto->getFormTypeOption('widget'));
$this->assertTrue($fieldDto->getFormTypeOption('html5'));
}

public function testFieldRenderAsNotNativeWidget()
{
$field = DateField::new('foo');
$field->setFieldFqcn(DateField::class);
$field->renderAsNativeWidget(false);
$fieldDto = $this->configure($field);

$this->assertSame(DateTimeField::WIDGET_CHOICE, $fieldDto->getCustomOption(DateField::OPTION_WIDGET));
}

public function testFieldRenderAsChoice()
{
$field = DateField::new('foo');
$field->setFieldFqcn(DateField::class);
$field->renderAsChoice();
$fieldDto = $this->configure($field);

$this->assertSame(DateTimeField::WIDGET_CHOICE, $fieldDto->getCustomOption(DateField::OPTION_WIDGET));
$this->assertSame('choice', $fieldDto->getFormTypeOption('widget'));
$this->assertTrue($fieldDto->getFormTypeOption('html5'));
}

public function testFieldRenderAsNotChoice()
{
$field = DateField::new('foo');
$field->setFieldFqcn(DateField::class);
$field->renderAsChoice(false);
$fieldDto = $this->configure($field);

$this->assertSame(DateTimeField::WIDGET_NATIVE, $fieldDto->getCustomOption(DateField::OPTION_WIDGET));
}

public function testFieldRenderAsText()
{
$field = DateField::new('foo');
$field->setFieldFqcn(DateField::class);
$field->renderAsText();
$fieldDto = $this->configure($field);

$this->assertSame(DateTimeField::WIDGET_TEXT, $fieldDto->getCustomOption(DateField::OPTION_WIDGET));
$this->assertSame('single_text', $fieldDto->getFormTypeOption('widget'));
$this->assertFalse($fieldDto->getFormTypeOption('html5'));
}

public function testFieldRenderAsNotText()
{
$field = DateField::new('foo');
$field->setFieldFqcn(DateField::class);
$field->renderAsText(false);
$fieldDto = $this->configure($field);

$this->assertSame(DateTimeField::WIDGET_NATIVE, $fieldDto->getCustomOption(DateField::OPTION_WIDGET));
}
}

0 comments on commit 7816d3e

Please sign in to comment.