Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 67a95f4

Browse filesBrowse files
franzwildingmcfedr
authored andcommitted
[Form] Fix DateTimeType html5 input format
1 parent 992a174 commit 67a95f4
Copy full SHA for 67a95f4

File tree

Expand file treeCollapse file tree

6 files changed

+240
-27
lines changed
Filter options
Expand file treeCollapse file tree

6 files changed

+240
-27
lines changed
+97Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\Core\DataTransformer;
13+
14+
use Symfony\Component\Form\Exception\TransformationFailedException;
15+
16+
/**
17+
* @author Franz Wilding <franz.wilding@me.com>
18+
* @author Bernhard Schussek <bschussek@gmail.com>
19+
*/
20+
class DateTimeToHtml5DateTimeLocalTransformer extends BaseDateTimeTransformer
21+
{
22+
const HTML5_FORMAT = "Y-m-d\TH:i:s";
23+
24+
/**
25+
* Transforms a normalized date into a localized date without trailing timezone.
26+
*
27+
* According to the HTML standard, the input string of a datetime-local
28+
* input is a RFC3339 date followed by 'T', followed by a RFC3339 time.
29+
* http://w3c.github.io/html-reference/datatypes.html#form.data.datetime-local
30+
*
31+
* @param \DateTime|\DateTimeInterface $dateTime A DateTime object
32+
*
33+
* @return string The formatted date
34+
*
35+
* @throws TransformationFailedException If the given value is not an
36+
* instance of \DateTime or \DateTimeInterface
37+
*/
38+
public function transform($dateTime)
39+
{
40+
if (null === $dateTime) {
41+
return '';
42+
}
43+
44+
if (!$dateTime instanceof \DateTime && !$dateTime instanceof \DateTimeInterface) {
45+
throw new TransformationFailedException('Expected a \DateTime or \DateTimeInterface.');
46+
}
47+
48+
if ($this->inputTimezone !== $this->outputTimezone) {
49+
if (!$dateTime instanceof \DateTimeImmutable) {
50+
$dateTime = clone $dateTime;
51+
}
52+
53+
$dateTime = $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone));
54+
}
55+
56+
return $dateTime->format(self::HTML5_FORMAT);
57+
}
58+
59+
/**
60+
* Transforms a formatted datetime-local string into a normalized date.
61+
*
62+
* @param string $dateTimeLocal Formatted string
63+
*
64+
* @return \DateTime Normalized date
65+
*
66+
* @throws TransformationFailedException If the given value is not a string,
67+
* if the value could not be transformed
68+
*/
69+
public function reverseTransform($dateTimeLocal)
70+
{
71+
if (!\is_string($dateTimeLocal)) {
72+
throw new TransformationFailedException('Expected a string.');
73+
}
74+
75+
if ('' === $dateTimeLocal) {
76+
return;
77+
}
78+
79+
try {
80+
$dateTime = new \DateTime($dateTimeLocal, new \DateTimeZone($this->outputTimezone));
81+
} catch (\Exception $e) {
82+
throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e);
83+
}
84+
85+
if ($this->inputTimezone !== $dateTime->getTimezone()->getName()) {
86+
$dateTime->setTimezone(new \DateTimeZone($this->inputTimezone));
87+
}
88+
89+
if (preg_match('/(\d{4})-(\d{2})-(\d{2})/', $dateTimeLocal, $m)) {
90+
if (!checkdate($m[2], $m[3], $m[1])) {
91+
throw new TransformationFailedException(sprintf('The date "%s-%s-%s" is not a valid date.', $m[1], $m[2], $m[3]));
92+
}
93+
}
94+
95+
return $dateTime;
96+
}
97+
}

‎src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php
+4-17Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
use Symfony\Component\Form\Extension\Core\DataTransformer\ArrayToPartsTransformer;
1616
use Symfony\Component\Form\Extension\Core\DataTransformer\DataTransformerChain;
1717
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer;
18+
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToHTML5DateTimeLocalTransformer;
1819
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer;
19-
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToRfc3339Transformer;
2020
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer;
2121
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
2222
use Symfony\Component\Form\FormBuilderInterface;
@@ -33,21 +33,8 @@ class DateTimeType extends AbstractType
3333
const DEFAULT_TIME_FORMAT = \IntlDateFormatter::MEDIUM;
3434

3535
/**
36-
* This is not quite the HTML5 format yet, because ICU lacks the
37-
* capability of parsing and generating RFC 3339 dates.
38-
*
39-
* For more information see:
40-
*
41-
* http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax
42-
* https://www.w3.org/TR/html5/sec-forms.html#local-date-and-time-state-typedatetimelocal
43-
* http://tools.ietf.org/html/rfc3339
44-
*
45-
* An ICU ticket was created:
46-
* http://icu-project.org/trac/ticket/9421
47-
*
48-
* It was supposedly fixed, but is not available in all PHP installations
49-
* yet. To temporarily circumvent this issue, DateTimeToRfc3339Transformer
50-
* is used when the format matches this constant.
36+
* The HTML5 datetime-local format as defined in
37+
* http://w3c.github.io/html-reference/datatypes.html#form.data.datetime-local.
5138
*/
5239
const HTML5_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
5340

