PageRenderTime 39ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 1ms

/tine20/library/qCal/lib/qCal/DateTime/Recur.php

https://gitlab.com/rsilveira1987/Expresso
PHP | 455 lines | 140 code | 80 blank | 235 comment | 15 complexity | 4e59f8d49d494bb621d308ef629eab84 MD5 | raw file
  1. <?php
  2. /**
  3. * Date/Time Recurrence object.
  4. * This class is used to define a date/time recurrence. It is capable of
  5. * creating recurrence rules for just about any type of recurrence. There are
  6. * many examples of recurrences below.
  7. *
  8. * @package qCal
  9. * @subpackage qCal_DateTime_Recur
  10. * @copyright Luke Visinoni (luke.visinoni@gmail.com)
  11. * @author Luke Visinoni (luke.visinoni@gmail.com)
  12. * @license GNU Lesser General Public License
  13. */
  14. abstract class qCal_DateTime_Recur implements Iterator, Countable {
  15. /**
  16. * @var array A list of valid recurrence frequencies
  17. * @access protected
  18. */
  19. protected static $validFreq = array(
  20. "yearly",
  21. "monthly",
  22. "weekly",
  23. "daily",
  24. "hourly",
  25. "minutely",
  26. "secondly",
  27. );
  28. /**
  29. * @var qCal_DateTime The start date/time of this recurrence
  30. * @access protected
  31. */
  32. protected $start;
  33. /**
  34. * @var integer The interval for this recurrence. For instance, every other year
  35. * would mean the interval is two. Every third year, interval is 3, etc.
  36. * @access protected
  37. */
  38. protected $interval = 1;
  39. /**
  40. * @var array A list of qCal_DateTime_Recur_Rule objects that define this recurrence
  41. * @access protected
  42. */
  43. protected $rules = array();
  44. /**
  45. * @var array A list of all of the date/time recurrences for the "current"
  46. * day. It is a simple one-dimensional array. Every call to next() advances
  47. * the pointer to the next item in this list until it is exhausted and then
  48. * another is generated for the next day in the recurrence set. Once all of
  49. * the days in the year are exhausted, the yearArray (see above) is
  50. * regenerated and the process starts all over again.
  51. * @access protected
  52. */
  53. protected $timeArray = array();
  54. /**
  55. * @var boolean When this is set to true, the timeArray is regenerated when
  56. * next() is called.
  57. * @access protected
  58. */
  59. protected $regenerateTimeArray = true;
  60. /**
  61. * @var qCal_DateTime The current recurrence in the set (comes from timeArray)
  62. * @todo might want to change this to a qCal_DateTime_Recur_Recurrence
  63. * object or something because this isn't always used for recurring events.
  64. * Sometimes it is to describe daylight savings time, and other things like
  65. * that. If we wrap it in a qCal_DateTime_Recur_Recurrence object, we can
  66. * do things to it that make it more flexible. We'll cross that bridge when
  67. * we get to it though. For now, just use qCal_DateTime.
  68. * @access protected
  69. */
  70. protected $current;
  71. /**
  72. * @var qCal_Date The current day in yearArray
  73. * @access public
  74. */
  75. protected $currentDay;
  76. /**
  77. * @var integer The amount of recurrences allowed in the set. If this is
  78. * set to false, then the recurrence set can recur an infinite number of times.
  79. * @access protected
  80. */
  81. protected $count = false;
  82. /**
  83. * @var qCal_DateTime The date/time that all recurrences must come before.
  84. * If this is set to false, then the recurrence can go on forever.
  85. * @access protected
  86. */
  87. protected $until = false;
  88. /**
  89. * @var integer The number of recurrences that have been looped over. Used
  90. * to determine at what point to stop recurring when setCount() is used.
  91. * @access protected
  92. */
  93. protected $recurrenceCount = 0;
  94. /**
  95. * Class constructor
  96. * This method instantiates the object by setting its "type" and its start date/time.
  97. * @param mixed $start Either a qCal_DateTime or a string representing one
  98. * @param integer $intvl An interval of years, months or whatever this recurrence type is
  99. * @param array $rules A list of rules to apply to the date/time recurrence
  100. * @throws qCal_DateTime_Exception_InvalidRecurrenceType If an invalid type is specified
  101. * @access public
  102. */
  103. public function __construct($start = null, $intvl = null, Array $rules = array()) {
  104. // set the start date/time and interval
  105. $this->setStart($start)
  106. ->setInterval($intvl);
  107. // loop through the array of rules and add them to this recur object
  108. foreach ($rules as $rule) {
  109. // if rule is not a supported rule type, report it
  110. if (!($rule instanceof qCal_DateTime_Recur_Rule)) {
  111. // first we need to determine what was passed in so we can complain properly
  112. if (is_object($rule)) {
  113. $ruletype = get_class($rule);
  114. } elseif (is_array($rule)) {
  115. $ruletype = "Array";
  116. } else {
  117. $ruletype = $rule;
  118. }
  119. // now throw an exception explaining why we couldn't accept the rule
  120. throw new qCal_DateTime_Exception_InvalidRecurrenceRule("'$ruletype' is an unsupported recurrence rule.");
  121. }
  122. $this->addRule($rule);
  123. }
  124. }
  125. /**
  126. * Factory Class
  127. * Generates a qCal_DateTime_Recur object that is specific to a certain
  128. * frequency type (yearly, monthly, weekly, etc.).
  129. * @param string $freq The recurrence frequency
  130. * @param mixed $start Either a qCal_DateTime object or a string representing one
  131. * @param integer $intvl The interval of years, months or whatever the recurrence type is
  132. * @return qCal_DateTime_Recur A date/time recurrence object of the specified frequency
  133. * @access public
  134. * @static
  135. */
  136. public static function factory($freq, $start, $intvl = null, Array $rules = array()) {
  137. $freq = strtolower($freq);
  138. if (!in_array($freq, self::$validFreq)) {
  139. throw new qCal_DateTime_Exception_InvalidRecurrenceFrequency("'$freq' is an unsupported recurrence frequency.");
  140. }
  141. $class = 'qCal_DateTime_Recur_' . ucfirst($freq);
  142. return new $class($start, $intvl, $rules);
  143. }
  144. /**
  145. * Set the date/time recurrence's start date (required)
  146. * @param mixed $start Either a qCal_DateTime object or a string representing one
  147. * @return $this
  148. * @access public
  149. */
  150. public function setStart($start) {
  151. if (!($start instanceof qCal_DateTime)) {
  152. $start = qCal_DateTime::factory($start);
  153. }
  154. $this->start = $start;
  155. return $this;
  156. }
  157. /**
  158. * Get the date/time recurrence's start date
  159. * @return qCal_DateTime
  160. * @access public
  161. */
  162. public function getStart() {
  163. return $this->start;
  164. }
  165. /**
  166. * Set the date/time interval.
  167. * @param integer The interval in years, months or whatever the recurrence type is
  168. * @return $this
  169. * @access public
  170. */
  171. public function setInterval($intvl = null) {
  172. if (is_null($intvl)) $intvl = 1;
  173. $this->interval = (integer) $intvl;
  174. return $this;
  175. }
  176. /**
  177. * Retrieve the date/time interval
  178. * @return integer The interval of time (for instance, every 3 years)
  179. * @access public
  180. */
  181. public function getInterval() {
  182. return (integer) $this->interval;
  183. }
  184. /**
  185. * Set the number or recurrences that are allowed (recurring stops after
  186. * this many recurrences).
  187. * @param integer The number or recurrences to allow
  188. * @return $this
  189. * @access public
  190. */
  191. public function setCount($count) {
  192. $this->count = (integer) $count;
  193. return $this;
  194. }
  195. /**
  196. * Retrieve the date/time interval
  197. * @return integer The interval in years, months or whatever the recurrence type is
  198. * @access public
  199. */
  200. public function getCount() {
  201. return (integer) $this->count;
  202. }
  203. /**
  204. * Set the end date/time for the recurrence set. No recurrences will be returned
  205. * beyond this date/time
  206. * @param mixed Either a qCal_DateTime object or a string representing one
  207. * @return $this
  208. * @access public
  209. */
  210. public function setUntil($datetime) {
  211. $this->until = ($datetime instanceof qCal_DateTime) ? $datetime : qCal_DateTime::factory($datetime);
  212. return $this;
  213. }
  214. /**
  215. * Retrieve the date/time that recurrences must come before.
  216. * @return qCal_DateTime The date/time that recurrences must come before
  217. * @access public
  218. */
  219. public function getUntil() {
  220. return $this->until;
  221. }
  222. /**
  223. * Add a qCal_DateTime_Recur_Rule object to this recurrence, changing the
  224. * way it recurs. Only one of each rule type is allowed, so if there is
  225. * already a rule of the type you are adding, it is overwritten.
  226. * @param qCal_DateTime_Recur_Rule $rule
  227. * @return $this
  228. * @access public
  229. */
  230. public function addRule(qCal_DateTime_Recur_Rule $rule) {
  231. $this->rules[get_class($rule)] = $rule;
  232. return $this;
  233. }
  234. /**
  235. * Determine if this recurrence contains the specified rule
  236. * @param string $rule The rule we want to determine the existence of
  237. * @return boolean Whether or not this recurrence contains the specified rule
  238. * @access public
  239. */
  240. public function hasRule($rule) {
  241. return (boolean) array_key_exists($rule, $this->rules);
  242. }
  243. /**
  244. * Retrieves the specified rule from the recurrence
  245. * @param string $rule The rule we want to retrieve
  246. * @return qCal_DateTime_Recur_Rule
  247. * @access public
  248. */
  249. public function getRule($rule) {
  250. if (!$this->hasRule($rule)) {
  251. throw new qCal_DateTime_Exception_MissingRecurrenceRule("This recurrence does not contain a '$rule' rule.");
  252. }
  253. return $this->rules[$rule];
  254. }
  255. /**
  256. * Retrieve the array of rules that this recurrence contains
  257. * @return array A list of rules that make up this recurrence
  258. * @access public
  259. */
  260. public function getRules() {
  261. return $this->rules;
  262. }
  263. /**
  264. * Begin Magic Methods
  265. */
  266. /**
  267. * Convert this object to a string (used in cases where you do something
  268. * like 'print $datetime')
  269. * @return string The current recurrence as a string
  270. * @access public
  271. */
  272. public function __toString() {
  273. $recurrence = $this->current();
  274. return $recurrence->__toString();
  275. }
  276. /**
  277. * Begin Iterator Methods
  278. */
  279. /**
  280. * Current
  281. * Retrieve the current recurrence in the set
  282. * @return qCal_DateTime_Recur_Recurrence The current recurrence in the set
  283. * @access public
  284. */
  285. public function current() {
  286. if (!$this->current) $this->rewind();
  287. return $this->current;
  288. }
  289. /**
  290. * Key
  291. * Retrieve the current recurrence's key
  292. * @return integer Each recurrence in the set has an associated key from 1
  293. * to however many recurrences are in the set
  294. * @access public
  295. * @todo Calling $this->get('20100423123000') should return a recurrence if
  296. * there is a recurrence at that date/time. So, returning a "key" should be
  297. * the date/time you want in YmdHis format.
  298. */
  299. public function key() {
  300. return $this->current->format('YmdHis');
  301. }
  302. /**
  303. * Next
  304. * Move the pointer to the next recurrence in the set. This method is
  305. * delegated to its children because (at least for now), there is no method
  306. * to get the next recurrence for any type of recurrence (yearly, monthly,
  307. * etc.). Once I am finished with all of the children classes, I may try to
  308. * refactor this class to be capable of finding recurrences for any type.
  309. * @return void
  310. * @access public
  311. * @abstract I want this method to be abstract, but for some reason I can't :(
  312. */
  313. public function next() {}
  314. /**
  315. * Rewind
  316. * Rewind the pointer to the first recurrence in the set
  317. * @return void
  318. * @access public
  319. */
  320. public function rewind() {
  321. // reset the recurrence count
  322. $this->recurrenceCount = 0;
  323. // set the "current" variable to the start date to rewind the "pointer"
  324. $this->current = $this->getStart();
  325. // tell the object to regenerate date and time arrays
  326. $this->regenerateTimeArray = true;
  327. // now use the "next()" method to set "current" to the first actual recurrence
  328. $this->next();
  329. return true;
  330. }
  331. /**
  332. * Valid
  333. * Determine if the current recurrence is within the boundaries of the recurrence set.
  334. * Recurrence count is computed in this method as well.
  335. * @return boolean If the current recurrence is valid, return true
  336. * @access public
  337. * @todo Child classes may have their own logic to apply here, so check that out...
  338. */
  339. public function valid() {
  340. // check to see if this date is past the "until" date
  341. if ($this->getUntil()) {
  342. if ($this->current->getUnixTimestamp() > $this->getUntil()->getUnixTimestamp()) {
  343. return false;
  344. }
  345. }
  346. // check to see if this recurrence makes more than the "count" allows
  347. $this->recurrenceCount++;
  348. if ($this->getCount()) {
  349. if ($this->recurrenceCount > $this->getCount()) {
  350. return false;
  351. }
  352. }
  353. return true;
  354. }
  355. /**
  356. * Count
  357. * If there is a finite number of recurrences, that number is returned.
  358. * If there is an infinite number of recurrences, -1 is returned.
  359. * Use this method only if you need it. This is a very expensive method.
  360. * @return integer The number of recurrences in the set
  361. * @access public
  362. */
  363. public function count() {
  364. if ($this->getCount() || $this->getUntil()) {
  365. $count = 0;
  366. foreach ($this as $recurrence) {
  367. $count++;
  368. }
  369. } else {
  370. $count = -1;
  371. }
  372. $this->rewind();
  373. return $count;
  374. }
  375. }