4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\Form\Extension\Core\Type
;
14 use Symfony\Component\Form\AbstractType
;
15 use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
;
16 use Symfony\Component\Form\FormInterface
;
17 use Symfony\Component\Form\FormBuilderInterface
;
18 use Symfony\Component\Form\FormView
;
19 use Symfony\Component\Form\ReversedTransformer
;
20 use Symfony\Component\Form\Extension\Core\DataTransformer\DataTransformerChain
;
21 use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer
;
22 use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer
;
23 use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer
;
24 use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer
;
25 use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToRfc3339Transformer
;
26 use Symfony\Component\Form\Extension\Core\DataTransformer\ArrayToPartsTransformer
;
27 use Symfony\Component\OptionsResolver\Options
;
28 use Symfony\Component\OptionsResolver\OptionsResolverInterface
;
30 class DateTimeType
extends AbstractType
32 const DEFAULT_DATE_FORMAT
= \IntlDateFormatter
::MEDIUM
;
34 const DEFAULT_TIME_FORMAT
= \IntlDateFormatter
::MEDIUM
;
37 * This is not quite the HTML5 format yet, because ICU lacks the
38 * capability of parsing and generating RFC 3339 dates, which
39 * are like the below pattern but with a timezone suffix. The
43 * * "(-|+)HH:mm" for other timezones (note the colon!)
45 * For more information see:
47 * http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax
48 * http://www.w3.org/TR/html-markup/input.datetime.html
49 * http://tools.ietf.org/html/rfc3339
51 * An ICU ticket was created:
52 * http://icu-project.org/trac/ticket/9421
54 * It was supposedly fixed, but is not available in all PHP installations
55 * yet. To temporarily circumvent this issue, DateTimeToRfc3339Transformer
56 * is used when the format matches this constant.
58 const HTML5_FORMAT
= "yyyy-MM-dd'T'HH:mm:ssZZZZZ";
60 private static $acceptedFormats = array(
61 \IntlDateFormatter
::FULL
,
62 \IntlDateFormatter
::LONG
,
63 \IntlDateFormatter
::MEDIUM
,
64 \IntlDateFormatter
::SHORT
,
70 public function buildForm(FormBuilderInterface
$builder, array $options)
72 $parts = array('year', 'month', 'day', 'hour');
73 $dateParts = array('year', 'month', 'day');
74 $timeParts = array('hour');
76 if ($options['with_minutes']) {
78 $timeParts[] = 'minute';
81 if ($options['with_seconds']) {
83 $timeParts[] = 'second';
86 $dateFormat = is_int($options['date_format']) ? $options['date_format'] : self
::DEFAULT_DATE_FORMAT
;
87 $timeFormat = self
::DEFAULT_TIME_FORMAT
;
88 $calendar = \IntlDateFormatter
::GREGORIAN
;
89 $pattern = is_string($options['format']) ? $options['format'] : null;
91 if (!in_array($dateFormat, self
::$acceptedFormats, true)) {
92 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.');
95 if ('single_text' === $options['widget']) {
96 if (self
::HTML5_FORMAT
=== $pattern) {
97 $builder->addViewTransformer(new DateTimeToRfc3339Transformer(
98 $options['model_timezone'],
99 $options['view_timezone']
102 $builder->addViewTransformer(new DateTimeToLocalizedStringTransformer(
103 $options['model_timezone'],
104 $options['view_timezone'],
112 // Only pass a subset of the options to children
113 $dateOptions = array_intersect_key($options, array_flip(array(
119 'translation_domain',
122 $timeOptions = array_intersect_key($options, array_flip(array(
130 'translation_domain',
133 if (null !== $options['date_widget']) {
134 $dateOptions['widget'] = $options['date_widget'];
137 if (null !== $options['time_widget']) {
138 $timeOptions['widget'] = $options['time_widget'];
141 if (null !== $options['date_format']) {
142 $dateOptions['format'] = $options['date_format'];
145 $dateOptions['input'] = $timeOptions['input'] = 'array';
146 $dateOptions['error_bubbling'] = $timeOptions['error_bubbling'] = true;
149 ->addViewTransformer(new DataTransformerChain(array(
150 new DateTimeToArrayTransformer($options['model_timezone'], $options['view_timezone'], $parts),
151 new ArrayToPartsTransformer(array(
152 'date' => $dateParts,
153 'time' => $timeParts,
156 ->add('date', 'date', $dateOptions)
157 ->add('time', 'time', $timeOptions)
161 if ('string' === $options['input']) {
162 $builder->addModelTransformer(new ReversedTransformer(
163 new DateTimeToStringTransformer($options['model_timezone'], $options['model_timezone'])
165 } elseif ('timestamp' === $options['input']) {
166 $builder->addModelTransformer(new ReversedTransformer(
167 new DateTimeToTimestampTransformer($options['model_timezone'], $options['model_timezone'])
169 } elseif ('array' === $options['input']) {
170 $builder->addModelTransformer(new ReversedTransformer(
171 new DateTimeToArrayTransformer($options['model_timezone'], $options['model_timezone'], $parts)
179 public function buildView(FormView
$view, FormInterface
$form, array $options)
181 $view->vars
['widget'] = $options['widget'];
183 // Change the input to a HTML5 date input if
184 // * the widget is set to "single_text"
185 // * the format matches the one expected by HTML5
186 if ('single_text' === $options['widget'] && self
::HTML5_FORMAT
=== $options['format']) {
187 $view->vars
['type'] = 'datetime';
194 public function setDefaultOptions(OptionsResolverInterface
$resolver)
196 $compound = function (Options
$options) {
197 return $options['widget'] !== 'single_text';
200 // Defaults to the value of "widget"
201 $dateWidget = function (Options
$options) {
202 return $options['widget'];
205 // Defaults to the value of "widget"
206 $timeWidget = function (Options
$options) {
207 return $options['widget'];
210 $resolver->setDefaults(array(
211 'input' => 'datetime',
212 'model_timezone' => null,
213 'view_timezone' => null,
214 'format' => self
::HTML5_FORMAT
,
215 'date_format' => null,
217 'date_widget' => $dateWidget,
218 'time_widget' => $timeWidget,
219 'with_minutes' => true,
220 'with_seconds' => false,
221 // Don't modify \DateTime classes by reference, we treat
222 // them like immutable value objects
223 'by_reference' => false,
224 'error_bubbling' => false,
225 // If initialized with a \DateTime object, FormType initializes
226 // this option to "\DateTime". Since the internal, normalized
227 // representation is not \DateTime, but an array, we need to unset
229 'data_class' => null,
230 'compound' => $compound,
233 // Don't add some defaults in order to preserve the defaults
234 // set in DateType and TimeType
235 $resolver->setOptional(array(
245 $resolver->setAllowedValues(array(
252 'date_widget' => array(
253 null, // inherit default from DateType
258 'time_widget' => array(
259 null, // inherit default from TimeType
264 // This option will overwrite "date_widget" and "time_widget" options
266 null, // default, don't overwrite options
277 public function getName()