comparison vendor/sabre/vobject/lib/DateTimeParser.php @ 7:430dbd5346f7

vendor sabre as distributed
author Charlie Root
date Sat, 13 Jan 2018 09:06:10 -0500
parents
children
comparison
equal deleted inserted replaced
6:cec75ba50afc 7:430dbd5346f7
1 <?php
2
3 namespace Sabre\VObject;
4
5 use DateTime;
6 use DateTimeZone;
7 use DateInterval;
8 use InvalidArgumentException;
9 use LogicException;
10
11 /**
12 * DateTimeParser
13 *
14 * This class is responsible for parsing the several different date and time
15 * formats iCalendar and vCards have.
16 *
17 * @copyright Copyright (C) 2011-2015 fruux GmbH (https://fruux.com/).
18 * @author Evert Pot (http://evertpot.com/)
19 * @license http://sabre.io/license/ Modified BSD License
20 */
21 class DateTimeParser {
22
23 /**
24 * Parses an iCalendar (rfc5545) formatted datetime and returns a DateTime object
25 *
26 * Specifying a reference timezone is optional. It will only be used
27 * if the non-UTC format is used. The argument is used as a reference, the
28 * returned DateTime object will still be in the UTC timezone.
29 *
30 * @param string $dt
31 * @param DateTimeZone $tz
32 * @return DateTime
33 */
34 static public function parseDateTime($dt, DateTimeZone $tz = null) {
35
36 // Format is YYYYMMDD + "T" + hhmmss
37 $result = preg_match('/^([0-9]{4})([0-1][0-9])([0-3][0-9])T([0-2][0-9])([0-5][0-9])([0-5][0-9])([Z]?)$/',$dt,$matches);
38
39 if (!$result) {
40 throw new LogicException('The supplied iCalendar datetime value is incorrect: ' . $dt);
41 }
42
43 if ($matches[7]==='Z' || is_null($tz)) {
44 $tz = new DateTimeZone('UTC');
45 }
46 $date = new DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3] . ' ' . $matches[4] . ':' . $matches[5] .':' . $matches[6], $tz);
47
48 // Still resetting the timezone, to normalize everything to UTC
49 // $date->setTimeZone(new \DateTimeZone('UTC'));
50 return $date;
51
52 }
53
54 /**
55 * Parses an iCalendar (rfc5545) formatted date and returns a DateTime object.
56 *
57 * @param string $date
58 * @param DateTimeZone $tz
59 * @return DateTime
60 */
61 static public function parseDate($date, DateTimeZone $tz = null) {
62
63 // Format is YYYYMMDD
64 $result = preg_match('/^([0-9]{4})([0-1][0-9])([0-3][0-9])$/',$date,$matches);
65
66 if (!$result) {
67 throw new LogicException('The supplied iCalendar date value is incorrect: ' . $date);
68 }
69
70 if (is_null($tz)) {
71 $tz = new DateTimeZone('UTC');
72 }
73
74 $date = new DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3], $tz);
75 return $date;
76
77 }
78
79 /**
80 * Parses an iCalendar (RFC5545) formatted duration value.
81 *
82 * This method will either return a DateTimeInterval object, or a string
83 * suitable for strtotime or DateTime::modify.
84 *
85 * @param string $duration
86 * @param bool $asString
87 * @return DateInterval|string
88 */
89 static public function parseDuration($duration, $asString = false) {
90
91 $result = preg_match('/^(?P<plusminus>\+|-)?P((?P<week>\d+)W)?((?P<day>\d+)D)?(T((?P<hour>\d+)H)?((?P<minute>\d+)M)?((?P<second>\d+)S)?)?$/', $duration, $matches);
92 if (!$result) {
93 throw new LogicException('The supplied iCalendar duration value is incorrect: ' . $duration);
94 }
95
96 if (!$asString) {
97 $invert = false;
98 if ($matches['plusminus']==='-') {
99 $invert = true;
100 }
101
102
103 $parts = array(
104 'week',
105 'day',
106 'hour',
107 'minute',
108 'second',
109 );
110 foreach($parts as $part) {
111 $matches[$part] = isset($matches[$part])&&$matches[$part]?(int)$matches[$part]:0;
112 }
113
114
115 // We need to re-construct the $duration string, because weeks and
116 // days are not supported by DateInterval in the same string.
117 $duration = 'P';
118 $days = $matches['day'];
119 if ($matches['week']) {
120 $days+=$matches['week']*7;
121 }
122 if ($days)
123 $duration.=$days . 'D';
124
125 if ($matches['minute'] || $matches['second'] || $matches['hour']) {
126 $duration.='T';
127
128 if ($matches['hour'])
129 $duration.=$matches['hour'].'H';
130
131 if ($matches['minute'])
132 $duration.=$matches['minute'].'M';
133
134 if ($matches['second'])
135 $duration.=$matches['second'].'S';
136
137 }
138
139 if ($duration==='P') {
140 $duration = 'PT0S';
141 }
142 $iv = new DateInterval($duration);
143 if ($invert) $iv->invert = true;
144
145 return $iv;
146
147 }
148
149
150
151 $parts = array(
152 'week',
153 'day',
154 'hour',
155 'minute',
156 'second',
157 );
158
159 $newDur = '';
160 foreach($parts as $part) {
161 if (isset($matches[$part]) && $matches[$part]) {
162 $newDur.=' '.$matches[$part] . ' ' . $part . 's';
163 }
164 }
165
166 $newDur = ($matches['plusminus']==='-'?'-':'+') . trim($newDur);
167 if ($newDur === '+') {
168 $newDur = '+0 seconds';
169 };
170 return $newDur;
171
172 }
173
174 /**
175 * Parses either a Date or DateTime, or Duration value.
176 *
177 * @param string $date
178 * @param DateTimeZone|string $referenceTz
179 * @return DateTime|DateInterval
180 */
181 static public function parse($date, $referenceTz = null) {
182
183 if ($date[0]==='P' || ($date[0]==='-' && $date[1]==='P')) {
184 return self::parseDuration($date);
185 } elseif (strlen($date)===8) {
186 return self::parseDate($date, $referenceTz);
187 } else {
188 return self::parseDateTime($date, $referenceTz);
189 }
190
191 }
192
193 /**
194 * This method parses a vCard date and or time value.
195 *
196 * This can be used for the DATE, DATE-TIME, TIMESTAMP and
197 * DATE-AND-OR-TIME value.
198 *
199 * This method returns an array, not a DateTime value.
200 *
201 * The elements in the array are in the following order:
202 * year, month, date, hour, minute, second, timezone
203 *
204 * Almost any part of the string may be omitted. It's for example legal to
205 * just specify seconds, leave out the year, etc.
206 *
207 * Timezone is either returned as 'Z' or as '+08:00'
208 *
209 * For any non-specified values null is returned.
210 *
211 * List of date formats that are supported:
212 * YYYY
213 * YYYY-MM
214 * YYYYMMDD
215 * --MMDD
216 * ---DD
217 *
218 * YYYY-MM-DD
219 * --MM-DD
220 * ---DD
221 *
222 * List of supported time formats:
223 *
224 * HH
225 * HHMM
226 * HHMMSS
227 * -MMSS
228 * --SS
229 *
230 * HH
231 * HH:MM
232 * HH:MM:SS
233 * -MM:SS
234 * --SS
235 *
236 * A full basic-format date-time string looks like :
237 * 20130603T133901
238 *
239 * A full extended-format date-time string looks like :
240 * 2013-06-03T13:39:01
241 *
242 * Times may be postfixed by a timezone offset. This can be either 'Z' for
243 * UTC, or a string like -0500 or +1100.
244 *
245 * @param string $date
246 * @return array
247 */
248 static public function parseVCardDateTime($date) {
249
250 $regex = '/^
251 (?: # date part
252 (?:
253 (?: (?P<year> [0-9]{4}) (?: -)?| --)
254 (?P<month> [0-9]{2})?
255 |---)
256 (?P<date> [0-9]{2})?
257 )?
258 (?:T # time part
259 (?P<hour> [0-9]{2} | -)
260 (?P<minute> [0-9]{2} | -)?
261 (?P<second> [0-9]{2})?
262
263 (?P<timezone> # timezone offset
264
265 Z | (?: \+|-)(?: [0-9]{4})
266
267 )?
268
269 )?
270 $/x';
271
272
273 if (!preg_match($regex, $date, $matches)) {
274
275 // Attempting to parse the extended format.
276 $regex = '/^
277 (?: # date part
278 (?: (?P<year> [0-9]{4}) - | -- )
279 (?P<month> [0-9]{2}) -
280 (?P<date> [0-9]{2})
281 )?
282 (?:T # time part
283
284 (?: (?P<hour> [0-9]{2}) : | -)
285 (?: (?P<minute> [0-9]{2}) : | -)?
286 (?P<second> [0-9]{2})?
287
288 (?P<timezone> # timezone offset
289
290 Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2})
291
292 )?
293
294 )?
295 $/x';
296
297 if (!preg_match($regex, $date, $matches)) {
298 throw new InvalidArgumentException('Invalid vCard date-time string: ' . $date);
299 }
300
301 }
302 $parts = array(
303 'year',
304 'month',
305 'date',
306 'hour',
307 'minute',
308 'second',
309 'timezone'
310 );
311
312 $result = array();
313 foreach($parts as $part) {
314
315 if (empty($matches[$part])) {
316 $result[$part] = null;
317 } elseif ($matches[$part] === '-' || $matches[$part] === '--') {
318 $result[$part] = null;
319 } else {
320 $result[$part] = $matches[$part];
321 }
322
323 }
324
325 return $result;
326
327 }
328
329 /**
330 * This method parses a vCard TIME value.
331 *
332 * This method returns an array, not a DateTime value.
333 *
334 * The elements in the array are in the following order:
335 * hour, minute, second, timezone
336 *
337 * Almost any part of the string may be omitted. It's for example legal to
338 * just specify seconds, leave out the hour etc.
339 *
340 * Timezone is either returned as 'Z' or as '+08:00'
341 *
342 * For any non-specified values null is returned.
343 *
344 * List of supported time formats:
345 *
346 * HH
347 * HHMM
348 * HHMMSS
349 * -MMSS
350 * --SS
351 *
352 * HH
353 * HH:MM
354 * HH:MM:SS
355 * -MM:SS
356 * --SS
357 *
358 * A full basic-format time string looks like :
359 * 133901
360 *
361 * A full extended-format time string looks like :
362 * 13:39:01
363 *
364 * Times may be postfixed by a timezone offset. This can be either 'Z' for
365 * UTC, or a string like -0500 or +11:00.
366 *
367 * @param string $date
368 * @return array
369 */
370 static public function parseVCardTime($date) {
371
372 $regex = '/^
373 (?P<hour> [0-9]{2} | -)
374 (?P<minute> [0-9]{2} | -)?
375 (?P<second> [0-9]{2})?
376
377 (?P<timezone> # timezone offset
378
379 Z | (?: \+|-)(?: [0-9]{4})
380
381 )?
382 $/x';
383
384
385 if (!preg_match($regex, $date, $matches)) {
386
387 // Attempting to parse the extended format.
388 $regex = '/^
389 (?: (?P<hour> [0-9]{2}) : | -)
390 (?: (?P<minute> [0-9]{2}) : | -)?
391 (?P<second> [0-9]{2})?
392
393 (?P<timezone> # timezone offset
394
395 Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2})
396
397 )?
398 $/x';
399
400 if (!preg_match($regex, $date, $matches)) {
401 throw new InvalidArgumentException('Invalid vCard time string: ' . $date);
402 }
403
404 }
405 $parts = array(
406 'hour',
407 'minute',
408 'second',
409 'timezone'
410 );
411
412 $result = array();
413 foreach($parts as $part) {
414
415 if (empty($matches[$part])) {
416 $result[$part] = null;
417 } elseif ($matches[$part] === '-') {
418 $result[$part] = null;
419 } else {
420 $result[$part] = $matches[$part];
421 }
422
423 }
424
425 return $result;
426
427 }
428 }