]> git.immae.eu Git - github/wallabag/wallabag.git/blob - vendor/symfony/intl/Symfony/Component/Intl/DateFormatter/DateFormat/FullTransformer.php
b89db3630e03d2322be59eedf44954ec994aed9e
[github/wallabag/wallabag.git] / vendor / symfony / intl / Symfony / Component / Intl / DateFormatter / DateFormat / FullTransformer.php
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\Intl\DateFormatter\DateFormat;
13
14 use Symfony\Component\Intl\Exception\NotImplementedException;
15 use Symfony\Component\Intl\Globals\IntlGlobals;
16 use Symfony\Component\Intl\DateFormatter\DateFormat\MonthTransformer;
17
18 /**
19 * Parser and formatter for date formats
20 *
21 * @author Igor Wiedler <igor@wiedler.ch>
22 */
23 class FullTransformer
24 {
25 private $quoteMatch = "'(?:[^']+|'')*'";
26 private $implementedChars = 'MLydQqhDEaHkKmsz';
27 private $notImplementedChars = 'GYuwWFgecSAZvVW';
28 private $regExp;
29
30 /**
31 * @var Transformer[]
32 */
33 private $transformers;
34
35 private $pattern;
36 private $timezone;
37
38 /**
39 * Constructor
40 *
41 * @param string $pattern The pattern to be used to format and/or parse values
42 * @param string $timezone The timezone to perform the date/time calculations
43 */
44 public function __construct($pattern, $timezone)
45 {
46 $this->pattern = $pattern;
47 $this->timezone = $timezone;
48
49 $implementedCharsMatch = $this->buildCharsMatch($this->implementedChars);
50 $notImplementedCharsMatch = $this->buildCharsMatch($this->notImplementedChars);
51 $this->regExp = "/($this->quoteMatch|$implementedCharsMatch|$notImplementedCharsMatch)/";
52
53 $this->transformers = array(
54 'M' => new MonthTransformer(),
55 'L' => new MonthTransformer(),
56 'y' => new YearTransformer(),
57 'd' => new DayTransformer(),
58 'q' => new QuarterTransformer(),
59 'Q' => new QuarterTransformer(),
60 'h' => new Hour1201Transformer(),
61 'D' => new DayOfYearTransformer(),
62 'E' => new DayOfWeekTransformer(),
63 'a' => new AmPmTransformer(),
64 'H' => new Hour2400Transformer(),
65 'K' => new Hour1200Transformer(),
66 'k' => new Hour2401Transformer(),
67 'm' => new MinuteTransformer(),
68 's' => new SecondTransformer(),
69 'z' => new TimeZoneTransformer(),
70 );
71 }
72
73 /**
74 * Return the array of Transformer objects
75 *
76 * @return Transformer[] Associative array of Transformer objects (format char => Transformer)
77 */
78 public function getTransformers()
79 {
80 return $this->transformers;
81 }
82
83 /**
84 * Format a DateTime using ICU dateformat pattern
85 *
86 * @param \DateTime $dateTime A DateTime object to be used to generate the formatted value
87 *
88 * @return string The formatted value
89 */
90 public function format(\DateTime $dateTime)
91 {
92 $that = $this;
93
94 $formatted = preg_replace_callback($this->regExp, function($matches) use ($that, $dateTime) {
95 return $that->formatReplace($matches[0], $dateTime);
96 }, $this->pattern);
97
98 return $formatted;
99 }
100
101 /**
102 * Return the formatted ICU value for the matched date characters
103 *
104 * @param string $dateChars The date characters to be replaced with a formatted ICU value
105 * @param DateTime $dateTime A DateTime object to be used to generate the formatted value
106 *
107 * @return string The formatted value
108 *
109 * @throws NotImplementedException When it encounters a not implemented date character
110 */
111 public function formatReplace($dateChars, $dateTime)
112 {
113 $length = strlen($dateChars);
114
115 if ($this->isQuoteMatch($dateChars)) {
116 return $this->replaceQuoteMatch($dateChars);
117 }
118
119 if (isset($this->transformers[$dateChars[0]])) {
120 $transformer = $this->transformers[$dateChars[0]];
121
122 return $transformer->format($dateTime, $length);
123 }
124
125 // handle unimplemented characters
126 if (false !== strpos($this->notImplementedChars, $dateChars[0])) {
127 throw new NotImplementedException(sprintf("Unimplemented date character '%s' in format '%s'", $dateChars[0], $this->pattern));
128 }
129 }
130
131 /**
132 * Parse a pattern based string to a timestamp value
133 *
134 * @param \DateTime $dateTime A configured DateTime object to use to perform the date calculation
135 * @param string $value String to convert to a time value
136 *
137 * @return int The corresponding Unix timestamp
138 *
139 * @throws \InvalidArgumentException When the value can not be matched with pattern
140 */
141 public function parse(\DateTime $dateTime, $value)
142 {
143 $reverseMatchingRegExp = $this->getReverseMatchingRegExp($this->pattern);
144 $reverseMatchingRegExp = '/^'.$reverseMatchingRegExp.'$/';
145
146 $options = array();
147
148 if (preg_match($reverseMatchingRegExp, $value, $matches)) {
149 $matches = $this->normalizeArray($matches);
150
151 foreach ($this->transformers as $char => $transformer) {
152 if (isset($matches[$char])) {
153 $length = strlen($matches[$char]['pattern']);
154 $options = array_merge($options, $transformer->extractDateOptions($matches[$char]['value'], $length));
155 }
156 }
157
158 // reset error code and message
159 IntlGlobals::setError(IntlGlobals::U_ZERO_ERROR);
160
161 return $this->calculateUnixTimestamp($dateTime, $options);
162 }
163
164 // behave like the intl extension
165 IntlGlobals::setError(IntlGlobals::U_PARSE_ERROR, 'Date parsing failed');
166
167 return false;
168 }
169
170 /**
171 * Retrieve a regular expression to match with a formatted value.
172 *
173 * @param string $pattern The pattern to create the reverse matching regular expression
174 *
175 * @return string The reverse matching regular expression with named captures being formed by the
176 * transformer index in the $transformer array
177 */
178 public function getReverseMatchingRegExp($pattern)
179 {
180 $that = $this;
181
182 $escapedPattern = preg_quote($pattern, '/');
183
184 // ICU 4.8 recognizes slash ("/") in a value to be parsed as a dash ("-") and vice-versa
185 // when parsing a date/time value
186 $escapedPattern = preg_replace('/\\\[\-|\/]/', '[\/\-]', $escapedPattern);
187
188 $reverseMatchingRegExp = preg_replace_callback($this->regExp, function($matches) use ($that) {
189 $length = strlen($matches[0]);
190 $transformerIndex = $matches[0][0];
191
192 $dateChars = $matches[0];
193 if ($that->isQuoteMatch($dateChars)) {
194 return $that->replaceQuoteMatch($dateChars);
195 }
196
197 $transformers = $that->getTransformers();
198 if (isset($transformers[$transformerIndex])) {
199 $transformer = $transformers[$transformerIndex];
200 $captureName = str_repeat($transformerIndex, $length);
201
202 return "(?P<$captureName>".$transformer->getReverseMatchingRegExp($length).')';
203 }
204 }, $escapedPattern);
205
206 return $reverseMatchingRegExp;
207 }
208
209 /**
210 * Check if the first char of a string is a single quote
211 *
212 * @param string $quoteMatch The string to check
213 *
214 * @return Boolean true if matches, false otherwise
215 */
216 public function isQuoteMatch($quoteMatch)
217 {
218 return ("'" === $quoteMatch[0]);
219 }
220
221 /**
222 * Replaces single quotes at the start or end of a string with two single quotes
223 *
224 * @param string $quoteMatch The string to replace the quotes
225 *
226 * @return string A string with the single quotes replaced
227 */
228 public function replaceQuoteMatch($quoteMatch)
229 {
230 if (preg_match("/^'+$/", $quoteMatch)) {
231 return str_replace("''", "'", $quoteMatch);
232 }
233
234 return str_replace("''", "'", substr($quoteMatch, 1, -1));
235 }
236
237 /**
238 * Builds a chars match regular expression
239 *
240 * @param string $specialChars A string of chars to build the regular expression
241 *
242 * @return string The chars match regular expression
243 */
244 protected function buildCharsMatch($specialChars)
245 {
246 $specialCharsArray = str_split($specialChars);
247
248 $specialCharsMatch = implode('|', array_map(function($char) {
249 return $char.'+';
250 }, $specialCharsArray));
251
252 return $specialCharsMatch;
253 }
254
255 /**
256 * Normalize a preg_replace match array, removing the numeric keys and returning an associative array
257 * with the value and pattern values for the matched Transformer
258 *
259 * @param array $data
260 *
261 * @return array
262 */
263 protected function normalizeArray(array $data)
264 {
265 $ret = array();
266
267 foreach ($data as $key => $value) {
268 if (!is_string($key)) {
269 continue;
270 }
271
272 $ret[$key[0]] = array(
273 'value' => $value,
274 'pattern' => $key
275 );
276 }
277
278 return $ret;
279 }
280
281 /**
282 * Calculates the Unix timestamp based on the matched values by the reverse matching regular
283 * expression of parse()
284 *
285 * @param \DateTime $dateTime The DateTime object to be used to calculate the timestamp
286 * @param array $options An array with the matched values to be used to calculate the timestamp
287 *
288 * @return Boolean|int The calculated timestamp or false if matched date is invalid
289 */
290 protected function calculateUnixTimestamp(\DateTime $dateTime, array $options)
291 {
292 $options = $this->getDefaultValueForOptions($options);
293
294 $year = $options['year'];
295 $month = $options['month'];
296 $day = $options['day'];
297 $hour = $options['hour'];
298 $hourInstance = $options['hourInstance'];
299 $minute = $options['minute'];
300 $second = $options['second'];
301 $marker = $options['marker'];
302 $timezone = $options['timezone'];
303
304 // If month is false, return immediately (intl behavior)
305 if (false === $month) {
306 IntlGlobals::setError(IntlGlobals::U_PARSE_ERROR, 'Date parsing failed');
307
308 return false;
309 }
310
311 // Normalize hour
312 if ($hourInstance instanceof HourTransformer) {
313 $hour = $hourInstance->normalizeHour($hour, $marker);
314 }
315
316 // Set the timezone if different from the default one
317 if (null !== $timezone && $timezone !== $this->timezone) {
318 $dateTime->setTimezone(new \DateTimeZone($timezone));
319 }
320
321 // Normalize yy year
322 preg_match_all($this->regExp, $this->pattern, $matches);
323 if (in_array('yy', $matches[0])) {
324 $dateTime->setTimestamp(time());
325 $year = $year > $dateTime->format('y') + 20 ? 1900 + $year : 2000 + $year;
326 }
327
328 $dateTime->setDate($year, $month, $day);
329 $dateTime->setTime($hour, $minute, $second);
330
331 return $dateTime->getTimestamp();
332 }
333
334 /**
335 * Add sensible default values for missing items in the extracted date/time options array. The values
336 * are base in the beginning of the Unix era
337 *
338 * @param array $options
339 *
340 * @return array
341 */
342 private function getDefaultValueForOptions(array $options)
343 {
344 return array(
345 'year' => isset($options['year']) ? $options['year'] : 1970,
346 'month' => isset($options['month']) ? $options['month'] : 1,
347 'day' => isset($options['day']) ? $options['day'] : 1,
348 'hour' => isset($options['hour']) ? $options['hour'] : 0,
349 'hourInstance' => isset($options['hourInstance']) ? $options['hourInstance'] : null,
350 'minute' => isset($options['minute']) ? $options['minute'] : 0,
351 'second' => isset($options['second']) ? $options['second'] : 0,
352 'marker' => isset($options['marker']) ? $options['marker'] : null,
353 'timezone' => isset($options['timezone']) ? $options['timezone'] : null,
354 );
355 }
356 }