diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index 54efc1dd29..dc66646005 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -149,3 +149,8 @@ CHANGELOG * fixed: the "data" option supersedes default values from the model * changed DateType to refer to the "format" option for calculating the year and day choices instead of padding them automatically + * [BC BREAK] DateType defaults to the format "yyyy-MM-dd" now if the widget is + "single_text", in order to support the HTML 5 date field out of the box + * added the option "format" to DateTimeType + * [BC BREAK] DateTimeType now outputs RFC 3339 dates by default, as generated and + consumed by HTML5 browsers, if the widget is "single_text" diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php new file mode 100644 index 0000000000..4062759913 --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * @author Bernhard Schussek + */ +class DateTimeToRfc3339Transformer extends BaseDateTimeTransformer +{ + /** + * {@inheritDoc} + */ + public function transform($dateTime) + { + if (null === $dateTime) { + return ''; + } + + if (!$dateTime instanceof \DateTime) { + throw new UnexpectedTypeException($dateTime, '\DateTime'); + } + + if ($this->inputTimezone !== $this->outputTimezone) { + $dateTime = clone $dateTime; + $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); + } + + return preg_replace('/\+00:00$/', 'Z', $dateTime->format('c')); + } + + /** + * {@inheritDoc} + */ + public function reverseTransform($rfc3339) + { + if (!is_string($rfc3339)) { + throw new UnexpectedTypeException($rfc3339, 'string'); + } + + if ('' === $rfc3339) { + return null; + } + + + $dateTime = new \DateTime($rfc3339); + + if ($this->outputTimezone !== $this->inputTimezone) { + try { + $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); + } catch (\Exception $e) { + throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); + } + } + + if (preg_match('/(\d{4})-(\d{2})-(\d{2})/', $rfc3339, $matches)) { + if (!checkdate($matches[2], $matches[3], $matches[1])) { + throw new TransformationFailedException(sprintf( + 'The date "%s-%s-%s" is not a valid date.', + $matches[1], + $matches[2], + $matches[3] + )); + } + } + + return $dateTime; + } +} diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php index dc76d8f738..eff633e09f 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormViewInterface; @@ -19,31 +20,93 @@ use Symfony\Component\Form\ReversedTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DataTransformerChain; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToRfc3339Transformer; use Symfony\Component\Form\Extension\Core\DataTransformer\ArrayToPartsTransformer; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class DateTimeType extends AbstractType { + const DEFAULT_DATE_FORMAT = \IntlDateFormatter::MEDIUM; + + const DEFAULT_TIME_FORMAT = \IntlDateFormatter::MEDIUM; + + /** + * This is not quite the HTML5 format yet, because ICU lacks the + * capability of parsing and generating RFC 3339 dates, which + * are like the below pattern but with a timezone suffix. The + * timezone suffix is + * + * * "Z" for UTC + * * "(-|+)HH:mm" for other timezones (note the colon!) + * + * http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax + * http://www.w3.org/TR/html-markup/input.datetime.html + * http://tools.ietf.org/html/rfc3339 + * + * An ICU ticket was created: + * http://icu-project.org/trac/ticket/9421 + * + * To temporarily circumvent this issue, DateTimeToRfc3339Transformer is used + * when the format matches this constant. + * + * ("ZZZZZZ" is not recognized by ICU and used here to differentiate this + * pattern from custom patterns). + */ + const HTML5_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZZZZZZ"; + + private static $acceptedFormats = array( + \IntlDateFormatter::FULL, + \IntlDateFormatter::LONG, + \IntlDateFormatter::MEDIUM, + \IntlDateFormatter::SHORT, + ); + /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $parts = array('year', 'month', 'day', 'hour', 'minute'); + $dateParts = array('year', 'month', 'day'); $timeParts = array('hour', 'minute'); - $format = 'Y-m-d H:i'; if ($options['with_seconds']) { - $format = 'Y-m-d H:i:s'; - $parts[] = 'second'; $timeParts[] = 'second'; } + $dateFormat = is_int($options['date_format']) ? $options['date_format'] : self::DEFAULT_DATE_FORMAT; + $timeFormat = self::DEFAULT_TIME_FORMAT; + $calendar = \IntlDateFormatter::GREGORIAN; + $pattern = is_string($options['format']) ? $options['format'] : null; + + if (!in_array($dateFormat, self::$acceptedFormats, true)) { + throw new InvalidOptionsException('The "date_format" option must be one of the IntlDateFormatter constants (FULL, LONG, MEDIUM, SHORT) or a string representing a custom format.'); + } + + if (null !== $pattern && (false === strpos($pattern, 'y') || false === strpos($pattern, 'M') || false === strpos($pattern, 'd') || false === strpos($pattern, 'H') || false === strpos($pattern, 'm'))) { + throw new InvalidOptionsException(sprintf('The "format" option should contain the letters "y", "M", "d", "H" and "m". Its current value is "%s".', $pattern)); + } + if ('single_text' === $options['widget']) { - $builder->addViewTransformer(new DateTimeToStringTransformer($options['data_timezone'], $options['user_timezone'], $format)); + if (self::HTML5_FORMAT === $pattern) { + $builder->addViewTransformer(new DateTimeToRfc3339Transformer( + $options['data_timezone'], + $options['user_timezone'] + )); + } else { + $builder->addViewTransformer(new DateTimeToLocalizedStringTransformer( + $options['data_timezone'], + $options['user_timezone'], + $dateFormat, + $timeFormat, + $calendar, + $pattern + )); + } } else { // Only pass a subset of the options to children $dateOptions = array_intersect_key($options, array_flip(array( @@ -54,6 +117,7 @@ class DateTimeType extends AbstractType 'required', 'translation_domain', ))); + $timeOptions = array_intersect_key($options, array_flip(array( 'hours', 'minutes', @@ -64,21 +128,15 @@ class DateTimeType extends AbstractType 'translation_domain', ))); - // If `widget` is set, overwrite widget options from `date` and `time` - if (isset($options['widget'])) { - $dateOptions['widget'] = $options['widget']; - $timeOptions['widget'] = $options['widget']; - } else { - if (isset($options['date_widget'])) { - $dateOptions['widget'] = $options['date_widget']; - } - - if (isset($options['time_widget'])) { - $timeOptions['widget'] = $options['time_widget']; - } + if (null !== $options['date_widget']) { + $dateOptions['widget'] = $options['date_widget']; } - if (isset($options['date_format'])) { + if (null !== $options['time_widget']) { + $timeOptions['widget'] = $options['time_widget']; + } + + if (null !== $options['date_format']) { $dateOptions['format'] = $options['date_format']; } @@ -89,7 +147,7 @@ class DateTimeType extends AbstractType ->addViewTransformer(new DataTransformerChain(array( new DateTimeToArrayTransformer($options['data_timezone'], $options['user_timezone'], $parts), new ArrayToPartsTransformer(array( - 'date' => array('year', 'month', 'day'), + 'date' => $dateParts, 'time' => $timeParts, )), ))) @@ -120,7 +178,10 @@ class DateTimeType extends AbstractType { $view->setVar('widget', $options['widget']); - if ('single_text' === $options['widget']) { + // Change the input to a HTML5 date input if + // * the widget is set to "single_text" + // * the format matches the one expected by HTML5 + if ('single_text' === $options['widget'] && self::HTML5_FORMAT === $options['format']) { $view->setVar('type', 'datetime'); } } @@ -134,27 +195,29 @@ class DateTimeType extends AbstractType return $options['widget'] !== 'single_text'; }; + // Defaults to the value of "widget" + $dateWidget = function (Options $options) { + return $options['widget']; + }; + + // Defaults to the value of "widget" + $timeWidget = function (Options $options) { + return $options['widget']; + }; + $resolver->setDefaults(array( 'input' => 'datetime', 'data_timezone' => null, 'user_timezone' => null, - 'date_widget' => null, + 'format' => self::HTML5_FORMAT, 'date_format' => null, - 'time_widget' => null, - /* Defaults for date field */ - 'years' => range(date('Y') - 5, date('Y') + 5), - 'months' => range(1, 12), - 'days' => range(1, 31), - /* Defaults for time field */ - 'hours' => range(0, 23), - 'minutes' => range(0, 59), - 'seconds' => range(0, 59), + 'widget' => null, + 'date_widget' => $dateWidget, + 'time_widget' => $timeWidget, 'with_seconds' => false, // Don't modify \DateTime classes by reference, we treat // them like immutable value objects 'by_reference' => false, - // This will overwrite "widget" child options - 'widget' => null, // If initialized with a \DateTime object, FormType initializes // this option to "\DateTime". Since the internal, normalized // representation is not \DateTime, but an array, we need to unset @@ -167,6 +230,12 @@ class DateTimeType extends AbstractType // set in DateType and TimeType $resolver->setOptional(array( 'empty_value', + 'years', + 'months', + 'days', + 'hours', + 'minutes', + 'seconds', )); $resolver->setAllowedValues(array( diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php index bd3b16f822..d3d3b09136 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -29,6 +29,8 @@ class DateType extends AbstractType { const DEFAULT_FORMAT = \IntlDateFormatter::MEDIUM; + const HTML5_FORMAT = 'yyyy-MM-dd'; + private static $acceptedFormats = array( \IntlDateFormatter::FULL, \IntlDateFormatter::LONG, @@ -51,7 +53,7 @@ class DateType extends AbstractType } if (null !== $pattern && (false === strpos($pattern, 'y') || false === strpos($pattern, 'M') || false === strpos($pattern, 'd'))) { - throw new InvalidOptionsException(sprintf('The "format" option should contain the patterns "y", "M" and "d". Its current value is "%s".', $pattern)); + throw new InvalidOptionsException(sprintf('The "format" option should contain the letters "y", "M" and "d". Its current value is "%s".', $pattern)); } if ('single_text' === $options['widget']) { @@ -130,7 +132,10 @@ class DateType extends AbstractType { $view->setVar('widget', $options['widget']); - if ('single_text' === $options['widget']) { + // Change the input to a HTML5 date input if + // * the widget is set to "single_text" + // * the format matches the one expected by HTML5 + if ('single_text' === $options['widget'] && self::HTML5_FORMAT === $options['format']) { $view->setVar('type', 'date'); } @@ -186,7 +191,7 @@ class DateType extends AbstractType 'days' => range(1, 31), 'widget' => 'choice', 'input' => 'datetime', - 'format' => self::DEFAULT_FORMAT, + 'format' => self::HTML5_FORMAT, 'data_timezone' => null, 'user_timezone' => null, 'empty_value' => $emptyValue, diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php index fe123f3c0f..549af044ab 100644 --- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php @@ -786,14 +786,14 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase [@id="name_date"] [ ./select + [@id="name_date_year"] + [./option[@value="2011"][@selected="selected"]] + /following-sibling::select [@id="name_date_month"] [./option[@value="2"][@selected="selected"]] /following-sibling::select [@id="name_date_day"] [./option[@value="3"][@selected="selected"]] - /following-sibling::select - [@id="name_date_year"] - [./option[@value="2011"][@selected="selected"]] ] /following-sibling::div [@id="name_time"] @@ -826,14 +826,14 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase [@id="name_date"] [ ./select + [@id="name_date_year"] + [./option[@value=""][.="[trans]Change&Me[/trans]"]] + /following-sibling::select [@id="name_date_month"] [./option[@value=""][.="[trans]Change&Me[/trans]"]] /following-sibling::select [@id="name_date_day"] [./option[@value=""][.="[trans]Change&Me[/trans]"]] - /following-sibling::select - [@id="name_date_year"] - [./option[@value=""][.="[trans]Change&Me[/trans]"]] ] /following-sibling::div [@id="name_time"] @@ -866,14 +866,14 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase [@id="name_date"] [ ./select + [@id="name_date_year"] + [./option[@value="2011"][@selected="selected"]] + /following-sibling::select [@id="name_date_month"] [./option[@value="2"][@selected="selected"]] /following-sibling::select [@id="name_date_day"] [./option[@value="3"][@selected="selected"]] - /following-sibling::select - [@id="name_date_year"] - [./option[@value="2011"][@selected="selected"]] ] /following-sibling::div [@id="name_time"] @@ -905,14 +905,14 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase [@id="name_date"] [ ./select + [@id="name_date_year"] + [./option[@value="2011"][@selected="selected"]] + /following-sibling::select [@id="name_date_month"] [./option[@value="2"][@selected="selected"]] /following-sibling::select [@id="name_date_day"] [./option[@value="3"][@selected="selected"]] - /following-sibling::select - [@id="name_date_year"] - [./option[@value="2011"][@selected="selected"]] ] /following-sibling::div [@id="name_time"] @@ -948,7 +948,7 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase [@type="date"] [@id="name_date"] [@name="name[date]"] - [@value="Feb 3, 2011"] + [@value="2011-02-03"] /following-sibling::input [@type="time"] [@id="name_time"] @@ -970,7 +970,7 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase '/input [@type="datetime"] [@name="name"] - [@value="2011-02-03 04:05"] + [@value="2011-02-03T04:05:06+01:00"] ' ); } @@ -988,7 +988,7 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase '/input [@type="datetime"] [@name="name"] - [@value="2011-02-03 04:05"] + [@value="2011-02-03T04:05:06+01:00"] ' ); } @@ -1004,14 +1004,14 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase '/div [ ./select + [@id="name_year"] + [./option[@value="2011"][@selected="selected"]] + /following-sibling::select [@id="name_month"] [./option[@value="2"][@selected="selected"]] /following-sibling::select [@id="name_day"] [./option[@value="3"][@selected="selected"]] - /following-sibling::select - [@id="name_year"] - [./option[@value="2011"][@selected="selected"]] ] [count(./select)=3] ' @@ -1031,14 +1031,14 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase '/div [ ./select + [@id="name_year"] + [./option[@value=""][.="[trans]Change&Me[/trans]"]] + /following-sibling::select [@id="name_month"] [./option[@value=""][.="[trans]Change&Me[/trans]"]] /following-sibling::select [@id="name_day"] [./option[@value=""][.="[trans]Change&Me[/trans]"]] - /following-sibling::select - [@id="name_year"] - [./option[@value=""][.="[trans]Change&Me[/trans]"]] ] [count(./select)=3] ' @@ -1058,14 +1058,14 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase '/div [ ./select + [@id="name_year"] + [./option[@value=""][.="[trans]Change&Me[/trans]"]] + /following-sibling::select [@id="name_month"] [./option[@value="1"]] /following-sibling::select [@id="name_day"] [./option[@value="1"]] - /following-sibling::select - [@id="name_year"] - [./option[@value=""][.="[trans]Change&Me[/trans]"]] ] [count(./select)=3] ' @@ -1083,6 +1083,10 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase '/div [ ./input + [@id="name_year"] + [@type="text"] + [@value="2011"] + /following-sibling::input [@id="name_month"] [@type="text"] [@value="2"] @@ -1090,10 +1094,6 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase [@id="name_day"] [@type="text"] [@value="3"] - /following-sibling::input - [@id="name_year"] - [@type="text"] - [@value="2011"] ] [count(./input)=3] ' @@ -1111,7 +1111,7 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase '/input [@type="date"] [@name="name"] - [@value="Feb 3, 2011"] + [@value="2011-02-03"] ' ); } @@ -1137,14 +1137,14 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase '/div [ ./select + [@id="name_year"] + [./option[@value="2000"][@selected="selected"]] + /following-sibling::select [@id="name_month"] [./option[@value="2"][@selected="selected"]] /following-sibling::select [@id="name_day"] [./option[@value="3"][@selected="selected"]] - /following-sibling::select - [@id="name_year"] - [./option[@value="2000"][@selected="selected"]] ] [count(./select)=3] ' @@ -1163,6 +1163,10 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase '/div [ ./select + [@id="name_year"] + [./option[@value=""][.="[trans][/trans]"]] + [./option[@value="1950"][@selected="selected"]] + /following-sibling::select [@id="name_month"] [./option[@value=""][.="[trans][/trans]"]] [./option[@value="1"][@selected="selected"]] @@ -1170,10 +1174,6 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase [@id="name_day"] [./option[@value=""][.="[trans][/trans]"]] [./option[@value="1"][@selected="selected"]] - /following-sibling::select - [@id="name_year"] - [./option[@value=""][.="[trans][/trans]"]] - [./option[@value="1950"][@selected="selected"]] ] [count(./select)=3] ' diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToRfc3339TransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToRfc3339TransformerTest.php new file mode 100644 index 0000000000..40195cb517 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToRfc3339TransformerTest.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToRfc3339Transformer; + +class DateTimeToRfc3339TransformerTest extends DateTimeTestCase +{ + protected $dateTime; + protected $dateTimeWithoutSeconds; + + protected function setUp() + { + parent::setUp(); + + $this->dateTime = new \DateTime('2010-02-03 04:05:06 UTC'); + $this->dateTimeWithoutSeconds = new \DateTime('2010-02-03 04:05:00 UTC'); + } + + protected function tearDown() + { + $this->dateTime = null; + $this->dateTimeWithoutSeconds = null; + } + + 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'); + $actual = $actual->format('c'); + } + + parent::assertEquals($expected, $actual, $message, $delta, $maxDepth, $canonicalize, $ignoreCase); + } + + public function allProvider() + { + return array( + array('UTC', 'UTC', '2010-02-03 04:05:06 UTC', '2010-02-03T04:05:06Z'), + array('UTC', 'UTC', null, ''), + array('America/New_York', 'Asia/Hong_Kong', '2010-02-03 04:05:06 America/New_York', '2010-02-03T17:05:06+08:00'), + array('America/New_York', 'Asia/Hong_Kong', null, ''), + array('UTC', 'Asia/Hong_Kong', '2010-02-03 04:05:06 UTC', '2010-02-03T12:05:06+08:00'), + array('America/New_York', 'UTC', '2010-02-03 04:05:06 America/New_York', '2010-02-03T09:05:06Z'), + ); + } + + public function transformProvider() + { + return $this->allProvider(); + } + + public function reverseTransformProvider() + { + return array_merge($this->allProvider(), array( + // format without seconds, as appears in some browsers + array('UTC', 'UTC', '2010-02-03 04:05:00 UTC', '2010-02-03T04:05Z'), + array('America/New_York', 'Asia/Hong_Kong', '2010-02-03 04:05:00 America/New_York', '2010-02-03T17:05+08:00'), + )); + } + + /** + * @dataProvider transformProvider + */ + public function testTransform($fromTz, $toTz, $from, $to) + { + $transformer = new DateTimeToRfc3339Transformer($fromTz, $toTz); + + $this->assertSame($to, $transformer->transform(null !== $from ? new \DateTime($from) : null)); + } + + /** + * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException + */ + public function testTransformRequiresValidDateTime() + { + $transformer = new DateTimeToRfc3339Transformer(); + $transformer->transform('2010-01-01'); + } + + /** + * @dataProvider reverseTransformProvider + */ + public function testReverseTransform($toTz, $fromTz, $to, $from) + { + $transformer = new DateTimeToRfc3339Transformer($toTz, $fromTz); + + if (null !== $to) { + $this->assertDateTimeEquals(new \DateTime($to), $transformer->reverseTransform($from)); + } else { + $this->assertSame($to, $transformer->reverseTransform($from)); + } + } + + /** + * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException + */ + public function testReverseTransformRequiresString() + { + $transformer = new DateTimeToRfc3339Transformer(); + $transformer->reverseTransform(12345); + } + + /** + * @expectedException Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformWithNonExistingDate() + { + $transformer = new DateTimeToRfc3339Transformer('UTC', 'UTC'); + + var_dump($transformer->reverseTransform('2010-04-31T04:05Z')); + } +} diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php index f48ed69fa2..ef31d72935 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php @@ -163,15 +163,14 @@ class DateTimeTypeTest extends LocalizedTestCase 'input' => 'datetime', )); - $dateTime = new \DateTime('2010-06-02 03:04:05 America/New_York'); - - $form->bind('2010-06-02 03:04:05'); - $outputTime = new \DateTime('2010-06-02 03:04:00 Pacific/Tahiti'); + + $form->bind('2010-06-02T03:04:00-10:00'); + $outputTime->setTimezone(new \DateTimeZone('America/New_York')); $this->assertDateTimeEquals($outputTime, $form->getData()); - $this->assertEquals('2010-06-02 03:04', $form->getViewData()); + $this->assertEquals('2010-06-02T03:04:00-10:00', $form->getViewData()); } public function testSubmit_stringSingleText() @@ -183,10 +182,10 @@ class DateTimeTypeTest extends LocalizedTestCase 'widget' => 'single_text', )); - $form->bind('2010-06-02 03:04:05'); + $form->bind('2010-06-02T03:04:00Z'); $this->assertEquals('2010-06-02 03:04:00', $form->getData()); - $this->assertEquals('2010-06-02 03:04', $form->getViewData()); + $this->assertEquals('2010-06-02T03:04:00Z', $form->getViewData()); } public function testSubmit_stringSingleText_withSeconds() @@ -199,10 +198,10 @@ class DateTimeTypeTest extends LocalizedTestCase 'with_seconds' => true, )); - $form->bind('2010-06-02 03:04:05'); + $form->bind('2010-06-02T03:04:05Z'); $this->assertEquals('2010-06-02 03:04:05', $form->getData()); - $this->assertEquals('2010-06-02 03:04:05', $form->getViewData()); + $this->assertEquals('2010-06-02T03:04:05Z', $form->getViewData()); } public function testSubmit_differentPattern() @@ -356,4 +355,35 @@ class DateTimeTypeTest extends LocalizedTestCase $this->assertNull($view->get('time')->get('minute')->getVar('empty_value')); $this->assertSame('Empty second', $view->get('time')->get('second')->getVar('empty_value')); } + + public function testPassHtml5TypeIfSingleTextAndHtml5Format() + { + $form = $this->factory->create('datetime', null, array( + 'widget' => 'single_text', + )); + + $view = $form->createView(); + $this->assertSame('datetime', $view->getVar('type')); + } + + public function testDontPassHtml5TypeIfNotHtml5Format() + { + $form = $this->factory->create('datetime', null, array( + 'widget' => 'single_text', + 'format' => 'yyyy-MM-dd HH:mm', + )); + + $view = $form->createView(); + $this->assertNull($view->getVar('datetime')); + } + + public function testDontPassHtml5TypeIfNotSingleText() + { + $form = $this->factory->create('datetime', null, array( + 'widget' => 'text', + )); + + $view = $form->createView(); + $this->assertNull($view->getVar('type')); + } } 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 d6ddd814d3..ca7218a9a8 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php @@ -42,9 +42,25 @@ class DateTypeTest extends LocalizedTestCase )); } + public function testSubmitFromSingleTextDateTimeWithDefaultFormat() + { + $form = $this->factory->create('date', null, array( + 'data_timezone' => 'UTC', + 'user_timezone' => 'UTC', + 'widget' => 'single_text', + 'input' => 'datetime', + )); + + $form->bind('2010-06-02'); + + $this->assertDateTimeEquals(new \DateTime('2010-06-02 UTC'), $form->getData()); + $this->assertEquals('2010-06-02', $form->getViewData()); + } + public function testSubmitFromSingleTextDateTime() { $form = $this->factory->create('date', null, array( + 'format' => \IntlDateFormatter::MEDIUM, 'data_timezone' => 'UTC', 'user_timezone' => 'UTC', 'widget' => 'single_text', @@ -60,6 +76,7 @@ class DateTypeTest extends LocalizedTestCase public function testSubmitFromSingleTextString() { $form = $this->factory->create('date', null, array( + 'format' => \IntlDateFormatter::MEDIUM, 'data_timezone' => 'UTC', 'user_timezone' => 'UTC', 'widget' => 'single_text', @@ -75,6 +92,7 @@ class DateTypeTest extends LocalizedTestCase public function testSubmitFromSingleTextTimestamp() { $form = $this->factory->create('date', null, array( + 'format' => \IntlDateFormatter::MEDIUM, 'data_timezone' => 'UTC', 'user_timezone' => 'UTC', 'widget' => 'single_text', @@ -92,6 +110,7 @@ class DateTypeTest extends LocalizedTestCase public function testSubmitFromSingleTextRaw() { $form = $this->factory->create('date', null, array( + 'format' => \IntlDateFormatter::MEDIUM, 'data_timezone' => 'UTC', 'user_timezone' => 'UTC', 'widget' => 'single_text', @@ -296,6 +315,7 @@ class DateTypeTest extends LocalizedTestCase public function testSetData_differentTimezones() { $form = $this->factory->create('date', null, array( + 'format' => \IntlDateFormatter::MEDIUM, 'data_timezone' => 'America/New_York', 'user_timezone' => 'Pacific/Tahiti', 'input' => 'string', @@ -310,6 +330,7 @@ class DateTypeTest extends LocalizedTestCase public function testSetData_differentTimezonesDateTime() { $form = $this->factory->create('date', null, array( + 'format' => \IntlDateFormatter::MEDIUM, 'data_timezone' => 'America/New_York', 'user_timezone' => 'Pacific/Tahiti', 'input' => 'datetime', @@ -488,6 +509,17 @@ class DateTypeTest extends LocalizedTestCase $form = $this->factory->create('date'); $view = $form->createView(); + $this->assertSame('{{ year }}-{{ month }}-{{ day }}', $view->getVar('date_pattern')); + } + + public function testPassDatePatternToViewDifferentFormat() + { + $form = $this->factory->create('date', null, array( + 'format' => \IntlDateFormatter::MEDIUM, + )); + + $view = $form->createView(); + $this->assertSame('{{ day }}.{{ month }}.{{ year }}', $view->getVar('date_pattern')); } @@ -623,4 +655,35 @@ class DateTypeTest extends LocalizedTestCase $this->assertNull($view->get('month')->getVar('empty_value')); $this->assertSame('Empty day', $view->get('day')->getVar('empty_value')); } + + public function testPassHtml5TypeIfSingleTextAndHtml5Format() + { + $form = $this->factory->create('date', null, array( + 'widget' => 'single_text', + )); + + $view = $form->createView(); + $this->assertSame('date', $view->getVar('type')); + } + + public function testDontPassHtml5TypeIfNotHtml5Format() + { + $form = $this->factory->create('date', null, array( + 'widget' => 'single_text', + 'format' => \IntlDateFormatter::MEDIUM, + )); + + $view = $form->createView(); + $this->assertNull($view->getVar('type')); + } + + public function testDontPassHtml5TypeIfNotSingleText() + { + $form = $this->factory->create('date', null, array( + 'widget' => 'text', + )); + + $view = $form->createView(); + $this->assertNull($view->getVar('type')); + } }