From fca95c5dd84141f62bb19d88bbb5bd0663710746 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Fri, 9 Aug 2024 09:46:47 +0200 Subject: [PATCH] [Form] Add support for the `calendar` option in `DateType` --- src/Symfony/Component/Form/CHANGELOG.md | 1 + .../DateTimeToLocalizedStringTransformer.php | 14 ++--- .../Form/Extension/Core/Type/DateType.php | 6 ++- ...teTimeToLocalizedStringTransformerTest.php | 54 +++++++++++++++++++ .../Extension/Core/Type/DateTypeTest.php | 34 ++++++++++++ 5 files changed, 101 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index 631041da40e89..6ad9ab93f1c41 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Deprecate the `VersionAwareTest` trait, use feature detection instead + * Add support for the `calendar` option in `DateType` 7.1 --- diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php index a93803cd8b3ae..5e93d289d25c8 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php @@ -30,12 +30,12 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer /** * @see BaseDateTimeTransformer::formats for available format options * - * @param string|null $inputTimezone The name of the input timezone - * @param string|null $outputTimezone The name of the output timezone - * @param int|null $dateFormat The date format - * @param int|null $timeFormat The time format - * @param int $calendar One of the \IntlDateFormatter calendar constants - * @param string|null $pattern A pattern to pass to \IntlDateFormatter + * @param string|null $inputTimezone The name of the input timezone + * @param string|null $outputTimezone The name of the output timezone + * @param int|null $dateFormat The date format + * @param int|null $timeFormat The time format + * @param int|\IntlCalendar $calendar One of the \IntlDateFormatter calendar constants or an \IntlCalendar instance + * @param string|null $pattern A pattern to pass to \IntlDateFormatter * * @throws UnexpectedTypeException If a format is not supported or if a timezone is not a string */ @@ -44,7 +44,7 @@ public function __construct( ?string $outputTimezone = null, ?int $dateFormat = null, ?int $timeFormat = null, - private int $calendar = \IntlDateFormatter::GREGORIAN, + private int|\IntlCalendar $calendar = \IntlDateFormatter::GREGORIAN, private ?string $pattern = null, ) { parent::__construct($inputTimezone, $outputTimezone); diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php index 773a51cbdd9b7..d30946d7c9eaf 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -49,7 +49,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void { $dateFormat = \is_int($options['format']) ? $options['format'] : self::DEFAULT_FORMAT; $timeFormat = \IntlDateFormatter::NONE; - $calendar = \IntlDateFormatter::GREGORIAN; + $calendar = $options['calendar'] ?? \IntlDateFormatter::GREGORIAN; $pattern = \is_string($options['format']) ? $options['format'] : ''; if (!\in_array($dateFormat, self::ACCEPTED_FORMATS, true)) { @@ -281,6 +281,7 @@ public function configureOptions(OptionsResolver $resolver): void 'format' => $format, 'model_timezone' => null, 'view_timezone' => null, + 'calendar' => null, 'placeholder' => $placeholderDefault, 'html5' => true, // Don't modify \DateTime classes by reference, we treat @@ -320,6 +321,9 @@ public function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('months', 'array'); $resolver->setAllowedTypes('days', 'array'); $resolver->setAllowedTypes('input_format', 'string'); + $resolver->setAllowedTypes('calendar', ['null', \IntlCalendar::class]); + + $resolver->setInfo('calendar', 'The calendar to use for formatting and parsing the date. The value should be one of the \IntlDateFormatter calendar constants or an instance of the \IntlCalendar to use.'); $resolver->setNormalizer('html5', static function (Options $options, $html5) { if ($html5 && 'single_text' === $options['widget'] && self::HTML5_FORMAT !== $options['format']) { diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php index c7918ae8b139e..0cf8b22f6c06c 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php @@ -392,6 +392,60 @@ public function testReverseTransformWrapsIntlErrorsWithExceptionsAndErrorLevel() } } + public function testTransformDateTimeWithCustomCalendar() + { + $dateTime = new \DateTimeImmutable('2024-03-31'); + + $weekBeginsOnSunday = \IntlCalendar::createInstance(); + $weekBeginsOnSunday->setFirstDayOfWeek(\IntlCalendar::DOW_SUNDAY); + + $this->assertSame( + '2024-03-31 2024w14', + (new DateTimeToLocalizedStringTransformer(calendar: $weekBeginsOnSunday, pattern: "y-MM-dd y'w'w"))->transform($dateTime), + ); + + $weekBeginsOnMonday = \IntlCalendar::createInstance(); + $weekBeginsOnMonday->setFirstDayOfWeek(\IntlCalendar::DOW_MONDAY); + + $this->assertSame( + '2024-03-31 2024w13', + (new DateTimeToLocalizedStringTransformer(calendar: $weekBeginsOnMonday, pattern: "y-MM-dd y'w'w"))->transform($dateTime), + ); + } + + public function testReverseTransformDateTimeWithCustomCalendar() + { + $weekBeginsOnSunday = \IntlCalendar::createInstance(); + $weekBeginsOnSunday->setFirstDayOfWeek(\IntlCalendar::DOW_SUNDAY); + + $this->assertSame( + '2024-03-31', + (new DateTimeToLocalizedStringTransformer(calendar: $weekBeginsOnSunday, pattern: "y-MM-dd y'w'w")) + ->reverseTransform('2024-03-31 2024w14') + ->format('Y-m-d'), + ); + + $weekBeginsOnMonday = \IntlCalendar::createInstance(); + $weekBeginsOnMonday->setFirstDayOfWeek(\IntlCalendar::DOW_MONDAY); + + $this->assertSame( + '2024-03-31', + (new DateTimeToLocalizedStringTransformer(calendar: $weekBeginsOnMonday, pattern: "y-MM-dd y'w'w")) + ->reverseTransform('2024-03-31 2024w13') + ->format('Y-m-d'), + ); + } + + public function testDefaultCalendarIsGregorian() + { + $now = new \DateTimeImmutable(); + + $this->assertSame( + (new DateTimeToLocalizedStringTransformer(calendar: \IntlDateFormatter::GREGORIAN, pattern: "y-MM-dd y'w'w"))->transform($now), + (new DateTimeToLocalizedStringTransformer(pattern: "y-MM-dd y'w'w"))->transform($now), + ); + } + protected function createDateTimeTransformer(?string $inputTimezone = null, ?string $outputTimezone = null): BaseDateTimeTransformer { return new DateTimeToLocalizedStringTransformer($inputTimezone, $outputTimezone); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php index dfbae933092da..61787512ae9b3 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php @@ -1156,6 +1156,40 @@ public function testDateTimeImmutableInputTimezoneNotMatchingModelTimezone() ]); } + public function testSubmitWithCustomCalendarOption() + { + // Creates a new form using the "roc" (Republic Of China) calendar. This calendar starts in 1912, the year 2024 in + // the Gregorian calendar is the year 113 in the "roc" calendar. + $form = $this->factory->create(static::TESTED_TYPE, options: [ + 'format' => 'y-MM-dd', + 'html5' => false, + 'input' => 'array', + 'calendar' => \IntlCalendar::createInstance(locale: 'zh_TW@calendar=roc'), + ]); + $form->submit('113-03-31'); + + $this->assertSame('2024', $form->getData()['year'], 'The year should be converted to the default locale (en)'); + $this->assertSame('31', $form->getData()['day']); + $this->assertSame('3', $form->getData()['month']); + + $this->assertSame('113-03-31', $form->getViewData()); + } + + public function testSetDataWithCustomCalendarOption() + { + // Creates a new form using the "roc" (Republic Of China) calendar. This calendar starts in 1912, the year 2024 in + // the Gregorian calendar is the year 113 in the "roc" calendar. + $form = $this->factory->create(static::TESTED_TYPE, options: [ + 'format' => 'y-MM-dd', + 'html5' => false, + 'input' => 'array', + 'calendar' => \IntlCalendar::createInstance(locale: 'zh_TW@calendar=roc'), + ]); + $form->setData(['year' => '2024', 'month' => '3', 'day' => '31']); + + $this->assertSame('113-03-31', $form->getViewData()); + } + protected function getTestOptions(): array { return ['widget' => 'choice'];