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