From c951bb6e970a41bcd2dd81a70558f560c0199659 Mon Sep 17 00:00:00 2001 From: Matteo Beccati Date: Fri, 5 Aug 2016 12:25:39 +0200 Subject: [PATCH] Fix #19531 [Form] DateType fails parsing when midnight is not a valid time --- .../DateTimeToLocalizedStringTransformer.php | 43 ++++++++++++++++--- ...teTimeToLocalizedStringTransformerTest.php | 20 +++++++++ 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php index cac50e16c7..352b7c85ff 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php @@ -117,20 +117,29 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer return; } - $timestamp = $this->getIntlDateFormatter()->parse($value); + // date-only patterns require parsing to be done in UTC, as midnight might not exist in the local timezone due + // to DST changes + $dateOnly = $this->isPatternDateOnly(); + + $timestamp = $this->getIntlDateFormatter($dateOnly)->parse($value); if (intl_get_error_code() != 0) { throw new TransformationFailedException(intl_get_error_message()); } try { - // read timestamp into DateTime object - the formatter delivers in UTC - $dateTime = new \DateTime(sprintf('@%s', $timestamp)); + if ($dateOnly) { + // we only care about year-month-date, which has been delivered as a timestamp pointing to UTC midnight + return new \DateTime(gmdate('Y-m-d', $timestamp), new \DateTimeZone($this->inputTimezone)); + } + + // read timestamp into DateTime object - the formatter delivers a timestamp + $dateTime = new \DateTime(sprintf('@%s', $timestamp), new \DateTimeZone($this->outputTimezone)); } catch (\Exception $e) { throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); } - if ('UTC' !== $this->inputTimezone) { + if ($this->outputTimezone !== $this->inputTimezone) { $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); } @@ -140,15 +149,17 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer /** * Returns a preconfigured IntlDateFormatter instance. * + * @param bool $ignoreTimezone Use UTC regardless of the configured timezone. + * * @return \IntlDateFormatter * * @throws TransformationFailedException in case the date formatter can not be constructed. */ - protected function getIntlDateFormatter() + protected function getIntlDateFormatter($ignoreTimezone = false) { $dateFormat = $this->dateFormat; $timeFormat = $this->timeFormat; - $timezone = $this->outputTimezone; + $timezone = $ignoreTimezone ? 'UTC' : $this->outputTimezone; $calendar = $this->calendar; $pattern = $this->pattern; @@ -163,4 +174,24 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer return $intlDateFormatter; } + + /** + * Checks if the pattern contains only a date. + * + * @param string $pattern The input pattern + * + * @return bool + */ + protected function isPatternDateOnly() + { + if (null === $this->pattern) { + return false; + } + + // strip escaped text + $pattern = preg_replace("#'(.*?)'#", '', $this->pattern); + + // check for the absence of time-related placeholders + return 0 === preg_match('#[ahHkKmsSAzZOvVxX]#', $pattern); + } } 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 96dd4a3cd1..444cdd4582 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php @@ -230,6 +230,26 @@ class DateTimeToLocalizedStringTransformerTest extends DateTimeTestCase $this->assertDateTimeEquals($this->dateTime, $transformer->reverseTransform('02*2010*03 04|05|06')); } + public function testReverseTransformDateOnlyWithDstIssue() + { + $transformer = new DateTimeToLocalizedStringTransformer('Europe/Rome', 'Europe/Rome', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, \IntlDateFormatter::GREGORIAN, 'dd/MM/yyyy'); + + $this->assertDateTimeEquals( + new \DateTime('1978-05-28', new \DateTimeZone('Europe/Rome')), + $transformer->reverseTransform('28/05/1978') + ); + } + + public function testReverseTransformDateOnlyWithDstIssueAndEscapedText() + { + $transformer = new DateTimeToLocalizedStringTransformer('Europe/Rome', 'Europe/Rome', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, \IntlDateFormatter::GREGORIAN, "'day': dd 'month': MM 'year': yyyy"); + + $this->assertDateTimeEquals( + new \DateTime('1978-05-28', new \DateTimeZone('Europe/Rome')), + $transformer->reverseTransform('day: 28 month: 05 year: 1978') + ); + } + public function testReverseTransformEmpty() { $transformer = new DateTimeToLocalizedStringTransformer();