@@ -88,7 +75,7 @@ public function buildForm(FormBuilderInterface $builder, array $options)
8875

8976
if ('single_text' === $options['widget']) {
9077
if (self::HTML5_FORMAT === $pattern) {
91-
$builder->addViewTransformer(new DateTimeToRfc3339Transformer(
78+
$builder->addViewTransformer(new DateTimeToHtml5DateTimeLocalTransformer(
9279
$options['model_timezone'],
9380
$options['view_timezone']
9481
));

‎src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1603,7 +1603,7 @@ public function testDateTimeWithWidgetSingleText()
16031603
[@type="datetime-local"]
16041604
[@name="name"]
16051605
[@class="my&class form-control"]
1606-
[@value="2011-02-03T04:05:06Z"]
1606+
[@value="2011-02-03T04:05:06"]
16071607
'
16081608
);
16091609
}
@@ -1624,7 +1624,7 @@ public function testDateTimeWithWidgetSingleTextIgnoreDateAndTimeWidgets()
16241624
[@type="datetime-local"]
16251625
[@name="name"]
16261626
[@class="my&class form-control"]
1627-
[@value="2011-02-03T04:05:06Z"]
1627+
[@value="2011-02-03T04:05:06"]
16281628
'
16291629
);
16301630
}

‎src/Symfony/Component/Form/Tests/AbstractLayoutTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,7 +1502,7 @@ public function testDateTimeWithWidgetSingleText()
15021502
'/input
15031503
[@type="datetime-local"]
15041504
[@name="name"]
1505-
[@value="2011-02-03T04:05:06Z"]
1505+
[@value="2011-02-03T04:05:06"]
15061506
'
15071507
);
15081508
}
@@ -1522,7 +1522,7 @@ public function testDateTimeWithWidgetSingleTextIgnoreDateAndTimeWidgets()
15221522
'/input
15231523
[@type="datetime-local"]
15241524
[@name="name"]
1525-
[@value="2011-02-03T04:05:06Z"]
1525+
[@value="2011-02-03T04:05:06"]
15261526
'
15271527
);
15281528
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Tests\Extension\Core\DataTransformer;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToHtml5DateTimeLocalTransformer;
16+
17+
class DateTimeToHtml5DateTimeLocaleTransformerTest extends TestCase
18+
{
19+
public static function assertEquals($expected, $actual, $message = '', $delta = 0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false)
20+
{
21+
if ($expected instanceof \DateTime && $actual instanceof \DateTime) {
22+
$expected = $expected->format('c');
23+
$actual = $actual->format('c');
24+
}
25+
26+
parent::assertEquals($expected, $actual, $message, $delta, $maxDepth, $canonicalize, $ignoreCase);
27+
}
28+
29+
public function transformProvider()
30+
{
31+
return array(
32+
array('UTC', 'UTC', '2010-02-03 04:05:06 UTC', '2010-02-03T04:05:06'),
33+
array('UTC', 'UTC', null, ''),
34+
array('America/New_York', 'Asia/Hong_Kong', '2010-02-03 04:05:06 America/New_York', '2010-02-03T17:05:06'),
35+
array('America/New_York', 'Asia/Hong_Kong', null, ''),
36+
array('UTC', 'Asia/Hong_Kong', '2010-02-03 04:05:06 UTC', '2010-02-03T12:05:06'),
37+
array('America/New_York', 'UTC', '2010-02-03 04:05:06 America/New_York', '2010-02-03T09:05:06'),
38+
);
39+
}
40+
41+
public function reverseTransformProvider()
42+
{
43+
return array(
44+
// format without seconds, as appears in some browsers
45+
array('UTC', 'UTC', '2010-02-03 04:05:06 UTC', '2010-02-03T04:05:06'),
46+
array('UTC', 'UTC', null, ''),
47+
array('America/New_York', 'Asia/Hong_Kong', '2010-02-03 04:05:06 America/New_York', '2010-02-03T17:05:06'),
48+
array('America/New_York', 'Asia/Hong_Kong', null, ''),
49+
array('UTC', 'Asia/Hong_Kong', '2010-02-03 04:05:06 UTC', '2010-02-03T12:05:06'),
50+
array('America/New_York', 'UTC', '2010-02-03 04:05:06 America/New_York', '2010-02-03T09:05:06'),
51+
array('UTC', 'UTC', '2010-02-03 04:05:00 UTC', '2010-02-03T04:05'),
52+
array('America/New_York', 'Asia/Hong_Kong', '2010-02-03 04:05:00 America/New_York', '2010-02-03T17:05'),
53+
array('Europe/Amsterdam', 'Europe/Amsterdam', '2013-08-21 10:30:00 Europe/Amsterdam', '2013-08-21T10:30:00'),
54+
);
55+
}
56+
57+
/**
58+
* @dataProvider transformProvider
59+
*/
60+
public function testTransform($fromTz, $toTz, $from, $to)
61+
{
62+
$transformer = new DateTimeToHtml5DateTimeLocalTransformer($fromTz, $toTz);
63+
64+
$this->assertSame($to, $transformer->transform(null !== $from ? new \DateTime($from) : null));
65+
}
66+
67+
/**
68+
* @dataProvider transformProvider
69+
* @requires PHP 5.5
70+
*/
71+
public function testTransformDateTimeImmutable($fromTz, $toTz, $from, $to)
72+
{
73+
$transformer = new DateTimeToHtml5DateTimeLocalTransformer($fromTz, $toTz);
74+
75+
$this->assertSame($to, $transformer->transform(null !== $from ? new \DateTimeImmutable($from) : null));
76+
}
77+
78+
/**
79+
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
80+
*/
81+
public function testTransformRequiresValidDateTime()
82+
{
83+
$transformer = new DateTimeToHtml5DateTimeLocalTransformer();
84+
$transformer->transform('2010-01-01');
85+
}
86+
87+
/**
88+
* @dataProvider reverseTransformProvider
89+
*/
90+
public function testReverseTransform($toTz, $fromTz, $to, $from)
91+
{
92+
$transformer = new DateTimeToHtml5DateTimeLocalTransformer($toTz, $fromTz);
93+
94+
if (null !== $to) {
95+
$this->assertEquals(new \DateTime($to), $transformer->reverseTransform($from));
96+
} else {
97+
$this->assertNull($transformer->reverseTransform($from));
98+
}
99+
}
100+
101+
/**
102+
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
103+
*/
104+
public function testReverseTransformRequiresString()
105+
{
106+
$transformer = new DateTimeToHtml5DateTimeLocalTransformer();
107+
$transformer->reverseTransform(12345);
108+
}
109+
110+
/**
111+
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
112+
*/
113+
public function testReverseTransformWithNonExistingDate()
114+
{
115+
$transformer = new DateTimeToHtml5DateTimeLocalTransformer('UTC', 'UTC');
116+
117+
$transformer->reverseTransform('2010-04-31T04:05');
118+
}
119+
120+
/**
121+
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
122+
*/
123+
public function testReverseTransformExpectsValidDateString()
124+
{
125+
$transformer = new DateTimeToHtml5DateTimeLocalTransformer('UTC', 'UTC');
126+
127+
$transformer->reverseTransform('2010-2010-2010');
128+
}
129+
}

