diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php index 41eea327a3..cbaac96859 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php @@ -39,23 +39,38 @@ class DateTimeToStringTransformer extends BaseDateTimeTransformer */ private $parseFormat; + /** + * Whether to parse by appending a pipe "|" to the parse format. + * + * This only works as of PHP 5.3.8. + * + * @var Boolean + */ + private $parseUsingPipe; + /** * Transforms a \DateTime instance to a string * * @see \DateTime::format() for supported formats * - * @param string $inputTimezone The name of the input timezone - * @param string $outputTimezone The name of the output timezone - * @param string $format The date format + * @param string $inputTimezone The name of the input timezone + * @param string $outputTimezone The name of the output timezone + * @param string $format The date format + * @param Boolean $parseUsingPipe Whether to parse by appending a pipe "|" to the parse format * * @throws UnexpectedTypeException if a timezone is not a string */ - public function __construct($inputTimezone = null, $outputTimezone = null, $format = 'Y-m-d H:i:s') + public function __construct($inputTimezone = null, $outputTimezone = null, $format = 'Y-m-d H:i:s', $parseUsingPipe = null) { parent::__construct($inputTimezone, $outputTimezone); $this->generateFormat = $this->parseFormat = $format; + // The pipe in the parser pattern only works as of PHP 5.3.8 + $this->parseUsingPipe = null === $parseUsingPipe + ? version_compare(phpversion(), '5.3.8', '>=') + : $parseUsingPipe; + // 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 @@ -64,7 +79,7 @@ class DateTimeToStringTransformer extends BaseDateTimeTransformer // 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, '|')) { + if ($this->parseUsingPipe && false === strpos($this->parseFormat, '|')) { $this->parseFormat .= '|'; } } @@ -125,6 +140,70 @@ class DateTimeToStringTransformer extends BaseDateTimeTransformer $outputTz = new \DateTimeZone($this->outputTimezone); $dateTime = \DateTime::createFromFormat($this->parseFormat, $value, $outputTz); + // On PHP versions < 5.3.8 we need to emulate the pipe operator + // and reset parts not given in the format to their equivalent + // of the UNIX base timestamp. + if (!$this->parseUsingPipe) { + list($year, $month, $day, $hour, $minute, $second) = explode('-', $dateTime->format('Y-m-d-H-i-s')); + + // Check which of the date parts are present in the pattern + preg_match( + '/(' . + '(?[djDl])|' . + '(?[FMmn])|' . + '(?[Yy])|' . + '(?[ghGH])|' . + '(?i)|' . + '(?s)|' . + '(?z)|' . + '(?U)|' . + '[^djDlFMmnYyghGHiszU]' . + ')*/', + $this->parseFormat, + $matches + ); + + // preg_match() does not guarantee to set all indices, so + // set them unless given + $matches = array_merge(array( + 'day' => false, + 'month' => false, + 'year' => false, + 'hour' => false, + 'minute' => false, + 'second' => false, + 'dayofyear' => false, + 'timestamp' => false, + ), $matches); + + // Reset all parts that don't exist in the format to the + // corresponding part of the UNIX base timestamp + if (!$matches['timestamp']) { + if (!$matches['dayofyear']) { + if (!$matches['day']) { + $day = 1; + } + if (!$matches['month']) { + $month = 1; + } + } + if (!$matches['year']) { + $year = 1970; + } + if (!$matches['hour']) { + $hour = 0; + } + if (!$matches['minute']) { + $minute = 0; + } + if (!$matches['second']) { + $second = 0; + } + $dateTime->setDate($year, $month, $day); + $dateTime->setTime($hour, $minute, $second); + } + } + $lastErrors = \DateTime::getLastErrors(); if (0 < $lastErrors['warning_count'] || 0 < $lastErrors['error_count']) { 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 893c4c2bfd..804549fd80 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php @@ -18,18 +18,43 @@ class DateTimeToStringTransformerTest extends DateTimeTestCase public function dataProvider() { 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 H:i:s', '2010-02-03 16:05:06', '2010-02-03 16:05:06 UTC'), + array('Y-m-d H:i:00', '2010-02-03 16:05:00', '2010-02-03 16:05:00 UTC'), + array('Y-m-d H:i', '2010-02-03 16:05', '2010-02-03 16:05:00 UTC'), + array('Y-m-d H', '2010-02-03 16', '2010-02-03 16: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'), + array('H:i:s', '16:05:06', '1970-01-01 16:05:06 UTC'), + array('H:i:00', '16:05:00', '1970-01-01 16:05:00 UTC'), + array('H:i', '16:05', '1970-01-01 16:05:00 UTC'), + array('H', '16', '1970-01-01 16:00:00 UTC'), + + // different day representations + array('Y-m-j', '2010-02-3', '2010-02-03 00:00:00 UTC'), + array('Y-z', '2010-33', '2010-02-03 00:00:00 UTC'), + array('z', '33', '1970-02-03 00:00:00 UTC'), + + // not bijective + //array('Y-m-D', '2010-02-Wed', '2010-02-03 00:00:00 UTC'), + //array('Y-m-l', '2010-02-Wednesday', '2010-02-03 00:00:00 UTC'), + + // different month representations + array('Y-n-d', '2010-2-03', '2010-02-03 00:00:00 UTC'), + array('Y-M-d', '2010-Feb-03', '2010-02-03 00:00:00 UTC'), + array('Y-F-d', '2010-February-03', '2010-02-03 00:00:00 UTC'), + + // different year representations + array('y-m-d', '10-02-03', '2010-02-03 00:00:00 UTC'), + + // different time representations + array('G:i:s', '16:05:06', '1970-01-01 16:05:06 UTC'), + array('g:i:s a', '4:05:06 pm', '1970-01-01 16:05:06 UTC'), + array('h:i:s a', '04:05:06 pm', '1970-01-01 16:05:06 UTC'), + + // seconds since unix + array('U', '1265213106', '2010-02-03 16:05:06 UTC'), ); } @@ -75,8 +100,24 @@ class DateTimeToStringTransformerTest extends DateTimeTestCase /** * @dataProvider dataProvider */ - public function testReverseTransform($format, $input, $output) + public function testReverseTransformBeforePhp538($format, $input, $output) { + $reverseTransformer = new DateTimeToStringTransformer('UTC', 'UTC', $format, false); + + $output = new \DateTime($output); + + $this->assertDateTimeEquals($output, $reverseTransformer->reverseTransform($input)); + } + + /** + * @dataProvider dataProvider + */ + public function testReverseTransformAsOfPhp538($format, $input, $output) + { + if (version_compare(phpversion(), '5.3.8', '<')) { + $this->markTestSkipped('Requires PHP 5.3.8 or newer'); + } + $reverseTransformer = new DateTimeToStringTransformer('UTC', 'UTC', $format); $output = new \DateTime($output); @@ -95,7 +136,7 @@ class DateTimeToStringTransformerTest extends DateTimeTestCase { $reverseTransformer = new DateTimeToStringTransformer('America/New_York', 'Asia/Hong_Kong', 'Y-m-d H:i:s'); - $output = new \DateTime('2010-02-03 04:05:06 Asia/Hong_Kong'); + $output = new \DateTime('2010-02-03 16:05:06 Asia/Hong_Kong'); $input = $output->format('Y-m-d H:i:s'); $output->setTimeZone(new \DateTimeZone('America/New_York'));