1*a1a3b679SAndreas Boehler<?php 2*a1a3b679SAndreas Boehler 3*a1a3b679SAndreas Boehlernamespace Sabre\VObject\Property\ICalendar; 4*a1a3b679SAndreas Boehler 5*a1a3b679SAndreas Boehleruse DateTimeZone; 6*a1a3b679SAndreas Boehleruse Sabre\VObject\Property; 7*a1a3b679SAndreas Boehleruse Sabre\VObject\DateTimeParser; 8*a1a3b679SAndreas Boehleruse Sabre\VObject\TimeZoneUtil; 9*a1a3b679SAndreas Boehler 10*a1a3b679SAndreas Boehler/** 11*a1a3b679SAndreas Boehler * DateTime property 12*a1a3b679SAndreas Boehler * 13*a1a3b679SAndreas Boehler * This object represents DATE-TIME values, as defined here: 14*a1a3b679SAndreas Boehler * 15*a1a3b679SAndreas Boehler * http://tools.ietf.org/html/rfc5545#section-3.3.4 16*a1a3b679SAndreas Boehler * 17*a1a3b679SAndreas Boehler * This particular object has a bit of hackish magic that it may also in some 18*a1a3b679SAndreas Boehler * cases represent a DATE value. This is because it's a common usecase to be 19*a1a3b679SAndreas Boehler * able to change a DATE-TIME into a DATE. 20*a1a3b679SAndreas Boehler * 21*a1a3b679SAndreas Boehler * @copyright Copyright (C) 2011-2015 fruux GmbH (https://fruux.com/). 22*a1a3b679SAndreas Boehler * @author Evert Pot (http://evertpot.com/) 23*a1a3b679SAndreas Boehler * @license http://sabre.io/license/ Modified BSD License 24*a1a3b679SAndreas Boehler */ 25*a1a3b679SAndreas Boehlerclass DateTime extends Property { 26*a1a3b679SAndreas Boehler 27*a1a3b679SAndreas Boehler /** 28*a1a3b679SAndreas Boehler * In case this is a multi-value property. This string will be used as a 29*a1a3b679SAndreas Boehler * delimiter. 30*a1a3b679SAndreas Boehler * 31*a1a3b679SAndreas Boehler * @var string|null 32*a1a3b679SAndreas Boehler */ 33*a1a3b679SAndreas Boehler public $delimiter = ','; 34*a1a3b679SAndreas Boehler 35*a1a3b679SAndreas Boehler /** 36*a1a3b679SAndreas Boehler * Sets a multi-valued property. 37*a1a3b679SAndreas Boehler * 38*a1a3b679SAndreas Boehler * You may also specify DateTime objects here. 39*a1a3b679SAndreas Boehler * 40*a1a3b679SAndreas Boehler * @param array $parts 41*a1a3b679SAndreas Boehler * @return void 42*a1a3b679SAndreas Boehler */ 43*a1a3b679SAndreas Boehler public function setParts(array $parts) { 44*a1a3b679SAndreas Boehler 45*a1a3b679SAndreas Boehler if (isset($parts[0]) && $parts[0] instanceof \DateTime) { 46*a1a3b679SAndreas Boehler $this->setDateTimes($parts); 47*a1a3b679SAndreas Boehler } else { 48*a1a3b679SAndreas Boehler parent::setParts($parts); 49*a1a3b679SAndreas Boehler } 50*a1a3b679SAndreas Boehler 51*a1a3b679SAndreas Boehler } 52*a1a3b679SAndreas Boehler 53*a1a3b679SAndreas Boehler /** 54*a1a3b679SAndreas Boehler * Updates the current value. 55*a1a3b679SAndreas Boehler * 56*a1a3b679SAndreas Boehler * This may be either a single, or multiple strings in an array. 57*a1a3b679SAndreas Boehler * 58*a1a3b679SAndreas Boehler * Instead of strings, you may also use DateTime here. 59*a1a3b679SAndreas Boehler * 60*a1a3b679SAndreas Boehler * @param string|array|\DateTime $value 61*a1a3b679SAndreas Boehler * @return void 62*a1a3b679SAndreas Boehler */ 63*a1a3b679SAndreas Boehler public function setValue($value) { 64*a1a3b679SAndreas Boehler 65*a1a3b679SAndreas Boehler if (is_array($value) && isset($value[0]) && $value[0] instanceof \DateTime) { 66*a1a3b679SAndreas Boehler $this->setDateTimes($value); 67*a1a3b679SAndreas Boehler } elseif ($value instanceof \DateTime) { 68*a1a3b679SAndreas Boehler $this->setDateTimes(array($value)); 69*a1a3b679SAndreas Boehler } else { 70*a1a3b679SAndreas Boehler parent::setValue($value); 71*a1a3b679SAndreas Boehler } 72*a1a3b679SAndreas Boehler 73*a1a3b679SAndreas Boehler } 74*a1a3b679SAndreas Boehler 75*a1a3b679SAndreas Boehler /** 76*a1a3b679SAndreas Boehler * Sets a raw value coming from a mimedir (iCalendar/vCard) file. 77*a1a3b679SAndreas Boehler * 78*a1a3b679SAndreas Boehler * This has been 'unfolded', so only 1 line will be passed. Unescaping is 79*a1a3b679SAndreas Boehler * not yet done, but parameters are not included. 80*a1a3b679SAndreas Boehler * 81*a1a3b679SAndreas Boehler * @param string $val 82*a1a3b679SAndreas Boehler * @return void 83*a1a3b679SAndreas Boehler */ 84*a1a3b679SAndreas Boehler public function setRawMimeDirValue($val) { 85*a1a3b679SAndreas Boehler 86*a1a3b679SAndreas Boehler $this->setValue(explode($this->delimiter, $val)); 87*a1a3b679SAndreas Boehler 88*a1a3b679SAndreas Boehler } 89*a1a3b679SAndreas Boehler 90*a1a3b679SAndreas Boehler /** 91*a1a3b679SAndreas Boehler * Returns a raw mime-dir representation of the value. 92*a1a3b679SAndreas Boehler * 93*a1a3b679SAndreas Boehler * @return string 94*a1a3b679SAndreas Boehler */ 95*a1a3b679SAndreas Boehler public function getRawMimeDirValue() { 96*a1a3b679SAndreas Boehler 97*a1a3b679SAndreas Boehler return implode($this->delimiter, $this->getParts()); 98*a1a3b679SAndreas Boehler 99*a1a3b679SAndreas Boehler } 100*a1a3b679SAndreas Boehler 101*a1a3b679SAndreas Boehler /** 102*a1a3b679SAndreas Boehler * Returns true if this is a DATE-TIME value, false if it's a DATE. 103*a1a3b679SAndreas Boehler * 104*a1a3b679SAndreas Boehler * @return bool 105*a1a3b679SAndreas Boehler */ 106*a1a3b679SAndreas Boehler public function hasTime() { 107*a1a3b679SAndreas Boehler 108*a1a3b679SAndreas Boehler return strtoupper((string)$this['VALUE']) !== 'DATE'; 109*a1a3b679SAndreas Boehler 110*a1a3b679SAndreas Boehler } 111*a1a3b679SAndreas Boehler 112*a1a3b679SAndreas Boehler /** 113*a1a3b679SAndreas Boehler * Returns true if this is a floating DATE or DATE-TIME. 114*a1a3b679SAndreas Boehler * 115*a1a3b679SAndreas Boehler * Note that DATE is always floating. 116*a1a3b679SAndreas Boehler */ 117*a1a3b679SAndreas Boehler public function isFloating() { 118*a1a3b679SAndreas Boehler 119*a1a3b679SAndreas Boehler return 120*a1a3b679SAndreas Boehler !$this->hasTime() || 121*a1a3b679SAndreas Boehler ( 122*a1a3b679SAndreas Boehler !isset($this['TZID']) && 123*a1a3b679SAndreas Boehler strpos($this->getValue(),'Z')===false 124*a1a3b679SAndreas Boehler ); 125*a1a3b679SAndreas Boehler 126*a1a3b679SAndreas Boehler } 127*a1a3b679SAndreas Boehler 128*a1a3b679SAndreas Boehler /** 129*a1a3b679SAndreas Boehler * Returns a date-time value. 130*a1a3b679SAndreas Boehler * 131*a1a3b679SAndreas Boehler * Note that if this property contained more than 1 date-time, only the 132*a1a3b679SAndreas Boehler * first will be returned. To get an array with multiple values, call 133*a1a3b679SAndreas Boehler * getDateTimes. 134*a1a3b679SAndreas Boehler * 135*a1a3b679SAndreas Boehler * If no timezone information is known, because it's either an all-day 136*a1a3b679SAndreas Boehler * property or floating time, we will use the DateTimeZone argument to 137*a1a3b679SAndreas Boehler * figure out the exact date. 138*a1a3b679SAndreas Boehler * 139*a1a3b679SAndreas Boehler * @param DateTimeZone $timeZone 140*a1a3b679SAndreas Boehler * @return \DateTime 141*a1a3b679SAndreas Boehler */ 142*a1a3b679SAndreas Boehler public function getDateTime(DateTimeZone $timeZone = null) { 143*a1a3b679SAndreas Boehler 144*a1a3b679SAndreas Boehler $dt = $this->getDateTimes($timeZone); 145*a1a3b679SAndreas Boehler if (!$dt) return null; 146*a1a3b679SAndreas Boehler 147*a1a3b679SAndreas Boehler return $dt[0]; 148*a1a3b679SAndreas Boehler 149*a1a3b679SAndreas Boehler } 150*a1a3b679SAndreas Boehler 151*a1a3b679SAndreas Boehler /** 152*a1a3b679SAndreas Boehler * Returns multiple date-time values. 153*a1a3b679SAndreas Boehler * 154*a1a3b679SAndreas Boehler * If no timezone information is known, because it's either an all-day 155*a1a3b679SAndreas Boehler * property or floating time, we will use the DateTimeZone argument to 156*a1a3b679SAndreas Boehler * figure out the exact date. 157*a1a3b679SAndreas Boehler * 158*a1a3b679SAndreas Boehler * @param DateTimeZone $timeZone 159*a1a3b679SAndreas Boehler * @return \DateTime[] 160*a1a3b679SAndreas Boehler */ 161*a1a3b679SAndreas Boehler public function getDateTimes(DateTimeZone $timeZone = null) { 162*a1a3b679SAndreas Boehler 163*a1a3b679SAndreas Boehler // Does the property have a TZID? 164*a1a3b679SAndreas Boehler $tzid = $this['TZID']; 165*a1a3b679SAndreas Boehler 166*a1a3b679SAndreas Boehler if ($tzid) { 167*a1a3b679SAndreas Boehler $timeZone = TimeZoneUtil::getTimeZone((string)$tzid, $this->root); 168*a1a3b679SAndreas Boehler } 169*a1a3b679SAndreas Boehler 170*a1a3b679SAndreas Boehler $dts = array(); 171*a1a3b679SAndreas Boehler foreach($this->getParts() as $part) { 172*a1a3b679SAndreas Boehler $dts[] = DateTimeParser::parse($part, $timeZone); 173*a1a3b679SAndreas Boehler } 174*a1a3b679SAndreas Boehler return $dts; 175*a1a3b679SAndreas Boehler 176*a1a3b679SAndreas Boehler } 177*a1a3b679SAndreas Boehler 178*a1a3b679SAndreas Boehler /** 179*a1a3b679SAndreas Boehler * Sets the property as a DateTime object. 180*a1a3b679SAndreas Boehler * 181*a1a3b679SAndreas Boehler * @param \DateTime $dt 182*a1a3b679SAndreas Boehler * @param bool isFloating If set to true, timezones will be ignored. 183*a1a3b679SAndreas Boehler * @return void 184*a1a3b679SAndreas Boehler */ 185*a1a3b679SAndreas Boehler public function setDateTime(\DateTime $dt, $isFloating = false) { 186*a1a3b679SAndreas Boehler 187*a1a3b679SAndreas Boehler $this->setDateTimes(array($dt), $isFloating); 188*a1a3b679SAndreas Boehler 189*a1a3b679SAndreas Boehler } 190*a1a3b679SAndreas Boehler 191*a1a3b679SAndreas Boehler /** 192*a1a3b679SAndreas Boehler * Sets the property as multiple date-time objects. 193*a1a3b679SAndreas Boehler * 194*a1a3b679SAndreas Boehler * The first value will be used as a reference for the timezones, and all 195*a1a3b679SAndreas Boehler * the otehr values will be adjusted for that timezone 196*a1a3b679SAndreas Boehler * 197*a1a3b679SAndreas Boehler * @param \DateTime[] $dt 198*a1a3b679SAndreas Boehler * @param bool isFloating If set to true, timezones will be ignored. 199*a1a3b679SAndreas Boehler * @return void 200*a1a3b679SAndreas Boehler */ 201*a1a3b679SAndreas Boehler public function setDateTimes(array $dt, $isFloating = false) { 202*a1a3b679SAndreas Boehler 203*a1a3b679SAndreas Boehler $values = array(); 204*a1a3b679SAndreas Boehler 205*a1a3b679SAndreas Boehler if($this->hasTime()) { 206*a1a3b679SAndreas Boehler 207*a1a3b679SAndreas Boehler $tz = null; 208*a1a3b679SAndreas Boehler $isUtc = false; 209*a1a3b679SAndreas Boehler 210*a1a3b679SAndreas Boehler foreach($dt as $d) { 211*a1a3b679SAndreas Boehler 212*a1a3b679SAndreas Boehler if ($isFloating) { 213*a1a3b679SAndreas Boehler $values[] = $d->format('Ymd\\THis'); 214*a1a3b679SAndreas Boehler continue; 215*a1a3b679SAndreas Boehler } 216*a1a3b679SAndreas Boehler if (is_null($tz)) { 217*a1a3b679SAndreas Boehler $tz = $d->getTimeZone(); 218*a1a3b679SAndreas Boehler $isUtc = in_array($tz->getName() , array('UTC', 'GMT', 'Z')); 219*a1a3b679SAndreas Boehler if (!$isUtc) { 220*a1a3b679SAndreas Boehler $this->offsetSet('TZID', $tz->getName()); 221*a1a3b679SAndreas Boehler } 222*a1a3b679SAndreas Boehler } else { 223*a1a3b679SAndreas Boehler $d->setTimeZone($tz); 224*a1a3b679SAndreas Boehler } 225*a1a3b679SAndreas Boehler 226*a1a3b679SAndreas Boehler if ($isUtc) { 227*a1a3b679SAndreas Boehler $values[] = $d->format('Ymd\\THis\\Z'); 228*a1a3b679SAndreas Boehler } else { 229*a1a3b679SAndreas Boehler $values[] = $d->format('Ymd\\THis'); 230*a1a3b679SAndreas Boehler } 231*a1a3b679SAndreas Boehler 232*a1a3b679SAndreas Boehler } 233*a1a3b679SAndreas Boehler if ($isUtc || $isFloating) { 234*a1a3b679SAndreas Boehler $this->offsetUnset('TZID'); 235*a1a3b679SAndreas Boehler } 236*a1a3b679SAndreas Boehler 237*a1a3b679SAndreas Boehler } else { 238*a1a3b679SAndreas Boehler 239*a1a3b679SAndreas Boehler foreach($dt as $d) { 240*a1a3b679SAndreas Boehler 241*a1a3b679SAndreas Boehler $values[] = $d->format('Ymd'); 242*a1a3b679SAndreas Boehler 243*a1a3b679SAndreas Boehler } 244*a1a3b679SAndreas Boehler $this->offsetUnset('TZID'); 245*a1a3b679SAndreas Boehler 246*a1a3b679SAndreas Boehler } 247*a1a3b679SAndreas Boehler 248*a1a3b679SAndreas Boehler $this->value = $values; 249*a1a3b679SAndreas Boehler 250*a1a3b679SAndreas Boehler } 251*a1a3b679SAndreas Boehler 252*a1a3b679SAndreas Boehler /** 253*a1a3b679SAndreas Boehler * Returns the type of value. 254*a1a3b679SAndreas Boehler * 255*a1a3b679SAndreas Boehler * This corresponds to the VALUE= parameter. Every property also has a 256*a1a3b679SAndreas Boehler * 'default' valueType. 257*a1a3b679SAndreas Boehler * 258*a1a3b679SAndreas Boehler * @return string 259*a1a3b679SAndreas Boehler */ 260*a1a3b679SAndreas Boehler public function getValueType() { 261*a1a3b679SAndreas Boehler 262*a1a3b679SAndreas Boehler return $this->hasTime()?'DATE-TIME':'DATE'; 263*a1a3b679SAndreas Boehler 264*a1a3b679SAndreas Boehler } 265*a1a3b679SAndreas Boehler 266*a1a3b679SAndreas Boehler /** 267*a1a3b679SAndreas Boehler * Returns the value, in the format it should be encoded for json. 268*a1a3b679SAndreas Boehler * 269*a1a3b679SAndreas Boehler * This method must always return an array. 270*a1a3b679SAndreas Boehler * 271*a1a3b679SAndreas Boehler * @return array 272*a1a3b679SAndreas Boehler */ 273*a1a3b679SAndreas Boehler public function getJsonValue() { 274*a1a3b679SAndreas Boehler 275*a1a3b679SAndreas Boehler $dts = $this->getDateTimes(); 276*a1a3b679SAndreas Boehler $hasTime = $this->hasTime(); 277*a1a3b679SAndreas Boehler $isFloating = $this->isFloating(); 278*a1a3b679SAndreas Boehler 279*a1a3b679SAndreas Boehler $tz = $dts[0]->getTimeZone(); 280*a1a3b679SAndreas Boehler $isUtc = $isFloating ? false : in_array($tz->getName() , array('UTC', 'GMT', 'Z')); 281*a1a3b679SAndreas Boehler 282*a1a3b679SAndreas Boehler return array_map( 283*a1a3b679SAndreas Boehler function($dt) use ($hasTime, $isUtc) { 284*a1a3b679SAndreas Boehler 285*a1a3b679SAndreas Boehler if ($hasTime) { 286*a1a3b679SAndreas Boehler return $dt->format('Y-m-d\\TH:i:s') . ($isUtc?'Z':''); 287*a1a3b679SAndreas Boehler } else { 288*a1a3b679SAndreas Boehler return $dt->format('Y-m-d'); 289*a1a3b679SAndreas Boehler } 290*a1a3b679SAndreas Boehler 291*a1a3b679SAndreas Boehler }, 292*a1a3b679SAndreas Boehler $dts 293*a1a3b679SAndreas Boehler ); 294*a1a3b679SAndreas Boehler 295*a1a3b679SAndreas Boehler } 296*a1a3b679SAndreas Boehler 297*a1a3b679SAndreas Boehler /** 298*a1a3b679SAndreas Boehler * Sets the json value, as it would appear in a jCard or jCal object. 299*a1a3b679SAndreas Boehler * 300*a1a3b679SAndreas Boehler * The value must always be an array. 301*a1a3b679SAndreas Boehler * 302*a1a3b679SAndreas Boehler * @param array $value 303*a1a3b679SAndreas Boehler * @return void 304*a1a3b679SAndreas Boehler */ 305*a1a3b679SAndreas Boehler public function setJsonValue(array $value) { 306*a1a3b679SAndreas Boehler 307*a1a3b679SAndreas Boehler // dates and times in jCal have one difference to dates and times in 308*a1a3b679SAndreas Boehler // iCalendar. In jCal date-parts are separated by dashes, and 309*a1a3b679SAndreas Boehler // time-parts are separated by colons. It makes sense to just remove 310*a1a3b679SAndreas Boehler // those. 311*a1a3b679SAndreas Boehler $this->setValue( 312*a1a3b679SAndreas Boehler array_map( 313*a1a3b679SAndreas Boehler function($item) { 314*a1a3b679SAndreas Boehler 315*a1a3b679SAndreas Boehler return strtr($item, array(':'=>'', '-'=>'')); 316*a1a3b679SAndreas Boehler 317*a1a3b679SAndreas Boehler }, 318*a1a3b679SAndreas Boehler $value 319*a1a3b679SAndreas Boehler ) 320*a1a3b679SAndreas Boehler ); 321*a1a3b679SAndreas Boehler 322*a1a3b679SAndreas Boehler } 323*a1a3b679SAndreas Boehler /** 324*a1a3b679SAndreas Boehler * We need to intercept offsetSet, because it may be used to alter the 325*a1a3b679SAndreas Boehler * VALUE from DATE-TIME to DATE or vice-versa. 326*a1a3b679SAndreas Boehler * 327*a1a3b679SAndreas Boehler * @param string $name 328*a1a3b679SAndreas Boehler * @param mixed $value 329*a1a3b679SAndreas Boehler * @return void 330*a1a3b679SAndreas Boehler */ 331*a1a3b679SAndreas Boehler public function offsetSet($name, $value) { 332*a1a3b679SAndreas Boehler 333*a1a3b679SAndreas Boehler parent::offsetSet($name, $value); 334*a1a3b679SAndreas Boehler if (strtoupper($name)!=='VALUE') { 335*a1a3b679SAndreas Boehler return; 336*a1a3b679SAndreas Boehler } 337*a1a3b679SAndreas Boehler 338*a1a3b679SAndreas Boehler // This will ensure that dates are correctly encoded. 339*a1a3b679SAndreas Boehler $this->setDateTimes($this->getDateTimes()); 340*a1a3b679SAndreas Boehler 341*a1a3b679SAndreas Boehler } 342*a1a3b679SAndreas Boehler 343*a1a3b679SAndreas Boehler /** 344*a1a3b679SAndreas Boehler * Validates the node for correctness. 345*a1a3b679SAndreas Boehler * 346*a1a3b679SAndreas Boehler * The following options are supported: 347*a1a3b679SAndreas Boehler * Node::REPAIR - May attempt to automatically repair the problem. 348*a1a3b679SAndreas Boehler * 349*a1a3b679SAndreas Boehler * This method returns an array with detected problems. 350*a1a3b679SAndreas Boehler * Every element has the following properties: 351*a1a3b679SAndreas Boehler * 352*a1a3b679SAndreas Boehler * * level - problem level. 353*a1a3b679SAndreas Boehler * * message - A human-readable string describing the issue. 354*a1a3b679SAndreas Boehler * * node - A reference to the problematic node. 355*a1a3b679SAndreas Boehler * 356*a1a3b679SAndreas Boehler * The level means: 357*a1a3b679SAndreas Boehler * 1 - The issue was repaired (only happens if REPAIR was turned on) 358*a1a3b679SAndreas Boehler * 2 - An inconsequential issue 359*a1a3b679SAndreas Boehler * 3 - A severe issue. 360*a1a3b679SAndreas Boehler * 361*a1a3b679SAndreas Boehler * @param int $options 362*a1a3b679SAndreas Boehler * @return array 363*a1a3b679SAndreas Boehler */ 364*a1a3b679SAndreas Boehler public function validate($options = 0) { 365*a1a3b679SAndreas Boehler 366*a1a3b679SAndreas Boehler $messages = parent::validate($options); 367*a1a3b679SAndreas Boehler $valueType = $this->getValueType(); 368*a1a3b679SAndreas Boehler $values = $this->getParts(); 369*a1a3b679SAndreas Boehler try { 370*a1a3b679SAndreas Boehler foreach($values as $value) { 371*a1a3b679SAndreas Boehler switch($valueType) { 372*a1a3b679SAndreas Boehler case 'DATE' : 373*a1a3b679SAndreas Boehler $foo = DateTimeParser::parseDate($value); 374*a1a3b679SAndreas Boehler break; 375*a1a3b679SAndreas Boehler case 'DATE-TIME' : 376*a1a3b679SAndreas Boehler $foo = DateTimeParser::parseDateTime($value); 377*a1a3b679SAndreas Boehler break; 378*a1a3b679SAndreas Boehler } 379*a1a3b679SAndreas Boehler } 380*a1a3b679SAndreas Boehler } catch (\LogicException $e) { 381*a1a3b679SAndreas Boehler $messages[] = array( 382*a1a3b679SAndreas Boehler 'level' => 3, 383*a1a3b679SAndreas Boehler 'message' => 'The supplied value (' . $value . ') is not a correct ' . $valueType, 384*a1a3b679SAndreas Boehler 'node' => $this, 385*a1a3b679SAndreas Boehler ); 386*a1a3b679SAndreas Boehler } 387*a1a3b679SAndreas Boehler return $messages; 388*a1a3b679SAndreas Boehler 389*a1a3b679SAndreas Boehler } 390*a1a3b679SAndreas Boehler} 391