‎src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php
+6-6Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -221,12 +221,12 @@ public function testSubmitDifferentTimezonesDateTime()
221221

222222
$outputTime = new \DateTime('2010-06-02 03:04:00 Pacific/Tahiti');
223223

224-
$form->submit('2010-06-02T03:04:00-10:00');
224+
$form->submit('2010-06-02T03:04:00');
225225

226226
$outputTime->setTimezone(new \DateTimeZone('America/New_York'));
227227

228228
$this->assertEquals($outputTime, $form->getData());
229-
$this->assertEquals('2010-06-02T03:04:00-10:00', $form->getViewData());
229+
$this->assertEquals('2010-06-02T03:04:00', $form->getViewData());
230230
}
231231

232232
public function testSubmitStringSingleText()
@@ -238,10 +238,10 @@ public function testSubmitStringSingleText()
238238
'widget' => 'single_text',
239239
));
240240

241-
$form->submit('2010-06-02T03:04:00Z');
241+
$form->submit('2010-06-02T03:04:00');
242242

243243
$this->assertEquals('2010-06-02 03:04:00', $form->getData());
244-
$this->assertEquals('2010-06-02T03:04:00Z', $form->getViewData());
244+
$this->assertEquals('2010-06-02T03:04:00', $form->getViewData());
245245
}
246246

247247
public function testSubmitStringSingleTextWithSeconds()
@@ -254,10 +254,10 @@ public function testSubmitStringSingleTextWithSeconds()
254254
'with_seconds' => true,
255255
));
256256

257-
$form->submit('2010-06-02T03:04:05Z');
257+
$form->submit('2010-06-02T03:04:05');
258258

259259
$this->assertEquals('2010-06-02 03:04:05', $form->getData());
260-
$this->assertEquals('2010-06-02T03:04:05Z', $form->getViewData());
260+
$this->assertEquals('2010-06-02T03:04:05', $form->getViewData());
261261
}
262262

263263
public function testSubmitDifferentPattern()

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.