DateTime.php 10.0 KB

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