[Form] Fix DateTimeType html5 input format
This commit is contained in:
parent
992a174470
commit
253d0a683b
@ -0,0 +1,97 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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\TransformationFailedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Franz Wilding <franz.wilding@me.com>
|
||||||
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
|
*/
|
||||||
|
class DateTimeToHtml5DateTimeLocalTransformer extends BaseDateTimeTransformer
|
||||||
|
{
|
||||||
|
const HTML5_FORMAT = 'Y-m-d\\TH:i:s';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms a normalized date into a localized date without trailing timezone.
|
||||||
|
*
|
||||||
|
* According to the HTML standard, the input string of a datetime-local
|
||||||
|
* input is a RFC3339 date followed by 'T', followed by a RFC3339 time.
|
||||||
|
* http://w3c.github.io/html-reference/datatypes.html#form.data.datetime-local
|
||||||
|
*
|
||||||
|
* @param \DateTime|\DateTimeInterface $dateTime A DateTime object
|
||||||
|
*
|
||||||
|
* @return string The formatted date
|
||||||
|
*
|
||||||
|
* @throws TransformationFailedException If the given value is not an
|
||||||
|
* instance of \DateTime or \DateTimeInterface
|
||||||
|
*/
|
||||||
|
public function transform($dateTime)
|
||||||
|
{
|
||||||
|
if (null === $dateTime) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$dateTime instanceof \DateTime && !$dateTime instanceof \DateTimeInterface) {
|
||||||
|
throw new TransformationFailedException('Expected a \DateTime or \DateTimeInterface.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->inputTimezone !== $this->outputTimezone) {
|
||||||
|
if (!$dateTime instanceof \DateTimeImmutable) {
|
||||||
|
$dateTime = clone $dateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dateTime = $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dateTime->format(self::HTML5_FORMAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms a formatted datetime-local string into a normalized date.
|
||||||
|
*
|
||||||
|
* @param string $dateTimeLocal Formatted string
|
||||||
|
*
|
||||||
|
* @return \DateTime Normalized date
|
||||||
|
*
|
||||||
|
* @throws TransformationFailedException If the given value is not a string,
|
||||||
|
* if the value could not be transformed
|
||||||
|
*/
|
||||||
|
public function reverseTransform($dateTimeLocal)
|
||||||
|
{
|
||||||
|
if (!\is_string($dateTimeLocal)) {
|
||||||
|
throw new TransformationFailedException('Expected a string.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('' === $dateTimeLocal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$dateTime = new \DateTime($dateTimeLocal, new \DateTimeZone($this->outputTimezone));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->inputTimezone !== $dateTime->getTimezone()->getName()) {
|
||||||
|
$dateTime->setTimezone(new \DateTimeZone($this->inputTimezone));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/(\d{4})-(\d{2})-(\d{2})/', $dateTimeLocal, $m)) {
|
||||||
|
if (!checkdate($m[2], $m[3], $m[1])) {
|
||||||
|
throw new TransformationFailedException(sprintf('The date "%s-%s-%s" is not a valid date.', $m[1], $m[2], $m[3]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dateTime;
|
||||||
|
}
|
||||||
|
}
|
@ -15,8 +15,8 @@ use Symfony\Component\Form\AbstractType;
|
|||||||
use Symfony\Component\Form\Extension\Core\DataTransformer\ArrayToPartsTransformer;
|
use Symfony\Component\Form\Extension\Core\DataTransformer\ArrayToPartsTransformer;
|
||||||
use Symfony\Component\Form\Extension\Core\DataTransformer\DataTransformerChain;
|
use Symfony\Component\Form\Extension\Core\DataTransformer\DataTransformerChain;
|
||||||
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer;
|
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer;
|
||||||
|
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToHtml5DateTimeLocalTransformer;
|
||||||
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer;
|
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer;
|
||||||
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToRfc3339Transformer;
|
|
||||||
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer;
|
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer;
|
||||||
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
|
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
@ -33,21 +33,8 @@ class DateTimeType extends AbstractType
|
|||||||
const DEFAULT_TIME_FORMAT = \IntlDateFormatter::MEDIUM;
|
const DEFAULT_TIME_FORMAT = \IntlDateFormatter::MEDIUM;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is not quite the HTML5 format yet, because ICU lacks the
|
* The HTML5 datetime-local format as defined in
|
||||||
* capability of parsing and generating RFC 3339 dates.
|
* http://w3c.github.io/html-reference/datatypes.html#form.data.datetime-local.
|
||||||
*
|
|
||||||
* For more information see:
|
|
||||||
*
|
|
||||||
* http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax
|
|
||||||
* https://www.w3.org/TR/html5/sec-forms.html#local-date-and-time-state-typedatetimelocal
|
|
||||||
* http://tools.ietf.org/html/rfc3339
|
|
||||||
*
|
|
||||||
* An ICU ticket was created:
|
|
||||||
* http://icu-project.org/trac/ticket/9421
|
|
||||||
*
|
|
||||||
* It was supposedly fixed, but is not available in all PHP installations
|
|
||||||
* yet. To temporarily circumvent this issue, DateTimeToRfc3339Transformer
|
|
||||||
* is used when the format matches this constant.
|
|
||||||
*/
|
*/
|
||||||
const HTML5_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
|
const HTML5_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
|
||||||
|
|
||||||
@ -88,7 +75,7 @@ class DateTimeType extends AbstractType
|
|||||||
|
|
||||||
if ('single_text' === $options['widget']) {
|
if ('single_text' === $options['widget']) {
|
||||||
if (self::HTML5_FORMAT === $pattern) {
|
if (self::HTML5_FORMAT === $pattern) {
|
||||||
$builder->addViewTransformer(new DateTimeToRfc3339Transformer(
|
$builder->addViewTransformer(new DateTimeToHtml5DateTimeLocalTransformer(
|
||||||
$options['model_timezone'],
|
$options['model_timezone'],
|
||||||
$options['view_timezone']
|
$options['view_timezone']
|
||||||
));
|
));
|
||||||
|
@ -1603,7 +1603,7 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
|
|||||||
[@type="datetime-local"]
|
[@type="datetime-local"]
|
||||||
[@name="name"]
|
[@name="name"]
|
||||||
[@class="my&class form-control"]
|
[@class="my&class form-control"]
|
||||||
[@value="2011-02-03T04:05:06Z"]
|
[@value="2011-02-03T04:05:06"]
|
||||||
'
|
'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1624,7 +1624,7 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
|
|||||||
[@type="datetime-local"]
|
[@type="datetime-local"]
|
||||||
[@name="name"]
|
[@name="name"]
|
||||||
[@class="my&class form-control"]
|
[@class="my&class form-control"]
|
||||||
[@value="2011-02-03T04:05:06Z"]
|
[@value="2011-02-03T04:05:06"]
|
||||||
'
|
'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1502,7 +1502,7 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
|
|||||||
'/input
|
'/input
|
||||||
[@type="datetime-local"]
|
[@type="datetime-local"]
|
||||||
[@name="name"]
|
[@name="name"]
|
||||||
[@value="2011-02-03T04:05:06Z"]
|
[@value="2011-02-03T04:05:06"]
|
||||||
'
|
'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1522,7 +1522,7 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
|
|||||||
'/input
|
'/input
|
||||||
[@type="datetime-local"]
|
[@type="datetime-local"]
|
||||||
[@name="name"]
|
[@name="name"]
|
||||||
[@value="2011-02-03T04:05:06Z"]
|
[@value="2011-02-03T04:05:06"]
|
||||||
'
|
'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,129 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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 PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToHtml5DateTimeLocalTransformer;
|
||||||
|
|
||||||
|
class DateTimeToHtml5DateTimeLocaleTransformerTest extends TestCase
|
||||||
|
{
|
||||||
|
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 transformProvider()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('UTC', 'UTC', '2010-02-03 04:05:06 UTC', '2010-02-03T04:05:06'),
|
||||||
|
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'),
|
||||||
|
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'),
|
||||||
|
array('America/New_York', 'UTC', '2010-02-03 04:05:06 America/New_York', '2010-02-03T09:05:06'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reverseTransformProvider()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
// format without seconds, as appears in some browsers
|
||||||
|
array('UTC', 'UTC', '2010-02-03 04:05:06 UTC', '2010-02-03T04:05:06'),
|
||||||
|
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'),
|
||||||
|
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'),
|
||||||
|
array('America/New_York', 'UTC', '2010-02-03 04:05:06 America/New_York', '2010-02-03T09:05:06'),
|
||||||
|
array('UTC', 'UTC', '2010-02-03 04:05:00 UTC', '2010-02-03T04:05'),
|
||||||
|
array('America/New_York', 'Asia/Hong_Kong', '2010-02-03 04:05:00 America/New_York', '2010-02-03T17:05'),
|
||||||
|
array('Europe/Amsterdam', 'Europe/Amsterdam', '2013-08-21 10:30:00 Europe/Amsterdam', '2013-08-21T10:30:00'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider transformProvider
|
||||||
|
*/
|
||||||
|
public function testTransform($fromTz, $toTz, $from, $to)
|
||||||
|
{
|
||||||
|
$transformer = new DateTimeToHtml5DateTimeLocalTransformer($fromTz, $toTz);
|
||||||
|
|
||||||
|
$this->assertSame($to, $transformer->transform(null !== $from ? new \DateTime($from) : null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider transformProvider
|
||||||
|
* @requires PHP 5.5
|
||||||
|
*/
|
||||||
|
public function testTransformDateTimeImmutable($fromTz, $toTz, $from, $to)
|
||||||
|
{
|
||||||
|
$transformer = new DateTimeToHtml5DateTimeLocalTransformer($fromTz, $toTz);
|
||||||
|
|
||||||
|
$this->assertSame($to, $transformer->transform(null !== $from ? new \DateTimeImmutable($from) : null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
|
||||||
|
*/
|
||||||
|
public function testTransformRequiresValidDateTime()
|
||||||
|
{
|
||||||
|
$transformer = new DateTimeToHtml5DateTimeLocalTransformer();
|
||||||
|
$transformer->transform('2010-01-01');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider reverseTransformProvider
|
||||||
|
*/
|
||||||
|
public function testReverseTransform($toTz, $fromTz, $to, $from)
|
||||||
|
{
|
||||||
|
$transformer = new DateTimeToHtml5DateTimeLocalTransformer($toTz, $fromTz);
|
||||||
|
|
||||||
|
if (null !== $to) {
|
||||||
|
$this->assertEquals(new \DateTime($to), $transformer->reverseTransform($from));
|
||||||
|
} else {
|
||||||
|
$this->assertNull($transformer->reverseTransform($from));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
|
||||||
|
*/
|
||||||
|
public function testReverseTransformRequiresString()
|
||||||
|
{
|
||||||
|
$transformer = new DateTimeToHtml5DateTimeLocalTransformer();
|
||||||
|
$transformer->reverseTransform(12345);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
|
||||||
|
*/
|
||||||
|
public function testReverseTransformWithNonExistingDate()
|
||||||
|
{
|
||||||
|
$transformer = new DateTimeToHtml5DateTimeLocalTransformer('UTC', 'UTC');
|
||||||
|
|
||||||
|
$transformer->reverseTransform('2010-04-31T04:05');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
|
||||||
|
*/
|
||||||
|
public function testReverseTransformExpectsValidDateString()
|
||||||
|
{
|
||||||
|
$transformer = new DateTimeToHtml5DateTimeLocalTransformer('UTC', 'UTC');
|
||||||
|
|
||||||
|
$transformer->reverseTransform('2010-2010-2010');
|
||||||
|
}
|
||||||
|
}
|
@ -221,12 +221,12 @@ class DateTimeTypeTest extends BaseTypeTest
|
|||||||
|
|
||||||
$outputTime = new \DateTime('2010-06-02 03:04:00 Pacific/Tahiti');
|
$outputTime = new \DateTime('2010-06-02 03:04:00 Pacific/Tahiti');
|
||||||
|
|
||||||
$form->submit('2010-06-02T03:04:00-10:00');
|
$form->submit('2010-06-02T03:04:00');
|
||||||
|
|
||||||
$outputTime->setTimezone(new \DateTimeZone('America/New_York'));
|
$outputTime->setTimezone(new \DateTimeZone('America/New_York'));
|
||||||
|
|
||||||
$this->assertEquals($outputTime, $form->getData());
|
$this->assertEquals($outputTime, $form->getData());
|
||||||
$this->assertEquals('2010-06-02T03:04:00-10:00', $form->getViewData());
|
$this->assertEquals('2010-06-02T03:04:00', $form->getViewData());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSubmitStringSingleText()
|
public function testSubmitStringSingleText()
|
||||||
@ -238,10 +238,10 @@ class DateTimeTypeTest extends BaseTypeTest
|
|||||||
'widget' => 'single_text',
|
'widget' => 'single_text',
|
||||||
));
|
));
|
||||||
|
|
||||||
$form->submit('2010-06-02T03:04:00Z');
|
$form->submit('2010-06-02T03:04:00');
|
||||||
|
|
||||||
$this->assertEquals('2010-06-02 03:04:00', $form->getData());
|
$this->assertEquals('2010-06-02 03:04:00', $form->getData());
|
||||||
$this->assertEquals('2010-06-02T03:04:00Z', $form->getViewData());
|
$this->assertEquals('2010-06-02T03:04:00', $form->getViewData());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSubmitStringSingleTextWithSeconds()
|
public function testSubmitStringSingleTextWithSeconds()
|
||||||
@ -254,10 +254,10 @@ class DateTimeTypeTest extends BaseTypeTest
|
|||||||
'with_seconds' => true,
|
'with_seconds' => true,
|
||||||
));
|
));
|
||||||
|
|
||||||
$form->submit('2010-06-02T03:04:05Z');
|
$form->submit('2010-06-02T03:04:05');
|
||||||
|
|
||||||
$this->assertEquals('2010-06-02 03:04:05', $form->getData());
|
$this->assertEquals('2010-06-02 03:04:05', $form->getData());
|
||||||
$this->assertEquals('2010-06-02T03:04:05Z', $form->getViewData());
|
$this->assertEquals('2010-06-02T03:04:05', $form->getViewData());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSubmitDifferentPattern()
|
public function testSubmitDifferentPattern()
|
||||||
|
Reference in New Issue
Block a user