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