/
DateField.php
278 lines (242 loc) · 9.53 KB
/
DateField.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
<?php
namespace Symfony\Component\Form;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use Symfony\Component\Form\ValueTransformer\ReversedTransformer;
use Symfony\Component\Form\ValueTransformer\DateTimeToStringTransformer;
use Symfony\Component\Form\ValueTransformer\DateTimeToTimestampTransformer;
use Symfony\Component\Form\ValueTransformer\ValueTransformerChain;
use Symfony\Component\Form\ValueTransformer\DateTimeToLocalizedStringTransformer;
use Symfony\Component\Form\ValueTransformer\DateTimeToArrayTransformer;
class DateField extends HybridField
{
const FULL = 'full';
const LONG = 'long';
const MEDIUM = 'medium';
const SHORT = 'short';
const DATETIME = 'datetime';
const STRING = 'string';
const TIMESTAMP = 'timestamp';
const RAW = 'raw';
const INPUT = 'input';
const CHOICE = 'choice';
protected static $formats = array(
self::FULL,
self::LONG,
self::MEDIUM,
self::SHORT,
);
protected static $intlFormats = array(
self::FULL => \IntlDateFormatter::FULL,
self::LONG => \IntlDateFormatter::LONG,
self::MEDIUM => \IntlDateFormatter::MEDIUM,
self::SHORT => \IntlDateFormatter::SHORT,
);
protected static $widgets = array(
self::INPUT,
self::CHOICE,
);
protected static $types = array(
self::DATETIME,
self::STRING,
self::TIMESTAMP,
self::RAW,
);
/**
* The ICU formatter instance
* @var \IntlDateFormatter
*/
protected $formatter;
/**
* Configures the text field.
*
* Available options:
*
* * widget: How to render the field ("input" or "select"). Default: "input"
* * years: An array of years for the year select tag (optional)
* * months: An array of months for the month select tag (optional)
* * days: An array of days for the day select tag (optional)
* * format: See DateValueTransformer. Default: medium
* * type: The type of the date ("date", "datetime" or "timestamp"). Default: "date"
* * data_timezone: The timezone of the data
* * user_timezone: The timezone of the user entering a new value
* * pattern: The pattern for the select boxes when "widget" is "select".
* You can use the placeholders "{{ year }}", "{{ month }}" and "{{ day }}".
* Default: locale dependent
*
* @param array $options Options for this field
* @throws \InvalidArgumentException Thrown if you want to show a timestamp with the select widget.
*/
protected function configure()
{
$this->addOption('years', range(date('Y') - 5, date('Y') + 5));
$this->addOption('months', range(1, 12));
$this->addOption('days', range(1, 31));
$this->addOption('format', self::MEDIUM, self::$formats);
$this->addOption('type', self::DATETIME, self::$types);
$this->addOption('data_timezone', 'UTC');
$this->addOption('user_timezone', 'UTC');
$this->addOption('widget', self::CHOICE, self::$widgets);
$this->addOption('pattern');
$this->formatter = new \IntlDateFormatter(
$this->locale,
self::$intlFormats[$this->getOption('format')],
\IntlDateFormatter::NONE
);
if ($this->getOption('type') === self::STRING) {
$this->setNormalizationTransformer(new ReversedTransformer(
new DateTimeToStringTransformer(array(
'input_timezone' => $this->getOption('data_timezone'),
'output_timezone' => $this->getOption('data_timezone'),
'format' => 'Y-m-d',
))
));
} else if ($this->getOption('type') === self::TIMESTAMP) {
$this->setNormalizationTransformer(new ReversedTransformer(
new DateTimeToTimestampTransformer(array(
'output_timezone' => $this->getOption('data_timezone'),
'input_timezone' => $this->getOption('data_timezone'),
))
));
} else if ($this->getOption('type') === self::RAW) {
$this->setNormalizationTransformer(new ReversedTransformer(
new DateTimeToArrayTransformer(array(
'input_timezone' => $this->getOption('data_timezone'),
'output_timezone' => $this->getOption('data_timezone'),
'fields' => array('year', 'month', 'day'),
))
));
}
if ($this->getOption('widget') === self::INPUT) {
$this->setValueTransformer(new DateTimeToLocalizedStringTransformer(array(
'date_format' => $this->getOption('format'),
'time_format' => DateTimeToLocalizedStringTransformer::NONE,
'input_timezone' => $this->getOption('data_timezone'),
'output_timezone' => $this->getOption('user_timezone'),
)));
$this->setFieldMode(self::FIELD);
} else {
$this->setValueTransformer(new DateTimeToArrayTransformer(array(
'input_timezone' => $this->getOption('data_timezone'),
'output_timezone' => $this->getOption('user_timezone'),
)));
$this->setFieldMode(self::GROUP);
$this->addChoiceFields();
}
}
/**
* Generates an array of choices for the given values
*
* If the values are shorter than $padLength characters, they are padded with
* zeros on the left side.
*
* @param array $values The available choices
* @param integer $padLength The length to pad the choices
* @return array An array with the input values as keys and the
* padded values as values
*/
protected function generatePaddedChoices(array $values, $padLength)
{
$choices = array();
foreach ($values as $value) {
$choices[$value] = str_pad($value, $padLength, '0', STR_PAD_LEFT);
}
return $choices;
}
/**
* Generates an array of localized month choices
*
* @param array $months The month numbers to generate
* @return array The localized months respecting the configured
* locale and date format
*/
protected function generateMonthChoices(array $months)
{
$pattern = $this->formatter->getPattern();
if (preg_match('/M+/', $pattern, $matches)) {
$this->formatter->setPattern($matches[0]);
$choices = array();
foreach ($months as $month) {
$choices[$month] = $this->formatter->format(gmmktime(0, 0, 0, $month));
}
$this->formatter->setPattern($pattern);
} else {
$choices = $this->generatePaddedChoices($months, 2);
}
return $choices;
}
public function getPattern()
{
// set order as specified in the pattern
if ($this->getOption('pattern')) {
return $this->getOption('pattern');
}
// set right order with respect to locale (e.g.: de_DE=dd.MM.yy; en_US=M/d/yy)
// lookup various formats at http://userguide.icu-project.org/formatparse/datetime
if (preg_match('/^([yMd]+).+([yMd]+).+([yMd]+)$/', $this->formatter->getPattern())) {
return preg_replace(array('/y+/', '/M+/', '/d+/'), array('{{ year }}', '{{ month }}', '{{ day }}'), $this->formatter->getPattern());
}
// default fallback
return '{{ year }}-{{ month }}-{{ day }}';
}
/**
* Adds (or replaces if already added) the fields used when widget=CHOICE
*/
protected function addChoiceFields()
{
$this->add(new ChoiceField('year', array(
'choices' => $this->generatePaddedChoices($this->getOption('years'), 4),
)));
$this->add(new ChoiceField('month', array(
'choices' => $this->generateMonthChoices($this->getOption('months')),
)));
$this->add(new ChoiceField('day', array(
'choices' => $this->generatePaddedChoices($this->getOption('days'), 2),
)));
}
/**
* Returns whether the year of the field's data is valid
*
* The year is valid if it is contained in the list passed to the field's
* option "years".
*
* @return boolean
*/
public function isYearWithinRange()
{
$date = $this->getNormalizedData();
return null === $date || in_array($date->format('Y'), $this->getOption('years'));
}
/**
* Returns whether the month of the field's data is valid
*
* The month is valid if it is contained in the list passed to the field's
* option "months".
*
* @return boolean
*/
public function isMonthWithinRange()
{
$date = $this->getNormalizedData();
return null === $date || in_array($date->format('m'), $this->getOption('months'));
}
/**
* Returns whether the day of the field's data is valid
*
* The day is valid if it is contained in the list passed to the field's
* option "days".
*
* @return boolean
*/
public function isDayWithinRange()
{
$date = $this->getNormalizedData();
return null === $date || in_array($date->format('d'), $this->getOption('days'));
}
}