From b20c5ca2efc92a99787340a7dff5455f0ceb38a9 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 13 Dec 2012 18:32:34 +0100 Subject: [PATCH] [Form] Fixed reverse transformation of values in DateTimeToStringTransformer The parts not given in the format are reset to the corresponding values of the UNIX base timestamp. For example, when parsing with the format "Y-m-d", parsing "2012-05-18" now results in the date "2012-05-18 00:00:00 UTC" instead of "2012-05-18 12:58:27 UTC" as before, where the time part corresponded to the local server time. Another example: When parsing with the format "H:i:s", parsing "12:58:27" now results in "1970-01-01 12:58:27 UTC" instead of "2012-12-13 12:58:27 UTC" as before, where again the date part corresponded to the local server time. This behavior is now consistent with DateTimeToArrayTransformer and DateTimeToLocalizedStringTransformer. --- .../DateTimeToLocalizedStringTransformer.php | 12 +- .../DateTimeToStringTransformer.php | 56 ++++-- ...teTimeToLocalizedStringTransformerTest.php | 163 ++++++++---------- .../DateTimeToStringTransformerTest.php | 65 +++---- .../Extension/Core/Type/TimeTypeTest.php | 6 +- 5 files changed, 145 insertions(+), 157 deletions(-) diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php index cc3f9272eb5a..673fd5fde2ef 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php @@ -32,12 +32,12 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer * * @see BaseDateTimeTransformer::formats for available format options * - * @param string $inputTimezone The name of the input timezone - * @param string $outputTimezone The name of the output timezone - * @param integer $dateFormat The date format - * @param integer $timeFormat The time format - * @param \IntlDateFormatter $calendar An \IntlDateFormatter instance - * @param string $pattern A pattern to pass to \IntlDateFormatter + * @param string $inputTimezone The name of the input timezone + * @param string $outputTimezone The name of the output timezone + * @param integer $dateFormat The date format + * @param integer $timeFormat The time format + * @param integer $calendar One of the \IntlDateFormatter calendar constants + * @param string $pattern A pattern to pass to \IntlDateFormatter * * @throws UnexpectedTypeException If a format is not supported * @throws UnexpectedTypeException if a timezone is not a string diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php index a257fd3ce813..41eea327a383 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php @@ -22,7 +22,22 @@ */ class DateTimeToStringTransformer extends BaseDateTimeTransformer { - private $format; + /** + * Format used for generating strings + * @var string + */ + private $generateFormat; + + /** + * Format used for parsing strings + * + * Different than the {@link $generateFormat} because formats for parsing + * support additional characters in PHP that are not supported for + * generating strings. + * + * @var string + */ + private $parseFormat; /** * Transforms a \DateTime instance to a string @@ -39,14 +54,26 @@ public function __construct($inputTimezone = null, $outputTimezone = null, $form { parent::__construct($inputTimezone, $outputTimezone); - $this->format = $format; + $this->generateFormat = $this->parseFormat = $format; + + // See http://php.net/manual/en/datetime.createfromformat.php + // The character "|" in the format makes sure that the parts of a date + // that are *not* specified in the format are reset to the corresponding + // values from 1970-01-01 00:00:00 instead of the current time. + // Without "|" and "Y-m-d", "2010-02-03" becomes "2010-02-03 12:32:47", + // where the time corresponds to the current server time. + // With "|" and "Y-m-d", "2010-02-03" becomes "2010-02-03 00:00:00", + // which is at least deterministic and thus used here. + if (false === strpos($this->parseFormat, '|')) { + $this->parseFormat .= '|'; + } } /** * Transforms a DateTime object into a date string with the configured format * and timezone * - * @param DateTime $value A DateTime object + * @param \DateTime $value A DateTime object * * @return string A value as produced by PHP's date() function * @@ -70,7 +97,7 @@ public function transform($value) throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); } - return $value->format($this->format); + return $value->format($this->generateFormat); } /** @@ -78,7 +105,7 @@ public function transform($value) * * @param string $value A value as produced by PHP's date() function * - * @return \DateTime An instance of \DateTime + * @return \DateTime An instance of \DateTime * * @throws UnexpectedTypeException if the given value is not a string * @throws TransformationFailedException if the date could not be parsed @@ -95,20 +122,25 @@ public function reverseTransform($value) } try { - $dateTime = new \DateTime($value, new \DateTimeZone($this->outputTimezone)); + $outputTz = new \DateTimeZone($this->outputTimezone); + $dateTime = \DateTime::createFromFormat($this->parseFormat, $value, $outputTz); + $lastErrors = \DateTime::getLastErrors(); - if (0 < $lastErrors['warning_count'] || 0 < $lastErrors['error_count']) { - throw new \UnexpectedValueException(implode(', ', array_merge(array_values($lastErrors['warnings']), array_values($lastErrors['errors'])))); - } - // Force value to be in same format as given to transform - if ($value !== $dateTime->format($this->format)) { - $dateTime = new \DateTime($dateTime->format($this->format), new \DateTimeZone($this->outputTimezone)); + if (0 < $lastErrors['warning_count'] || 0 < $lastErrors['error_count']) { + throw new TransformationFailedException( + implode(', ', array_merge( + array_values($lastErrors['warnings']), + array_values($lastErrors['errors']) + )) + ); } if ($this->inputTimezone !== $this->outputTimezone) { $dateTime->setTimeZone(new \DateTimeZone($this->inputTimezone)); } + } catch (TransformationFailedException $e) { + throw $e; } catch (\Exception $e) { throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); } 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 ab0bf2f2d591..f776f5e12dce 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php @@ -34,7 +34,7 @@ protected function tearDown() $this->dateTimeWithoutSeconds = null; } - public static function assertEquals($expected, $actual, $message = '', $delta = 0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE) + public static function assertEquals($expected, $actual, $message = '', $delta = 0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) { if ($expected instanceof \DateTime && $actual instanceof \DateTime) { $expected = $expected->format('c'); @@ -44,54 +44,59 @@ public static function assertEquals($expected, $actual, $message = '', $delta = parent::assertEquals($expected, $actual, $message, $delta, $maxDepth, $canonicalize, $ignoreCase); } - public function testTransformShortDate() - { - $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::SHORT); - $this->assertEquals('03.02.10 04:05', $transformer->transform($this->dateTime)); - } - - public function testTransformMediumDate() - { - $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::MEDIUM); - - $this->assertEquals('03.02.2010 04:05', $transformer->transform($this->dateTime)); - } - - public function testTransformLongDate() - { - $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::LONG); - - $this->assertEquals('03. Februar 2010 04:05', $transformer->transform($this->dateTime)); - } - - public function testTransformFullDate() - { - $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::FULL); - - $this->assertEquals('Mittwoch, 03. Februar 2010 04:05', $transformer->transform($this->dateTime)); + public function dataProvider() + { + return array( + array(\IntlDateFormatter::SHORT, null, null, '03.02.10 04:05', '2010-02-03 04:05:00 UTC'), + array(\IntlDateFormatter::MEDIUM, null, null, '03.02.2010 04:05', '2010-02-03 04:05:00 UTC'), + array(\IntlDateFormatter::LONG, null, null, '03. Februar 2010 04:05', '2010-02-03 04:05:00 UTC'), + array(\IntlDateFormatter::FULL, null, null, 'Mittwoch, 03. Februar 2010 04:05', '2010-02-03 04:05:00 UTC'), + array(\IntlDateFormatter::SHORT, \IntlDateFormatter::NONE, null, '03.02.10', '2010-02-03 00:00:00 UTC'), + array(\IntlDateFormatter::MEDIUM, \IntlDateFormatter::NONE, null, '03.02.2010', '2010-02-03 00:00:00 UTC'), + array(\IntlDateFormatter::LONG, \IntlDateFormatter::NONE, null, '03. Februar 2010', '2010-02-03 00:00:00 UTC'), + array(\IntlDateFormatter::FULL, \IntlDateFormatter::NONE, null, 'Mittwoch, 03. Februar 2010', '2010-02-03 00:00:00 UTC'), + array(null, \IntlDateFormatter::SHORT, null, '03.02.2010 04:05', '2010-02-03 04:05:00 UTC'), + array(null, \IntlDateFormatter::MEDIUM, null, '03.02.2010 04:05:06', '2010-02-03 04:05:06 UTC'), + array(null, \IntlDateFormatter::LONG, null, + '03.02.2010 04:05:06 GMT' . ($this->isLowerThanIcuVersion('4.8') ? '+00:00' : ''), + '2010-02-03 04:05:06 UTC'), + // see below for extra test case for time format FULL + array(\IntlDateFormatter::NONE, \IntlDateFormatter::SHORT, null, '04:05', '1970-01-01 04:05:00 UTC'), + array(\IntlDateFormatter::NONE, \IntlDateFormatter::MEDIUM, null, '04:05:06', '1970-01-01 04:05:06 UTC'), + array(\IntlDateFormatter::NONE, \IntlDateFormatter::LONG, null, + '04:05:06 GMT' . ($this->isLowerThanIcuVersion('4.8') ? '+00:00' : ''), + '1970-01-01 04:05:06 UTC'), + array(null, null, 'yyyy-MM-dd HH:mm:00', '2010-02-03 04:05:00', '2010-02-03 04:05:00 UTC'), + array(null, null, 'yyyy-MM-dd HH:mm', '2010-02-03 04:05', '2010-02-03 04:05:00 UTC'), + array(null, null, 'yyyy-MM-dd HH', '2010-02-03 04', '2010-02-03 04:00:00 UTC'), + array(null, null, 'yyyy-MM-dd', '2010-02-03', '2010-02-03 00:00:00 UTC'), + array(null, null, 'yyyy-MM', '2010-02', '2010-02-01 00:00:00 UTC'), + array(null, null, 'yyyy', '2010', '2010-01-01 00:00:00 UTC'), + array(null, null, 'dd-MM-yyyy', '03-02-2010', '2010-02-03 00:00:00 UTC'), + array(null, null, 'HH:mm:ss', '04:05:06', '1970-01-01 04:05:06 UTC'), + array(null, null, 'HH:mm:00', '04:05:00', '1970-01-01 04:05:00 UTC'), + array(null, null, 'HH:mm', '04:05', '1970-01-01 04:05:00 UTC'), + array(null, null, 'HH', '04', '1970-01-01 04:00:00 UTC'), + ); } - public function testTransformShortTime() - { - $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', null, \IntlDateFormatter::SHORT); - - $this->assertEquals('03.02.2010 04:05', $transformer->transform($this->dateTime)); - } - - public function testTransformMediumTime() - { - $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', null, \IntlDateFormatter::MEDIUM); - - $this->assertEquals('03.02.2010 04:05:06', $transformer->transform($this->dateTime)); - } - - public function testTransformLongTime() + /** + * @dataProvider dataProvider + */ + public function testTransform($dateFormat, $timeFormat, $pattern, $output, $input) { - $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', null, \IntlDateFormatter::LONG); + $transformer = new DateTimeToLocalizedStringTransformer( + 'UTC', + 'UTC', + $dateFormat, + $timeFormat, + \IntlDateFormatter::GREGORIAN, + $pattern + ); - $expected = $this->isLowerThanIcuVersion('4.8') ? '03.02.2010 04:05:06 GMT+00:00' : '03.02.2010 04:05:06 GMT'; + $input = new \DateTime($input); - $this->assertEquals($expected, $transformer->transform($this->dateTime)); + $this->assertEquals($output, $transformer->transform($input)); } public function testTransformFullTime() @@ -143,7 +148,7 @@ public function testTransform_differentPatterns() } /** - * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException + * @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException */ public function testTransformRequiresValidDateTime() { @@ -162,53 +167,23 @@ public function testTransformWrapsIntlErrors() //$transformer->transform(1.5); } - public function testReverseTransformShortDate() - { - $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::SHORT); - - $this->assertDateTimeEquals($this->dateTimeWithoutSeconds, $transformer->reverseTransform('03.02.10 04:05')); - } - - public function testReverseTransformMediumDate() - { - $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::MEDIUM); - - $this->assertDateTimeEquals($this->dateTimeWithoutSeconds, $transformer->reverseTransform('03.02.2010 04:05')); - } - - public function testReverseTransformLongDate() - { - $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::LONG); - - $this->assertDateTimeEquals($this->dateTimeWithoutSeconds, $transformer->reverseTransform('03. Februar 2010 04:05')); - } - - public function testReverseTransformFullDate() - { - $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::FULL); - - $this->assertDateTimeEquals($this->dateTimeWithoutSeconds, $transformer->reverseTransform('Mittwoch, 03. Februar 2010 04:05')); - } - - public function testReverseTransformShortTime() - { - $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', null, \IntlDateFormatter::SHORT); - - $this->assertDateTimeEquals($this->dateTimeWithoutSeconds, $transformer->reverseTransform('03.02.2010 04:05')); - } - - public function testReverseTransformMediumTime() + /** + * @dataProvider dataProvider + */ + public function testReverseTransform($dateFormat, $timeFormat, $pattern, $input, $output) { - $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', null, \IntlDateFormatter::MEDIUM); - - $this->assertDateTimeEquals($this->dateTime, $transformer->reverseTransform('03.02.2010 04:05:06')); - } + $transformer = new DateTimeToLocalizedStringTransformer( + 'UTC', + 'UTC', + $dateFormat, + $timeFormat, + \IntlDateFormatter::GREGORIAN, + $pattern + ); - public function testReverseTransformLongTime() - { - $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', null, \IntlDateFormatter::LONG); + $output = new \DateTime($output); - $this->assertDateTimeEquals($this->dateTime, $transformer->reverseTransform('03.02.2010 04:05:06 GMT+00:00')); + $this->assertEquals($output, $transformer->reverseTransform($input)); } public function testReverseTransformFullTime() @@ -256,7 +231,7 @@ public function testReverseTransform_empty() } /** - * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException + * @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException */ public function testReverseTransformRequiresString() { @@ -265,7 +240,7 @@ public function testReverseTransformRequiresString() } /** - * @expectedException Symfony\Component\Form\Exception\TransformationFailedException + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException */ public function testReverseTransformWrapsIntlErrors() { @@ -274,7 +249,7 @@ public function testReverseTransformWrapsIntlErrors() } /** - * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException + * @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException */ public function testValidateDateFormatOption() { @@ -282,7 +257,7 @@ public function testValidateDateFormatOption() } /** - * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException + * @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException */ public function testValidateTimeFormatOption() { @@ -290,7 +265,7 @@ public function testValidateTimeFormatOption() } /** - * @expectedException Symfony\Component\Form\Exception\TransformationFailedException + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException */ public function testReverseTransformWithNonExistingDate() { diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php index 16fe2b29eda1..893c4c2bfdca 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php @@ -15,30 +15,34 @@ class DateTimeToStringTransformerTest extends DateTimeTestCase { - public function testTransform() + public function dataProvider() { - $transformer = new DateTimeToStringTransformer('UTC', 'UTC', 'Y-m-d H:i:s'); - - $input = new \DateTime('2010-02-03 04:05:06 UTC'); - $output = clone $input; - $output->setTimezone(new \DateTimeZone('UTC')); - $output = $output->format('Y-m-d H:i:s'); - - $this->assertEquals($output, $transformer->transform($input)); + return array( + array('Y-m-d H:i:s', '2010-02-03 04:05:06', '2010-02-03 04:05:06 UTC'), + array('Y-m-d H:i:00', '2010-02-03 04:05:00', '2010-02-03 04:05:00 UTC'), + array('Y-m-d H:i', '2010-02-03 04:05', '2010-02-03 04:05:00 UTC'), + array('Y-m-d H', '2010-02-03 04', '2010-02-03 04:00:00 UTC'), + array('Y-m-d', '2010-02-03', '2010-02-03 00:00:00 UTC'), + array('Y-m', '2010-02', '2010-02-01 00:00:00 UTC'), + array('Y', '2010', '2010-01-01 00:00:00 UTC'), + array('d-m-Y', '03-02-2010', '2010-02-03 00:00:00 UTC'), + array('H:i:s', '04:05:06', '1970-01-01 04:05:06 UTC'), + array('H:i:00', '04:05:00', '1970-01-01 04:05:00 UTC'), + array('H:i', '04:05', '1970-01-01 04:05:00 UTC'), + array('H', '04', '1970-01-01 04:00:00 UTC'), + ); } /** - * @dataProvider getFormatAndDateTime + * @dataProvider dataProvider */ - public function testTransformRandomFormat($format, $datetime) + public function testTransform($format, $output, $input) { $transformer = new DateTimeToStringTransformer('UTC', 'UTC', $format); - $input = new \DateTime($datetime); - $output = clone $input; - $output->setTimezone(new \DateTimeZone('UTC')); + $input = new \DateTime($input); - $this->assertEquals($output->format($format), $transformer->transform($input)); + $this->assertEquals($output, $transformer->transform($input)); } public function testTransform_empty() @@ -68,39 +72,16 @@ public function testTransformExpectsDateTime() $transformer->transform('1234'); } - public function testReverseTransform() - { - $reverseTransformer = new DateTimeToStringTransformer('UTC', 'UTC', 'Y-m-d H:i:s'); - - $output = new \DateTime('2010-02-03 04:05:06 UTC'); - $input = $output->format('Y-m-d H:i:s'); - - $this->assertDateTimeEquals($output, $reverseTransformer->reverseTransform($input)); - } - /** - * @dataProvider getFormatAndDateTime + * @dataProvider dataProvider */ - public function testReverseTransformRandomFormat($format, $datetime) + public function testReverseTransform($format, $input, $output) { $reverseTransformer = new DateTimeToStringTransformer('UTC', 'UTC', $format); - $dateTime = new \DateTime($datetime); - $input = $dateTime->format($format); - - $this->assertDateTimeEquals($dateTime, $reverseTransformer->reverseTransform($input)); - } + $output = new \DateTime($output); - public function getFormatAndDateTime() - { - return array( - array('Y-m-d H:i:s', '2010-02-03 04:05:06 UTC'), - array('Y-m-d H:i:00', '2010-02-03 04:05:00 UTC'), - array('Y-m-d', '2010-02-03 UTC'), - array('d-m-Y', '03-02-2010 UTC'), - array('H:i:s', '04:05:06 UTC'), - array('H:i:00', '04:05:00 UTC'), - ); + $this->assertDateTimeEquals($output, $reverseTransformer->reverseTransform($input)); } public function testReverseTransform_empty() diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php index 5c0d960870bc..1f51a9dc35d1 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php @@ -105,9 +105,9 @@ public function testSubmit_datetimeSingleText() 'widget' => 'single_text', )); - $form->bind('03:04:05'); + $form->bind('03:04'); - $this->assertEquals(new \DateTime('03:04:00 UTC'), $form->getData()); + $this->assertEquals(new \DateTime('1970-01-01 03:04:00 UTC'), $form->getData()); $this->assertEquals('03:04', $form->getViewData()); } @@ -162,7 +162,7 @@ public function testSubmit_stringSingleText() 'widget' => 'single_text', )); - $form->bind('03:04:05'); + $form->bind('03:04'); $this->assertEquals('03:04:00', $form->getData()); $this->assertEquals('03:04', $form->getViewData());